import { Repository, In } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import { Tax, TaxType } from './entities/index.js';
import { NotFoundError, ConflictError } from '../../shared/errors/index.js';
import { logger } from '../../shared/utils/logger.js';

// ===== Interfaces =====

export interface CreateTaxDto {
  companyId: string;
  name: string;
  code: string;
  taxType: TaxType;
  amount: number;
  includedInPrice?: boolean;
}

export interface UpdateTaxDto {
  name?: string;
  code?: string;
  taxType?: TaxType;
  amount?: number;
  includedInPrice?: boolean;
  active?: boolean;
}

export interface TaxFilters {
  companyId?: string;
  taxType?: TaxType;
  active?: boolean;
  search?: string;
  page?: number;
  limit?: number;
}

export interface TaxWithRelations extends Tax {
  companyName?: string;
}

// ===== Tax Calculation Interfaces =====

export interface TaxCalculationInput {
  quantity: number;
  priceUnit: number;
  discount: number;
  taxIds: string[];
}

export interface TaxBreakdownItem {
  taxId: string;
  taxName: string;
  taxCode: string;
  taxRate: number;
  includedInPrice: boolean;
  base: number;
  taxAmount: number;
}

export interface TaxCalculationResult {
  amountUntaxed: number;
  amountTax: number;
  amountTotal: number;
  taxBreakdown: TaxBreakdownItem[];
}

// ===== TaxesService Class =====

class TaxesService {
  private taxRepository: Repository<Tax>;

  constructor() {
    this.taxRepository = AppDataSource.getRepository(Tax);
  }

