/**
 * Partner Tax Info Service
 * Servicio para gestion de informacion fiscal de socios comerciales
 *
 * @module Partners
 */

import { DataSource, Repository } from 'typeorm';
import { PartnerTaxInfo } from '../entities';

/**
 * Service context for multi-tenant operations
 */
export interface ServiceContext {
  tenantId: string;
  userId?: string;
}

export interface CreatePartnerTaxInfoDto {
  partnerId: string;
  taxIdType?: string;
  taxIdCountry?: string;
  satRegime?: string;
  satRegimeName?: string;
  cfdiUse?: string;
  cfdiUseName?: string;
  fiscalZipCode?: string;
  withholdingIsr?: number;
  withholdingIva?: number;
}

export interface UpdatePartnerTaxInfoDto {
  taxIdType?: string;
  taxIdCountry?: string;
  satRegime?: string;
  satRegimeName?: string;
  cfdiUse?: string;
  cfdiUseName?: string;
  fiscalZipCode?: string;
  withholdingIsr?: number;
  withholdingIva?: number;
}

export interface VerifyTaxInfoDto {
  verificationSource: string;
}

// Common SAT Regimes
export const SAT_REGIMES = {
  '601': 'General de Ley Personas Morales',
  '603': 'Personas Morales con Fines no Lucrativos',
  '605': 'Sueldos y Salarios e Ingresos Asimilados a Salarios',
  '606': 'Arrendamiento',
  '607': 'Regimen de Enajenacion o Adquisicion de Bienes',
  '608': 'Demas Ingresos',
  '610': 'Residentes en el Extranjero sin Establecimiento Permanente en Mexico',
  '611': 'Ingresos por Dividendos (socios y accionistas)',
  '612': 'Personas Fisicas con Actividades Empresariales y Profesionales',
  '614': 'Ingresos por Intereses',
  '615': 'Regimen de los Ingresos por Obtencion de Premios',
  '616': 'Sin Obligaciones Fiscales',
  '620': 'Sociedades Cooperativas de Produccion que optan por diferir sus ingresos',
  '621': 'Incorporacion Fiscal',
  '622': 'Actividades Agricolas, Ganaderas, Silvicolas y Pesqueras',
  '623': 'Opcional para Grupos de Sociedades',
  '624': 'Coordinados',
  '625': 'Regimen de las Actividades Empresariales con ingresos a traves de Plataformas Tecnologicas',
  '626': 'Regimen Simplificado de Confianza',
};

// Common CFDI Uses
export const CFDI_USES = {
  'G01': 'Adquisicion de mercancias',
  'G02': 'Devoluciones, descuentos o bonificaciones',
  'G03': 'Gastos en general',
  'I01': 'Construcciones',
  'I02': 'Mobiliario y equipo de oficina por inversiones',
  'I03': 'Equipo de transporte',
  'I04': 'Equipo de computo y accesorios',
  'I05': 'Dados, troqueles, moldes, matrices y herramental',
  'I06': 'Comunicaciones telefonicas',
  'I07': 'Comunicaciones satelitales',
  'I08': 'Otra maquinaria y equipo',
  'D01': 'Honorarios medicos, dentales y gastos hospitalarios',
  'D02': 'Gastos medicos por incapacidad o discapacidad',
  'D03': 'Gastos funerales',
  'D04': 'Donativos',
  'D05': 'Intereses reales efectivamente pagados por creditos hipotecarios',
  'D06': 'Aportaciones voluntarias al SAR',
  'D07': 'Primas por seguros de gastos medicos',
  'D08': 'Gastos de transportacion escolar obligatoria',
  'D09': 'Depositos en cuentas para el ahorro',
  'D10': 'Pagos por servicios educativos',
  'S01': 'Sin efectos fiscales',
  'CP01': 'Pagos',
  'CN01': 'Nomina',
};

export class PartnerTaxInfoService {
  private readonly repository: Repository<PartnerTaxInfo>;

  constructor(dataSource: DataSource) {
    this.repository = dataSource.getRepository(PartnerTaxInfo);
  }

  async findByPartnerId(partnerId: string): Promise<PartnerTaxInfo | null> {
    return this.repository.findOne({
      where: { partnerId },
      relations: ['partner'],
    });
  }

  async findById(id: string): Promise<PartnerTaxInfo | null> {
    return this.repository.findOne({
      where: { id },
      relations: ['partner'],
    });
  }

