import { Repository, FindOptionsWhere, ILike, In, Between } from 'typeorm';
import {
  CartaPorte,
  EstadoCartaPorte,
  TipoCfdiCartaPorte,
  UbicacionCartaPorte,
  MercanciaCartaPorte,
  FiguraTransporte,
  AutotransporteCartaPorte,
} from '../entities';

export interface CartaPorteSearchParams {
  tenantId: string;
  search?: string;
  estado?: EstadoCartaPorte;
  estados?: EstadoCartaPorte[];
  tipoCfdi?: TipoCfdiCartaPorte;
  viajeId?: string;
  emisorRfc?: string;
  receptorRfc?: string;
  fechaDesde?: Date;
  fechaHasta?: Date;
  limit?: number;
  offset?: number;
}

export interface CreateCartaPorteDto {
  viajeId: string;
  tipoCfdi: TipoCfdiCartaPorte;
  versionCartaPorte?: string;
  serie?: string;
  // Emisor
  emisorRfc: string;
  emisorNombre: string;
  emisorRegimenFiscal?: string;
  // Receptor
  receptorRfc: string;
  receptorNombre: string;
  receptorUsoCfdi?: string;
  receptorDomicilioFiscalCp?: string;
  // Totales
  subtotal?: number;
  total?: number;
  moneda?: string;
  // Transporte internacional
  transporteInternacional?: boolean;
  entradaSalidaMerc?: string;
  paisOrigenDestino?: string;
  // Datos autotransporte
  permisoSct?: string;
  numPermisoSct?: string;
  configVehicular?: string;
  pesoBrutoTotal?: number;
  unidadPeso?: string;
  numTotalMercancias?: number;
  // Seguros
  aseguraRespCivil?: string;
  polizaRespCivil?: string;
  aseguraMedAmbiente?: string;
  polizaMedAmbiente?: string;
  aseguraCarga?: string;
  polizaCarga?: string;
  primaSeguro?: number;
}

export interface UpdateCartaPorteDto extends Partial<CreateCartaPorteDto> {
  estado?: EstadoCartaPorte;
}

export interface TimbrarCartaPorteDto {
  uuidCfdi: string;
  fechaTimbrado: Date;
  xmlCfdi: string;
  xmlCartaPorte: string;
  pdfUrl?: string;
  qrUrl?: string;
}

export interface CancelarCartaPorteDto {
  motivoCancelacion: string;
  uuidSustitucion?: string;
}

export class CartaPorteService {
  constructor(
    private readonly cartaPorteRepository: Repository<CartaPorte>,
    private readonly ubicacionRepository: Repository<UbicacionCartaPorte>,
    private readonly mercanciaRepository: Repository<MercanciaCartaPorte>,
    private readonly figuraRepository: Repository<FiguraTransporte>,
    private readonly autotransporteRepository: Repository<AutotransporteCartaPorte>,
  ) {}

  async findAll(params: CartaPorteSearchParams): Promise<{ data: CartaPorte[]; total: number }> {
    const {
      tenantId,
      search,
      estado,
      estados,
      tipoCfdi,
      viajeId,
      emisorRfc,
      receptorRfc,
      fechaDesde,
      fechaHasta,
      limit = 50,
      offset = 0,
    } = params;

    const where: FindOptionsWhere<CartaPorte>[] = [];
    const baseWhere: FindOptionsWhere<CartaPorte> = { tenantId };

    if (estado) {
      baseWhere.estado = estado;
    }

    if (estados && estados.length > 0) {
      baseWhere.estado = In(estados);
    }

    if (tipoCfdi) {
      baseWhere.tipoCfdi = tipoCfdi;
    }

    if (viajeId) {
      baseWhere.viajeId = viajeId;
    }

    if (emisorRfc) {
      baseWhere.emisorRfc = emisorRfc;
    }

    if (receptorRfc) {
      baseWhere.receptorRfc = receptorRfc;
    }

    if (fechaDesde && fechaHasta) {
      baseWhere.createdAt = Between(fechaDesde, fechaHasta);
    }

    if (search) {
      where.push(
        { ...baseWhere, folio: ILike(`%${search}%`) },
        { ...baseWhere, serie: ILike(`%${search}%`) },
        { ...baseWhere, emisorNombre: ILike(`%${search}%`) },
        { ...baseWhere, receptorNombre: ILike(`%${search}%`) },
        { ...baseWhere, receptorRfc: ILike(`%${search}%`) }
      );
    } else {
      where.push(baseWhere);
    }

    const [data, total] = await this.cartaPorteRepository.findAndCount({
      where,
      relations: ['ubicaciones', 'mercancias', 'figuras', 'autotransporte'],
      take: limit,
      skip: offset,
      order: { createdAt: 'DESC' },
    });

    return { data, total };
  }

  async findOne(id: string, tenantId: string): Promise<CartaPorte | null> {
    return this.cartaPorteRepository.findOne({
      where: { id, tenantId },
      relations: ['ubicaciones', 'mercancias', 'figuras', 'autotransporte'],
    });
  }

