import { Repository, DataSource } from 'typeorm';
import { FiguraTransporte, TipoFigura, ParteTransporte } from '../entities';
import { CartaPorte, EstadoCartaPorte } from '../entities/carta-porte.entity';

/**
 * DTO para crear figura de transporte
 */
export interface CreateFiguraDto {
  tipoFigura: string;
  rfcFigura?: string;
  nombreFigura?: string;
  numLicencia?: string;
  pais?: string;
  estado?: string;
  codigoPostal?: string;
  calle?: string;
  partesTransporte?: ParteTransporte[];
}

/**
 * DTO para actualizar figura de transporte
 */
export interface UpdateFiguraDto extends Partial<CreateFiguraDto> {}

/**
 * Resultado de validacion de figuras requeridas
 */
export interface ValidacionFigurasRequeridas {
  valid: boolean;
  tieneOperador: boolean;
  tienePropietario: boolean;
  operadores: FiguraTransporte[];
  totalFiguras: number;
  errors: string[];
  warnings: string[];
}

/**
 * Servicio para gestion de figuras de transporte de Carta Porte
 * CFDI 3.1 Compliance
 */
export class FiguraTransporteService {
  private figuraRepository: Repository<FiguraTransporte>;
  private cartaPorteRepository: Repository<CartaPorte>;

  constructor(private readonly dataSource: DataSource) {
    this.figuraRepository = dataSource.getRepository(FiguraTransporte);
    this.cartaPorteRepository = dataSource.getRepository(CartaPorte);
  }

  /**
   * Verifica que la Carta Porte exista y pertenezca al tenant
   */
  private async getCartaPorteOrFail(
    cartaPorteId: string,
    tenantId: string
  ): Promise<CartaPorte> {
    const cartaPorte = await this.cartaPorteRepository.findOne({
      where: { id: cartaPorteId, tenantId },
    });

    if (!cartaPorte) {
      throw new Error('Carta Porte no encontrada');
    }

    return cartaPorte;
  }

  /**
   * Verifica que la Carta Porte este en estado editable
   */
  private assertEditable(cartaPorte: CartaPorte): void {
    if (cartaPorte.estado !== EstadoCartaPorte.BORRADOR) {
      throw new Error(
        `No se pueden modificar figuras de transporte en estado ${cartaPorte.estado}`
      );
    }
  }

  /**
   * Crea una nueva figura de transporte
   */
  async create(
    tenantId: string,
    cartaPorteId: string,
    data: CreateFiguraDto
  ): Promise<FiguraTransporte> {
    const cartaPorte = await this.getCartaPorteOrFail(cartaPorteId, tenantId);
    this.assertEditable(cartaPorte);

    // Validar datos segun tipo de figura
    this.validateFiguraData(data);

    const figura = this.figuraRepository.create({
      tenantId,
      cartaPorteId,
      tipoFigura: data.tipoFigura,
      rfcFigura: data.rfcFigura || null,
      nombreFigura: data.nombreFigura || null,
      numLicencia: data.numLicencia || null,
      pais: data.pais || null,
      estado: data.estado || null,
      codigoPostal: data.codigoPostal || null,
      calle: data.calle || null,
      partesTransporte: data.partesTransporte || null,
    });

    return this.figuraRepository.save(figura);
  }

  /**
   * Obtiene todas las figuras de una Carta Porte
   */
  async findByCartaParte(
    tenantId: string,
    cartaPorteId: string
  ): Promise<FiguraTransporte[]> {
    await this.getCartaPorteOrFail(cartaPorteId, tenantId);

    return this.figuraRepository.find({
      where: { cartaPorteId, tenantId },
      order: { tipoFigura: 'ASC' },
    });
  }

  /**
   * Obtiene una figura por ID
   */
  async findById(
    tenantId: string,
    id: string
  ): Promise<FiguraTransporte | null> {
    return this.figuraRepository.findOne({
      where: { id, tenantId },
    });
  }

  /**
   * Actualiza una figura existente
   */
  async update(
    tenantId: string,
    id: string,
    data: UpdateFiguraDto
  ): Promise<FiguraTransporte | null> {
    const figura = await this.findById(tenantId, id);
    if (!figura) return null;

    const cartaPorte = await this.getCartaPorteOrFail(
      figura.cartaPorteId,
      tenantId
    );
    this.assertEditable(cartaPorte);

    // Validar datos si se actualiza tipo de figura
    if (data.tipoFigura) {
      this.validateFiguraData({
        tipoFigura: data.tipoFigura,
        rfcFigura: data.rfcFigura ?? figura.rfcFigura ?? undefined,
        nombreFigura: data.nombreFigura ?? figura.nombreFigura ?? undefined,
        numLicencia: data.numLicencia ?? figura.numLicencia ?? undefined,
      } as CreateFiguraDto);
    }

    Object.assign(figura, data);
    return this.figuraRepository.save(figura);
  }