  async create(
    _ctx: ServiceContext,
    dto: CreatePartnerTaxInfoDto
  ): Promise<PartnerTaxInfo> {
    // Check if tax info already exists for partner
    const existing = await this.findByPartnerId(dto.partnerId);
    if (existing) {
      throw new Error('Tax info already exists for this partner. Use update instead.');
    }

    // Validate SAT regime if provided
    if (dto.satRegime && !SAT_REGIMES[dto.satRegime as keyof typeof SAT_REGIMES]) {
      throw new Error('Invalid SAT regime code');
    }

    // Validate CFDI use if provided
    if (dto.cfdiUse && !CFDI_USES[dto.cfdiUse as keyof typeof CFDI_USES]) {
      throw new Error('Invalid CFDI use code');
    }

    // Auto-fill regime and CFDI use names if codes provided
    const taxInfo = this.repository.create({
      ...dto,
      taxIdCountry: dto.taxIdCountry ?? 'MEX',
      satRegimeName: dto.satRegimeName ?? SAT_REGIMES[dto.satRegime as keyof typeof SAT_REGIMES],
      cfdiUseName: dto.cfdiUseName ?? CFDI_USES[dto.cfdiUse as keyof typeof CFDI_USES],
      withholdingIsr: dto.withholdingIsr ?? 0,
      withholdingIva: dto.withholdingIva ?? 0,
    });

    return this.repository.save(taxInfo);
  }

  async update(
    _ctx: ServiceContext,
    id: string,
    dto: UpdatePartnerTaxInfoDto
  ): Promise<PartnerTaxInfo | null> {
    const taxInfo = await this.findById(id);
    if (!taxInfo) {
      return null;
    }

    // Validate SAT regime if provided
    if (dto.satRegime && !SAT_REGIMES[dto.satRegime as keyof typeof SAT_REGIMES]) {
      throw new Error('Invalid SAT regime code');
    }

    // Validate CFDI use if provided
    if (dto.cfdiUse && !CFDI_USES[dto.cfdiUse as keyof typeof CFDI_USES]) {
      throw new Error('Invalid CFDI use code');
    }

    // Auto-fill names if codes changed
    const updateData = { ...dto };
    if (dto.satRegime && dto.satRegime !== taxInfo.satRegime) {
      updateData.satRegimeName = dto.satRegimeName ?? SAT_REGIMES[dto.satRegime as keyof typeof SAT_REGIMES];
    }
    if (dto.cfdiUse && dto.cfdiUse !== taxInfo.cfdiUse) {
      updateData.cfdiUseName = dto.cfdiUseName ?? CFDI_USES[dto.cfdiUse as keyof typeof CFDI_USES];
    }

    Object.assign(taxInfo, updateData);
    return this.repository.save(taxInfo);
  }

  async upsert(
    ctx: ServiceContext,
    partnerId: string,
    dto: Omit<CreatePartnerTaxInfoDto, 'partnerId'>
  ): Promise<PartnerTaxInfo> {
    const existing = await this.findByPartnerId(partnerId);

    if (existing) {
      const updated = await this.update(ctx, existing.id, dto);
      if (!updated) {
        throw new Error('Failed to update tax info');
      }
      return updated;
    }

    return this.create(ctx, { ...dto, partnerId });
  }

  async verify(
    _ctx: ServiceContext,
    id: string,
    dto: VerifyTaxInfoDto
  ): Promise<PartnerTaxInfo | null> {
    const taxInfo = await this.findById(id);
    if (!taxInfo) {
      return null;
    }

    taxInfo.isVerified = true;
    taxInfo.verifiedAt = new Date();
    taxInfo.verificationSource = dto.verificationSource;

    return this.repository.save(taxInfo);
  }

  async unverify(_ctx: ServiceContext, id: string): Promise<PartnerTaxInfo | null> {
    const taxInfo = await this.findById(id);
    if (!taxInfo) {
      return null;
    }

    taxInfo.isVerified = false;
    taxInfo.verifiedAt = null;
    taxInfo.verificationSource = null;

    return this.repository.save(taxInfo);
  }

  async delete(id: string): Promise<boolean> {
    const result = await this.repository.delete({ id });
    return result.affected ? result.affected > 0 : false;
  }

  getSatRegimes(): Record<string, string> {
    return SAT_REGIMES;
  }

  getCfdiUses(): Record<string, string> {
    return CFDI_USES;
  }

  async getWithholdingTotals(partnerId: string): Promise<{
    withholdingIsr: number;
    withholdingIva: number;
    totalWithholding: number;
  }> {
    const taxInfo = await this.findByPartnerId(partnerId);

    if (!taxInfo) {
      return {
        withholdingIsr: 0,
        withholdingIva: 0,
        totalWithholding: 0,
      };
    }

    const isr = Number(taxInfo.withholdingIsr || 0);
    const iva = Number(taxInfo.withholdingIva || 0);

    return {
      withholdingIsr: isr,
      withholdingIva: iva,
      totalWithholding: isr + iva,
    };
  }
}