  /**
   * Get all taxes with filters and pagination
   */
  async findAll(
    tenantId: string,
    filters: TaxFilters = {}
  ): Promise<{ data: TaxWithRelations[]; total: number }> {
    try {
      const {
        companyId,
        taxType,
        active,
        search,
        page = 1,
        limit = 20
      } = filters;
      const skip = (page - 1) * limit;

      const queryBuilder = this.taxRepository
        .createQueryBuilder('tax')
        .leftJoin('tax.company', 'company')
        .addSelect(['company.name'])
        .where('tax.tenantId = :tenantId', { tenantId });

      // Apply filters
      if (companyId) {
        queryBuilder.andWhere('tax.companyId = :companyId', { companyId });
      }

      if (taxType) {
        queryBuilder.andWhere('tax.taxType = :taxType', { taxType });
      }

      if (active !== undefined) {
        queryBuilder.andWhere('tax.active = :active', { active });
      }

      if (search) {
        queryBuilder.andWhere(
          '(tax.name ILIKE :search OR tax.code ILIKE :search)',
          { search: `%${search}%` }
        );
      }

      // Get total count
      const total = await queryBuilder.getCount();

      // Get paginated results
      const taxes = await queryBuilder
        .orderBy('tax.name', 'ASC')
        .skip(skip)
        .take(limit)
        .getMany();

      // Map to include relation names
      const data: TaxWithRelations[] = taxes.map(tax => ({
        ...tax,
        companyName: tax.company?.name,
      }));

      logger.debug('Taxes retrieved', { tenantId, count: data.length, total });

      return { data, total };
    } catch (error) {
      logger.error('Error retrieving taxes', {
        error: (error as Error).message,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Get tax by ID
   */
  async findById(id: string, tenantId: string): Promise<TaxWithRelations> {
    try {
      const tax = await this.taxRepository
        .createQueryBuilder('tax')
        .leftJoin('tax.company', 'company')
        .addSelect(['company.name'])
        .where('tax.id = :id', { id })
        .andWhere('tax.tenantId = :tenantId', { tenantId })
        .getOne();

      if (!tax) {
        throw new NotFoundError('Impuesto no encontrado');
      }

      return {
        ...tax,
        companyName: tax.company?.name,
      };
    } catch (error) {
      logger.error('Error finding tax', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Create a new tax
   */
  async create(
    dto: CreateTaxDto,
    tenantId: string,
    userId: string
  ): Promise<Tax> {
    try {
      // Check unique code within tenant
      const existing = await this.taxRepository.findOne({
        where: {
          tenantId,
          code: dto.code,
        },
      });

      if (existing) {
        throw new ConflictError('Ya existe un impuesto con ese codigo');
      }

      // Create tax
      const tax = this.taxRepository.create({
        tenantId,
        companyId: dto.companyId,
        name: dto.name,
        code: dto.code,
        taxType: dto.taxType,
        amount: dto.amount,
        includedInPrice: dto.includedInPrice ?? false,
        createdBy: userId,
      });

      await this.taxRepository.save(tax);

      logger.info('Tax created', {
        taxId: tax.id,
        tenantId,
        code: tax.code,
        createdBy: userId,
      });

      return tax;
    } catch (error) {
      logger.error('Error creating tax', {
        error: (error as Error).message,
        tenantId,
        dto,
      });
      throw error;
    }
  }

  /**
   * Update a tax
   */
  async update(
    id: string,
    dto: UpdateTaxDto,
    tenantId: string,
    userId: string
  ): Promise<Tax> {
    try {
      const existing = await this.findById(id, tenantId);

      // Check unique code if updating
      if (dto.code !== undefined && dto.code !== existing.code) {
        const existingCode = await this.taxRepository.findOne({
          where: {
            tenantId,
            code: dto.code,
          },
        });

        if (existingCode && existingCode.id !== id) {
          throw new ConflictError('Ya existe un impuesto con ese codigo');
        }
      }

      // Update allowed fields
      if (dto.name !== undefined) existing.name = dto.name;
      if (dto.code !== undefined) existing.code = dto.code;
      if (dto.taxType !== undefined) existing.taxType = dto.taxType;
      if (dto.amount !== undefined) existing.amount = dto.amount;
      if (dto.includedInPrice !== undefined) existing.includedInPrice = dto.includedInPrice;
      if (dto.active !== undefined) existing.active = dto.active;

      existing.updatedBy = userId;
      existing.updatedAt = new Date();

      await this.taxRepository.save(existing);

      logger.info('Tax updated', {
        taxId: id,
        tenantId,
        updatedBy: userId,
      });

      return await this.findById(id, tenantId);
    } catch (error) {
      logger.error('Error updating tax', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Delete a tax (hard delete)
   */
  async delete(id: string, tenantId: string): Promise<void> {
    try {
      await this.findById(id, tenantId);

      // Check if tax is used in any invoice lines (use raw query for this check)
      const usageCheck = await this.taxRepository.query(
        `SELECT COUNT(*) as count FROM financial.invoice_lines
         WHERE $1 = ANY(tax_ids)`,
        [id]
      );

      if (parseInt(usageCheck[0]?.count || '0', 10) > 0) {
        throw new ConflictError('No se puede eliminar: el impuesto esta siendo usado en facturas');
      }

      await this.taxRepository.delete({ id, tenantId });

      logger.info('Tax deleted', {
        taxId: id,
        tenantId,
      });
    } catch (error) {
      logger.error('Error deleting tax', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Calcula impuestos para una linea de documento
   * Sigue la logica de Odoo para calculos de IVA
   *
   * IMPORTANT: This calculation logic must be preserved exactly as per MIGRATION_GUIDE.md
   */
  async calculateTaxes(
    lineData: TaxCalculationInput,
    tenantId: string,
    transactionType: 'sales' | 'purchase' = 'sales'
  ): Promise<TaxCalculationResult> {
    // Validar inputs
    if (lineData.quantity <= 0 || lineData.priceUnit < 0) {
      return {
        amountUntaxed: 0,
        amountTax: 0,
        amountTotal: 0,
        taxBreakdown: [],
      };
    }

    // Calcular subtotal antes de impuestos
    const subtotal = lineData.quantity * lineData.priceUnit;
    const discountAmount = subtotal * (lineData.discount || 0) / 100;
    const amountUntaxed = subtotal - discountAmount;

    // Si no hay impuestos, retornar solo el monto sin impuestos
    if (!lineData.taxIds || lineData.taxIds.length === 0) {
      return {
        amountUntaxed,
        amountTax: 0,
        amountTotal: amountUntaxed,
        taxBreakdown: [],
      };
    }

    // Obtener impuestos de la BD usando TypeORM
    const taxResults = await this.taxRepository.find({
      where: {
        id: In(lineData.taxIds),
        tenantId,
        active: true,
      },
    });

    // Filter by transaction type (sales, purchase, or all)
    const applicableTaxes = taxResults.filter(
      tax => tax.taxType === transactionType || tax.taxType === TaxType.ALL
    );

    if (applicableTaxes.length === 0) {
      return {
        amountUntaxed,
        amountTax: 0,
        amountTotal: amountUntaxed,
        taxBreakdown: [],
      };
    }

    // Calcular impuestos
    const taxBreakdown: TaxBreakdownItem[] = [];
    let totalTax = 0;

    for (const tax of applicableTaxes) {
      let taxBase = amountUntaxed;
      let taxAmount: number;

      if (tax.includedInPrice) {
        // Precio incluye impuesto (IVA incluido)
        // Base = Precio / (1 + tasa)
        // Impuesto = Precio - Base
        taxBase = amountUntaxed / (1 + Number(tax.amount) / 100);
        taxAmount = amountUntaxed - taxBase;
      } else {
        // Precio sin impuesto (IVA anadido)
        // Impuesto = Base * tasa
        taxAmount = amountUntaxed * Number(tax.amount) / 100;
      }

      taxBreakdown.push({
        taxId: tax.id,
        taxName: tax.name,
        taxCode: tax.code,
        taxRate: Number(tax.amount),
        includedInPrice: tax.includedInPrice,
        base: Math.round(taxBase * 100) / 100,
        taxAmount: Math.round(taxAmount * 100) / 100,
      });

      totalTax += taxAmount;
    }

    // Redondear a 2 decimales
    const finalAmountTax = Math.round(totalTax * 100) / 100;
    const finalAmountUntaxed = Math.round(amountUntaxed * 100) / 100;
    const finalAmountTotal = Math.round((amountUntaxed + finalAmountTax) * 100) / 100;

    return {
      amountUntaxed: finalAmountUntaxed,
      amountTax: finalAmountTax,
      amountTotal: finalAmountTotal,
      taxBreakdown,
    };
  }

  /**
   * Calcula impuestos para multiples lineas (ej: para totales de documento)
   */
  async calculateDocumentTaxes(
    lines: TaxCalculationInput[],
    tenantId: string,
    transactionType: 'sales' | 'purchase' = 'sales'
  ): Promise<TaxCalculationResult> {
    let totalUntaxed = 0;
    let totalTax = 0;
    const allBreakdown: TaxBreakdownItem[] = [];

    for (const line of lines) {
      const result = await this.calculateTaxes(line, tenantId, transactionType);
      totalUntaxed += result.amountUntaxed;
      totalTax += result.amountTax;
      allBreakdown.push(...result.taxBreakdown);
    }

    // Consolidar breakdown por impuesto
    const consolidatedBreakdown = new Map<string, TaxBreakdownItem>();
    for (const item of allBreakdown) {
      const existing = consolidatedBreakdown.get(item.taxId);
      if (existing) {
        existing.base += item.base;
        existing.taxAmount += item.taxAmount;
      } else {
        consolidatedBreakdown.set(item.taxId, { ...item });
      }
    }

    return {
      amountUntaxed: Math.round(totalUntaxed * 100) / 100,
      amountTax: Math.round(totalTax * 100) / 100,
      amountTotal: Math.round((totalUntaxed + totalTax) * 100) / 100,
      taxBreakdown: Array.from(consolidatedBreakdown.values()),
    };
  }
}

// ===== Export Singleton Instance =====

export const taxesService = new TaxesService();

// Re-export TaxType for backwards compatibility
export { TaxType };