  /**
   * Elimina una figura de transporte
   */
  async delete(tenantId: string, id: string): Promise<boolean> {
    const figura = await this.findById(tenantId, id);
    if (!figura) return false;

    const cartaPorte = await this.getCartaPorteOrFail(
      figura.cartaPorteId,
      tenantId
    );
    this.assertEditable(cartaPorte);

    const result = await this.figuraRepository.delete(id);
    return (result.affected ?? 0) > 0;
  }

  /**
   * Valida que las figuras requeridas esten presentes
   */
  async validateFigurasRequeridas(
    tenantId: string,
    cartaPorteId: string
  ): Promise<ValidacionFigurasRequeridas> {
    await this.getCartaPorteOrFail(cartaPorteId, tenantId);
    const figuras = await this.findByCartaParte(tenantId, cartaPorteId);

    const errors: string[] = [];
    const warnings: string[] = [];

    const operadores = figuras.filter(f => f.tipoFigura === TipoFigura.OPERADOR);
    const propietarios = figuras.filter(f => f.tipoFigura === TipoFigura.PROPIETARIO);
    const arrendadores = figuras.filter(f => f.tipoFigura === TipoFigura.ARRENDADOR);

    const tieneOperador = operadores.length > 0;
    const tienePropietario = propietarios.length > 0;

    // Validacion SAT: Se requiere al menos un operador
    if (!tieneOperador) {
      errors.push('Se requiere al menos un operador (figura tipo 01)');
    }

    // Validar datos de operadores
    for (const op of operadores) {
      if (!op.rfcFigura) {
        errors.push(`Operador "${op.nombreFigura || 'sin nombre'}" requiere RFC`);
      }
      if (!op.numLicencia) {
        errors.push(`Operador "${op.nombreFigura || 'sin nombre'}" requiere numero de licencia`);
      }
      if (!op.nombreFigura) {
        errors.push(`Operador con RFC ${op.rfcFigura || 'sin RFC'} requiere nombre`);
      }
    }

    // Validar datos de propietarios
    for (const prop of propietarios) {
      if (!prop.rfcFigura) {
        errors.push(`Propietario "${prop.nombreFigura || 'sin nombre'}" requiere RFC`);
      }
      if (!prop.nombreFigura) {
        errors.push(`Propietario con RFC ${prop.rfcFigura || 'sin RFC'} requiere nombre`);
      }
    }

    // Validar datos de arrendadores
    for (const arr of arrendadores) {
      if (!arr.rfcFigura) {
        errors.push(`Arrendador "${arr.nombreFigura || 'sin nombre'}" requiere RFC`);
      }
      if (!arr.nombreFigura) {
        errors.push(`Arrendador con RFC ${arr.rfcFigura || 'sin RFC'} requiere nombre`);
      }
    }

    // Warnings opcionales
    if (operadores.length > 2) {
      warnings.push('Se registraron mas de 2 operadores');
    }

    if (!tienePropietario && arrendadores.length === 0) {
      warnings.push('No se registro propietario ni arrendador de la mercancia');
    }

    return {
      valid: errors.length === 0,
      tieneOperador,
      tienePropietario,
      operadores,
      totalFiguras: figuras.length,
      errors,
      warnings,
    };
  }

  /**
   * Obtiene solo los operadores de una Carta Porte
   */
  async getOperadores(
    tenantId: string,
    cartaPorteId: string
  ): Promise<FiguraTransporte[]> {
    await this.getCartaPorteOrFail(cartaPorteId, tenantId);

    return this.figuraRepository.find({
      where: {
        cartaPorteId,
        tenantId,
        tipoFigura: TipoFigura.OPERADOR,
      },
    });
  }

  /**
   * Obtiene propietarios de mercancia
   */
  async getPropietarios(
    tenantId: string,
    cartaPorteId: string
  ): Promise<FiguraTransporte[]> {
    await this.getCartaPorteOrFail(cartaPorteId, tenantId);

    return this.figuraRepository.find({
      where: {
        cartaPorteId,
        tenantId,
        tipoFigura: TipoFigura.PROPIETARIO,
      },
    });
  }

  /**
   * Obtiene arrendadores
   */
  async getArrendadores(
    tenantId: string,
    cartaPorteId: string
  ): Promise<FiguraTransporte[]> {
    await this.getCartaPorteOrFail(cartaPorteId, tenantId);

    return this.figuraRepository.find({
      where: {
        cartaPorteId,
        tenantId,
        tipoFigura: TipoFigura.ARRENDADOR,
      },
    });
  }