  async findByViaje(viajeId: string, tenantId: string): Promise<CartaPorte[]> {
    return this.cartaPorteRepository.find({
      where: { viajeId, tenantId },
      relations: ['ubicaciones', 'mercancias', 'figuras', 'autotransporte'],
      order: { createdAt: 'DESC' },
    });
  }

  async findByUuid(uuidCfdi: string, tenantId: string): Promise<CartaPorte | null> {
    return this.cartaPorteRepository.findOne({
      where: { uuidCfdi, tenantId },
      relations: ['ubicaciones', 'mercancias', 'figuras', 'autotransporte'],
    });
  }

  async create(tenantId: string, dto: CreateCartaPorteDto, createdBy: string): Promise<CartaPorte> {
    const cartaPorte = this.cartaPorteRepository.create({
      ...dto,
      tenantId,
      estado: EstadoCartaPorte.BORRADOR,
      createdById: createdBy,
    });

    return this.cartaPorteRepository.save(cartaPorte);
  }

  async update(
    id: string,
    tenantId: string,
    dto: UpdateCartaPorteDto,
    updatedBy: string
  ): Promise<CartaPorte | null> {
    const cartaPorte = await this.findOne(id, tenantId);
    if (!cartaPorte) return null;

    // Cannot update if already timbrada or cancelada
    if (cartaPorte.estado === EstadoCartaPorte.TIMBRADA) {
      throw new Error('No se puede modificar una Carta Porte ya timbrada');
    }

    if (cartaPorte.estado === EstadoCartaPorte.CANCELADA) {
      throw new Error('No se puede modificar una Carta Porte cancelada');
    }

    Object.assign(cartaPorte, dto);
    return this.cartaPorteRepository.save(cartaPorte);
  }

  async validar(id: string, tenantId: string): Promise<{ valid: boolean; errors: string[] }> {
    const cartaPorte = await this.findOne(id, tenantId);
    if (!cartaPorte) {
      return { valid: false, errors: ['Carta Porte no encontrada'] };
    }

    const errors: string[] = [];

    // Validaciones requeridas para Carta Porte 3.1
    if (!cartaPorte.emisorRfc) errors.push('RFC del emisor es requerido');
    if (!cartaPorte.emisorNombre) errors.push('Nombre del emisor es requerido');
    if (!cartaPorte.receptorRfc) errors.push('RFC del receptor es requerido');
    if (!cartaPorte.receptorNombre) errors.push('Nombre del receptor es requerido');

    // Validar ubicaciones (minimo origen y destino)
    if (!cartaPorte.ubicaciones || cartaPorte.ubicaciones.length < 2) {
      errors.push('Se requieren al menos 2 ubicaciones (origen y destino)');
    }

    // Validar mercancias
    if (!cartaPorte.mercancias || cartaPorte.mercancias.length === 0) {
      errors.push('Se requiere al menos una mercancia');
    }

    // Validar figuras de transporte
    if (!cartaPorte.figuras || cartaPorte.figuras.length === 0) {
      errors.push('Se requiere al menos una figura de transporte (operador)');
    }

    // Validar autotransporte
    if (!cartaPorte.autotransporte || cartaPorte.autotransporte.length === 0) {
      errors.push('Se requiere informacion de autotransporte');
    }

    // Validar datos de transporte federal
    if (!cartaPorte.permisoSct) errors.push('Tipo de permiso SCT es requerido');
    if (!cartaPorte.numPermisoSct) errors.push('Numero de permiso SCT es requerido');
    if (!cartaPorte.configVehicular) errors.push('Configuracion vehicular es requerida');

    // Si es CFDI de ingreso, validar totales
    if (cartaPorte.tipoCfdi === TipoCfdiCartaPorte.INGRESO) {
      if (cartaPorte.total === null || cartaPorte.total === undefined) {
        errors.push('Total del CFDI es requerido para tipo Ingreso');
      }
    }

    return { valid: errors.length === 0, errors };
  }

  async marcarValidada(id: string, tenantId: string): Promise<CartaPorte | null> {
    const cartaPorte = await this.findOne(id, tenantId);
    if (!cartaPorte) return null;

    const validation = await this.validar(id, tenantId);
    if (!validation.valid) {
      throw new Error(`Carta Porte no valida: ${validation.errors.join(', ')}`);
    }

    cartaPorte.estado = EstadoCartaPorte.VALIDADA;
    return this.cartaPorteRepository.save(cartaPorte);
  }

  async timbrar(
    id: string,
    tenantId: string,
    dto: TimbrarCartaPorteDto
  ): Promise<CartaPorte | null> {
    const cartaPorte = await this.findOne(id, tenantId);
    if (!cartaPorte) return null;

    if (cartaPorte.estado !== EstadoCartaPorte.VALIDADA) {
      throw new Error('La Carta Porte debe estar validada antes de timbrar');
    }

    Object.assign(cartaPorte, {
      ...dto,
      estado: EstadoCartaPorte.TIMBRADA,
    });

    return this.cartaPorteRepository.save(cartaPorte);
  }

