import { Repository } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import {
  TaxCategory,
  FiscalRegime,
  CfdiUse,
  PaymentMethod,
  PaymentType,
  WithholdingType,
  PersonType,
} from './entities/index.js';
import { NotFoundError } from '../../shared/errors/index.js';
import { logger } from '../../shared/utils/logger.js';

// ==========================================
// TAX CATEGORIES SERVICE
// ==========================================

export interface TaxCategoryFilter {
  taxNature?: string;
  active?: boolean;
}

class TaxCategoriesService {
  private repository: Repository<TaxCategory>;

  constructor() {
    this.repository = AppDataSource.getRepository(TaxCategory);
  }

  async findAll(filter: TaxCategoryFilter = {}): Promise<TaxCategory[]> {
    logger.debug('Finding all tax categories', { filter });

    const qb = this.repository.createQueryBuilder('tc');

    if (filter.taxNature) {
      qb.andWhere('tc.tax_nature = :taxNature', { taxNature: filter.taxNature });
    }

    if (filter.active !== undefined) {
      qb.andWhere('tc.is_active = :active', { active: filter.active });
    }

    qb.orderBy('tc.name', 'ASC');

    return qb.getMany();
  }

  async findById(id: string): Promise<TaxCategory> {
    logger.debug('Finding tax category by id', { id });

    const category = await this.repository.findOne({ where: { id } });

    if (!category) {
      throw new NotFoundError('Categoría de impuesto no encontrada');
    }

    return category;
  }

  async findByCode(code: string): Promise<TaxCategory | null> {
    logger.debug('Finding tax category by code', { code });

    return this.repository.findOne({
      where: { code: code.toUpperCase() },
    });
  }

  async findBySatCode(satCode: string): Promise<TaxCategory | null> {
    logger.debug('Finding tax category by SAT code', { satCode });

    return this.repository.findOne({
      where: { satCode },
    });
  }
}

// ==========================================
// FISCAL REGIMES SERVICE
// ==========================================

export interface FiscalRegimeFilter {
  appliesTo?: PersonType;
  active?: boolean;
}

class FiscalRegimesService {
  private repository: Repository<FiscalRegime>;

  constructor() {
    this.repository = AppDataSource.getRepository(FiscalRegime);
  }

  async findAll(filter: FiscalRegimeFilter = {}): Promise<FiscalRegime[]> {
    logger.debug('Finding all fiscal regimes', { filter });

    const qb = this.repository.createQueryBuilder('fr');

    if (filter.appliesTo) {
      qb.andWhere('(fr.applies_to = :appliesTo OR fr.applies_to = :both)', {
        appliesTo: filter.appliesTo,
        both: PersonType.BOTH,
      });
    }

    if (filter.active !== undefined) {
      qb.andWhere('fr.is_active = :active', { active: filter.active });
    }

    qb.orderBy('fr.code', 'ASC');

    return qb.getMany();
  }

  async findById(id: string): Promise<FiscalRegime> {
    logger.debug('Finding fiscal regime by id', { id });

    const regime = await this.repository.findOne({ where: { id } });

    if (!regime) {
      throw new NotFoundError('Régimen fiscal no encontrado');
    }

    return regime;
  }

  async findByCode(code: string): Promise<FiscalRegime | null> {
    logger.debug('Finding fiscal regime by code', { code });

    return this.repository.findOne({
      where: { code },
    });
  }

  async findForPersonType(personType: PersonType): Promise<FiscalRegime[]> {
    logger.debug('Finding fiscal regimes for person type', { personType });

    return this.repository
      .createQueryBuilder('fr')
      .where('fr.applies_to = :personType OR fr.applies_to = :both', {
        personType,
        both: PersonType.BOTH,
      })
      .andWhere('fr.is_active = true')
      .orderBy('fr.code', 'ASC')
      .getMany();
  }
}

// ==========================================
// CFDI USES SERVICE
// ==========================================

export interface CfdiUseFilter {
  appliesTo?: PersonType;
  active?: boolean;
}

class CfdiUsesService {
  private repository: Repository<CfdiUse>;

  constructor() {
    this.repository = AppDataSource.getRepository(CfdiUse);
  }

  async findAll(filter: CfdiUseFilter = {}): Promise<CfdiUse[]> {
    logger.debug('Finding all CFDI uses', { filter });

    const qb = this.repository.createQueryBuilder('cu');

    if (filter.appliesTo) {
      qb.andWhere('(cu.applies_to = :appliesTo OR cu.applies_to = :both)', {
        appliesTo: filter.appliesTo,
        both: PersonType.BOTH,
      });
    }

    if (filter.active !== undefined) {
      qb.andWhere('cu.is_active = :active', { active: filter.active });
    }

    qb.orderBy('cu.code', 'ASC');

    return qb.getMany();
  }

  async findById(id: string): Promise<CfdiUse> {
    logger.debug('Finding CFDI use by id', { id });

    const cfdiUse = await this.repository.findOne({ where: { id } });

    if (!cfdiUse) {
      throw new NotFoundError('Uso de CFDI no encontrado');
    }

    return cfdiUse;
  }

  async findByCode(code: string): Promise<CfdiUse | null> {
    logger.debug('Finding CFDI use by code', { code });

    return this.repository.findOne({
      where: { code: code.toUpperCase() },
    });
  }

  async findForPersonType(personType: PersonType): Promise<CfdiUse[]> {
    logger.debug('Finding CFDI uses for person type', { personType });

    return this.repository
      .createQueryBuilder('cu')
      .where('cu.applies_to = :personType OR cu.applies_to = :both', {
        personType,
        both: PersonType.BOTH,
      })
      .andWhere('cu.is_active = true')
      .orderBy('cu.code', 'ASC')
      .getMany();
  }