  /**
   * Agrega un operador con datos minimos requeridos
   */
  async addOperador(
    tenantId: string,
    cartaPorteId: string,
    data: {
      rfcFigura: string;
      nombreFigura: string;
      numLicencia: string;
      pais?: string;
      estado?: string;
      codigoPostal?: string;
    }
  ): Promise<FiguraTransporte> {
    return this.create(tenantId, cartaPorteId, {
      tipoFigura: TipoFigura.OPERADOR,
      ...data,
    });
  }

  /**
   * Agrega un propietario de mercancia
   */
  async addPropietario(
    tenantId: string,
    cartaPorteId: string,
    data: {
      rfcFigura: string;
      nombreFigura: string;
      pais?: string;
      estado?: string;
      codigoPostal?: string;
      calle?: string;
      partesTransporte?: ParteTransporte[];
    }
  ): Promise<FiguraTransporte> {
    return this.create(tenantId, cartaPorteId, {
      tipoFigura: TipoFigura.PROPIETARIO,
      ...data,
    });
  }

  /**
   * Agrega un arrendador
   */
  async addArrendador(
    tenantId: string,
    cartaPorteId: string,
    data: {
      rfcFigura: string;
      nombreFigura: string;
      pais?: string;
      estado?: string;
      codigoPostal?: string;
      calle?: string;
      partesTransporte?: ParteTransporte[];
    }
  ): Promise<FiguraTransporte> {
    return this.create(tenantId, cartaPorteId, {
      tipoFigura: TipoFigura.ARRENDADOR,
      ...data,
    });
  }

  /**
   * Obtiene resumen de figuras por tipo
   */
  async getResumenPorTipo(
    tenantId: string,
    cartaPorteId: string
  ): Promise<{
    operadores: number;
    propietarios: number;
    arrendadores: number;
    notificados: number;
    total: number;
  }> {
    await this.getCartaPorteOrFail(cartaPorteId, tenantId);

    const figuras = await this.findByCartaParte(tenantId, cartaPorteId);

    return {
      operadores: figuras.filter(f => f.tipoFigura === TipoFigura.OPERADOR).length,
      propietarios: figuras.filter(f => f.tipoFigura === TipoFigura.PROPIETARIO).length,
      arrendadores: figuras.filter(f => f.tipoFigura === TipoFigura.ARRENDADOR).length,
      notificados: figuras.filter(f => f.tipoFigura === TipoFigura.NOTIFICADO).length,
      total: figuras.length,
    };
  }

  /**
   * Valida datos de figura segun tipo
   */
  private validateFiguraData(data: CreateFiguraDto): void {
    const errors: string[] = [];

    if (!data.tipoFigura) {
      errors.push('tipoFigura es requerido');
    }

    const tiposValidos = Object.values(TipoFigura);
    if (data.tipoFigura && !tiposValidos.includes(data.tipoFigura as TipoFigura)) {
      errors.push(`tipoFigura debe ser uno de: ${tiposValidos.join(', ')}`);
    }

    // Validaciones especificas por tipo
    if (data.tipoFigura === TipoFigura.OPERADOR) {
      if (!data.rfcFigura) {
        errors.push('RFC es requerido para operador');
      }
      if (!data.numLicencia) {
        errors.push('Numero de licencia es requerido para operador');
      }
      if (!data.nombreFigura) {
        errors.push('Nombre es requerido para operador');
      }
    }

    if (
      data.tipoFigura === TipoFigura.PROPIETARIO ||
      data.tipoFigura === TipoFigura.ARRENDADOR
    ) {
      if (!data.rfcFigura) {
        errors.push(`RFC es requerido para ${data.tipoFigura === TipoFigura.PROPIETARIO ? 'propietario' : 'arrendador'}`);
      }
      if (!data.nombreFigura) {
        errors.push(`Nombre es requerido para ${data.tipoFigura === TipoFigura.PROPIETARIO ? 'propietario' : 'arrendador'}`);
      }
    }

    // Validar formato RFC (opcional pero si se proporciona debe ser valido)
    if (data.rfcFigura) {
      const rfcRegex = /^[A-Z&]{3,4}\d{6}[A-Z0-9]{3}$/;
      if (!rfcRegex.test(data.rfcFigura.toUpperCase())) {
        errors.push('Formato de RFC invalido');
      }
    }

    if (errors.length > 0) {
      throw new Error(`Datos de figura invalidos: ${errors.join(', ')}`);
    }
  }
}