  async cancelar(
    id: string,
    tenantId: string,
    dto: CancelarCartaPorteDto
  ): Promise<CartaPorte | null> {
    const cartaPorte = await this.findOne(id, tenantId);
    if (!cartaPorte) return null;

    if (cartaPorte.estado !== EstadoCartaPorte.TIMBRADA) {
      throw new Error('Solo se pueden cancelar Cartas Porte timbradas');
    }

    Object.assign(cartaPorte, {
      estado: EstadoCartaPorte.CANCELADA,
      fechaCancelacion: new Date(),
      motivoCancelacion: dto.motivoCancelacion,
      uuidSustitucion: dto.uuidSustitucion,
    });

    return this.cartaPorteRepository.save(cartaPorte);
  }

  // Ubicaciones
  async addUbicacion(
    cartaPorteId: string,
    tenantId: string,
    ubicacionData: Partial<UbicacionCartaPorte>
  ): Promise<UbicacionCartaPorte> {
    const cartaPorte = await this.findOne(cartaPorteId, tenantId);
    if (!cartaPorte) {
      throw new Error('Carta Porte no encontrada');
    }

    if (cartaPorte.estado !== EstadoCartaPorte.BORRADOR) {
      throw new Error('Solo se pueden agregar ubicaciones en estado BORRADOR');
    }

    const ubicacion = this.ubicacionRepository.create({
      ...ubicacionData,
      cartaPorteId,
    });

    return this.ubicacionRepository.save(ubicacion);
  }

  // Mercancias
  async addMercancia(
    cartaPorteId: string,
    tenantId: string,
    mercanciaData: Partial<MercanciaCartaPorte>
  ): Promise<MercanciaCartaPorte> {
    const cartaPorte = await this.findOne(cartaPorteId, tenantId);
    if (!cartaPorte) {
      throw new Error('Carta Porte no encontrada');
    }

    if (cartaPorte.estado !== EstadoCartaPorte.BORRADOR) {
      throw new Error('Solo se pueden agregar mercancias en estado BORRADOR');
    }

    const mercancia = this.mercanciaRepository.create({
      ...mercanciaData,
      cartaPorteId,
    });

    return this.mercanciaRepository.save(mercancia);
  }

  // Figuras de transporte
  async addFigura(
    cartaPorteId: string,
    tenantId: string,
    figuraData: Partial<FiguraTransporte>
  ): Promise<FiguraTransporte> {
    const cartaPorte = await this.findOne(cartaPorteId, tenantId);
    if (!cartaPorte) {
      throw new Error('Carta Porte no encontrada');
    }

    if (cartaPorte.estado !== EstadoCartaPorte.BORRADOR) {
      throw new Error('Solo se pueden agregar figuras en estado BORRADOR');
    }

    const figura = this.figuraRepository.create({
      ...figuraData,
      cartaPorteId,
    });

    return this.figuraRepository.save(figura);
  }

  // Autotransporte
  async addAutotransporte(
    cartaPorteId: string,
    tenantId: string,
    autoData: Partial<AutotransporteCartaPorte>
  ): Promise<AutotransporteCartaPorte> {
    const cartaPorte = await this.findOne(cartaPorteId, tenantId);
    if (!cartaPorte) {
      throw new Error('Carta Porte no encontrada');
    }

    if (cartaPorte.estado !== EstadoCartaPorte.BORRADOR) {
      throw new Error('Solo se puede agregar autotransporte en estado BORRADOR');
    }

    const auto = this.autotransporteRepository.create({
      ...autoData,
      cartaPorteId,
    });

    return this.autotransporteRepository.save(auto);
  }

  // Cartas porte pendientes de timbrar
  async getPendientesTimbrar(tenantId: string): Promise<CartaPorte[]> {
    return this.cartaPorteRepository.find({
      where: {
        tenantId,
        estado: In([EstadoCartaPorte.BORRADOR, EstadoCartaPorte.VALIDADA]),
      },
      order: { createdAt: 'ASC' },
    });
  }

  // Cartas porte por viaje (para reportes)
  async getCartasPorteViaje(viajeId: string, tenantId: string): Promise<CartaPorte[]> {
    return this.cartaPorteRepository.find({
      where: { viajeId, tenantId },
      relations: ['ubicaciones', 'mercancias', 'figuras', 'autotransporte'],
      order: { createdAt: 'DESC' },
    });
  }

  async delete(id: string, tenantId: string): Promise<boolean> {
    const cartaPorte = await this.findOne(id, tenantId);
    if (!cartaPorte) return false;

    // Only allow deletion of BORRADOR
    if (cartaPorte.estado !== EstadoCartaPorte.BORRADOR) {
      throw new Error('Solo se pueden eliminar Cartas Porte en estado BORRADOR');
    }

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