  async findForRegime(regimeCode: string): Promise<CfdiUse[]> {
    logger.debug('Finding CFDI uses for regime', { regimeCode });

    // Get all active CFDI uses and filter by allowed regimes
    const all = await this.repository
      .createQueryBuilder('cu')
      .where('cu.is_active = true')
      .orderBy('cu.code', 'ASC')
      .getMany();

    return all.filter(
      (cu) => !cu.allowedRegimes || cu.allowedRegimes.length === 0 || cu.allowedRegimes.includes(regimeCode)
    );
  }
}

// ==========================================
// PAYMENT METHODS SERVICE
// ==========================================

export interface PaymentMethodFilter {
  requiresBankInfo?: boolean;
  active?: boolean;
}

class PaymentMethodsService {
  private repository: Repository<PaymentMethod>;

  constructor() {
    this.repository = AppDataSource.getRepository(PaymentMethod);
  }

  async findAll(filter: PaymentMethodFilter = {}): Promise<PaymentMethod[]> {
    logger.debug('Finding all payment methods', { filter });

    const qb = this.repository.createQueryBuilder('pm');

    if (filter.requiresBankInfo !== undefined) {
      qb.andWhere('pm.requires_bank_info = :requiresBankInfo', {
        requiresBankInfo: filter.requiresBankInfo,
      });
    }

    if (filter.active !== undefined) {
      qb.andWhere('pm.is_active = :active', { active: filter.active });
    }

    qb.orderBy('pm.code', 'ASC');

    return qb.getMany();
  }

  async findById(id: string): Promise<PaymentMethod> {
    logger.debug('Finding payment method by id', { id });

    const method = await this.repository.findOne({ where: { id } });

    if (!method) {
      throw new NotFoundError('Forma de pago no encontrada');
    }

    return method;
  }

  async findByCode(code: string): Promise<PaymentMethod | null> {
    logger.debug('Finding payment method by code', { code });

    return this.repository.findOne({
      where: { code },
    });
  }
}

// ==========================================
// PAYMENT TYPES SERVICE
// ==========================================

export interface PaymentTypeFilter {
  active?: boolean;
}

class PaymentTypesService {
  private repository: Repository<PaymentType>;

  constructor() {
    this.repository = AppDataSource.getRepository(PaymentType);
  }

  async findAll(filter: PaymentTypeFilter = {}): Promise<PaymentType[]> {
    logger.debug('Finding all payment types', { filter });

    const qb = this.repository.createQueryBuilder('pt');

    if (filter.active !== undefined) {
      qb.andWhere('pt.is_active = :active', { active: filter.active });
    }

    qb.orderBy('pt.code', 'ASC');

    return qb.getMany();
  }

  async findById(id: string): Promise<PaymentType> {
    logger.debug('Finding payment type by id', { id });

    const type = await this.repository.findOne({ where: { id } });

    if (!type) {
      throw new NotFoundError('Método de pago no encontrado');
    }

    return type;
  }

  async findByCode(code: string): Promise<PaymentType | null> {
    logger.debug('Finding payment type by code', { code });

    return this.repository.findOne({
      where: { code: code.toUpperCase() },
    });
  }
}

// ==========================================
// WITHHOLDING TYPES SERVICE
// ==========================================

export interface WithholdingTypeFilter {
  taxCategoryId?: string;
  active?: boolean;
}

class WithholdingTypesService {
  private repository: Repository<WithholdingType>;

  constructor() {
    this.repository = AppDataSource.getRepository(WithholdingType);
  }

  async findAll(filter: WithholdingTypeFilter = {}): Promise<WithholdingType[]> {
    logger.debug('Finding all withholding types', { filter });

    const qb = this.repository
      .createQueryBuilder('wt')
      .leftJoinAndSelect('wt.taxCategory', 'taxCategory');

    if (filter.taxCategoryId) {
      qb.andWhere('wt.tax_category_id = :taxCategoryId', {
        taxCategoryId: filter.taxCategoryId,
      });
    }

    if (filter.active !== undefined) {
      qb.andWhere('wt.is_active = :active', { active: filter.active });
    }

    qb.orderBy('wt.code', 'ASC');

    return qb.getMany();
  }

  async findById(id: string): Promise<WithholdingType> {
    logger.debug('Finding withholding type by id', { id });

    const type = await this.repository.findOne({
      where: { id },
      relations: ['taxCategory'],
    });

    if (!type) {
      throw new NotFoundError('Tipo de retención no encontrado');
    }

    return type;
  }

  async findByCode(code: string): Promise<WithholdingType | null> {
    logger.debug('Finding withholding type by code', { code });

    return this.repository.findOne({
      where: { code },
      relations: ['taxCategory'],
    });
  }

  async findByTaxCategory(taxCategoryId: string): Promise<WithholdingType[]> {
    logger.debug('Finding withholding types by tax category', { taxCategoryId });

    return this.repository.find({
      where: { taxCategoryId, isActive: true },
      relations: ['taxCategory'],
      order: { code: 'ASC' },
    });
  }
}

// ==========================================
// SERVICE EXPORTS
// ==========================================

export const taxCategoriesService = new TaxCategoriesService();
export const fiscalRegimesService = new FiscalRegimesService();
export const cfdiUsesService = new CfdiUsesService();
export const paymentMethodsService = new PaymentMethodsService();
export const paymentTypesService = new PaymentTypesService();
export const withholdingTypesService = new WithholdingTypesService();
