import { Request, Response, NextFunction, Router } from 'express';
import {
  CartaPorteService,
  CartaPorteSearchParams,
  CreateCartaPorteDto,
  UpdateCartaPorteDto,
  TimbrarCartaPorteDto,
  CancelarCartaPorteDto,
} from '../services/carta-porte.service';
import { CartaPorte, EstadoCartaPorte, TipoCfdiCartaPorte } from '../entities';

/**
 * Response interfaces for API documentation
 */
export interface CartaPorteResponse {
  data: CartaPorte;
}

export interface CartaPorteListResponse {
  data: CartaPorte[];
  total: number;
  limit: number;
  offset: number;
}

export interface CartaPorteValidationResponse {
  data: {
    valid: boolean;
    errors: string[];
  };
}

export interface ExpedienteFiscalResponse {
  data: {
    viajeId: string;
    cartasPorte: CartaPorte[];
    total: number;
    timbradas: number;
    pendientes: number;
    canceladas: number;
  };
}

export interface PendientesTimbrarResponse {
  data: CartaPorte[];
  total: number;
}

/**
 * Controlador principal para Carta Porte
 * CFDI 3.1 Compliance - Gestion completa de Cartas Porte
 *
 * @apiTags CartaPorte
 *
 * Endpoints:
 * - POST /carta-porte - Crear nueva Carta Porte desde viaje
 * - GET /carta-porte - Listar con filtros
 * - GET /carta-porte/:id - Obtener detalle con relaciones
 * - PUT /carta-porte/:id - Actualizar (solo BORRADOR)
 * - DELETE /carta-porte/:id - Eliminar (solo BORRADOR)
 * - POST /carta-porte/:id/validar - Validar campos SAT
 * - POST /carta-porte/:id/timbrar - Timbrar con PAC
 * - POST /carta-porte/:id/cancelar - Cancelar con motivo
 * - GET /carta-porte/:id/xml - Descargar XML
 * - GET /carta-porte/:id/pdf - Descargar PDF
 * - GET /carta-porte/expediente/:viajeId - Obtener expediente fiscal por viaje
 * - GET /carta-porte/pendientes-timbrar - Listar listas para timbrar
 */
export class CartaPorteController {
  public router: Router;

  constructor(private readonly cartaPorteService: CartaPorteService) {
    this.router = Router();
    this.initializeRoutes();
  }

  private initializeRoutes(): void {
    // Special routes (must be before :id routes)
    this.router.get('/pendientes-timbrar', this.getPendientesTimbrar.bind(this));
    this.router.get('/expediente/:viajeId', this.getExpedienteFiscal.bind(this));

    // CRUD basico
    this.router.post('/', this.create.bind(this));
    this.router.get('/', this.findAll.bind(this));
    this.router.get('/:id', this.findOne.bind(this));
    this.router.put('/:id', this.update.bind(this));
    this.router.delete('/:id', this.delete.bind(this));

    // Workflow: Validacion, Timbrado, Cancelacion
    this.router.post('/:id/validar', this.validar.bind(this));
    this.router.post('/:id/timbrar', this.timbrar.bind(this));
    this.router.post('/:id/cancelar', this.cancelar.bind(this));

    // Documentos: XML y PDF
    this.router.get('/:id/xml', this.downloadXml.bind(this));
    this.router.get('/:id/pdf', this.downloadPdf.bind(this));
  }

  /**
   * POST / - Crear nueva Carta Porte
   * @apiOperation Crear Carta Porte desde viaje
   * @apiResponse 201 Carta Porte creada exitosamente
   * @apiResponse 400 Datos invalidos o tenant no especificado
   * @apiResponse 500 Error interno del servidor
   */
  private async create(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const userId = req.headers['x-user-id'] as string;
      if (!userId) {
        res.status(400).json({ error: 'User ID is required' });
        return;
      }

      const dto: CreateCartaPorteDto = req.body;

      // Validar campos requeridos
      if (!dto.viajeId) {
        res.status(400).json({ error: 'viajeId es requerido' });
        return;
      }

      if (!dto.tipoCfdi) {
        res.status(400).json({ error: 'tipoCfdi es requerido (INGRESO o TRASLADO)' });
        return;
      }

      if (!dto.emisorRfc || !dto.emisorNombre) {
        res.status(400).json({ error: 'Datos del emisor son requeridos (emisorRfc, emisorNombre)' });
        return;
      }

      if (!dto.receptorRfc || !dto.receptorNombre) {
        res.status(400).json({ error: 'Datos del receptor son requeridos (receptorRfc, receptorNombre)' });
        return;
      }

      const cartaPorte = await this.cartaPorteService.create(tenantId, dto, userId);

      res.status(201).json({ data: cartaPorte } as CartaPorteResponse);
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET / - Listar Cartas Porte con filtros
   * @apiOperation Listar Cartas Porte
   * @apiQuery estado - Filtrar por estado (BORRADOR, VALIDADA, TIMBRADA, CANCELADA)
   * @apiQuery tipoCfdi - Filtrar por tipo CFDI (INGRESO, TRASLADO)
   * @apiQuery viajeId - Filtrar por ID de viaje
   * @apiQuery fechaDesde - Fecha inicio (ISO 8601)
   * @apiQuery fechaHasta - Fecha fin (ISO 8601)
   * @apiQuery search - Busqueda en folio, serie, emisor, receptor
   * @apiQuery limit - Limite de resultados (default 50)
   * @apiQuery offset - Offset para paginacion (default 0)
   * @apiResponse 200 Lista de Cartas Porte
   * @apiResponse 400 Tenant no especificado
   */
  private async findAll(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const {
        estado,
        tipoCfdi,
        viajeId,
        fechaDesde,
        fechaHasta,
        search,
        emisorRfc,
        receptorRfc,
        limit = '50',
        offset = '0',
      } = req.query;

      // Validar estado si se proporciona
      if (estado && !Object.values(EstadoCartaPorte).includes(estado as EstadoCartaPorte)) {
        res.status(400).json({
          error: `estado debe ser uno de: ${Object.values(EstadoCartaPorte).join(', ')}`,
        });
        return;
      }

      // Validar tipoCfdi si se proporciona
      if (tipoCfdi && !Object.values(TipoCfdiCartaPorte).includes(tipoCfdi as TipoCfdiCartaPorte)) {
        res.status(400).json({
          error: `tipoCfdi debe ser uno de: ${Object.values(TipoCfdiCartaPorte).join(', ')}`,
        });
        return;
      }

      const params: CartaPorteSearchParams = {
        tenantId,
        estado: estado as EstadoCartaPorte | undefined,
        tipoCfdi: tipoCfdi as TipoCfdiCartaPorte | undefined,
        viajeId: viajeId as string | undefined,
        emisorRfc: emisorRfc as string | undefined,
        receptorRfc: receptorRfc as string | undefined,
        fechaDesde: fechaDesde ? new Date(fechaDesde as string) : undefined,
        fechaHasta: fechaHasta ? new Date(fechaHasta as string) : undefined,
        search: search as string | undefined,
        limit: parseInt(limit as string, 10),
        offset: parseInt(offset as string, 10),
      };

      const { data, total } = await this.cartaPorteService.findAll(params);

      res.json({
        data,
        total,
        limit: params.limit,
        offset: params.offset,
      } as CartaPorteListResponse);
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /:id - Obtener Carta Porte por ID con todas las relaciones
   * @apiOperation Obtener detalle de Carta Porte
   * @apiParam id - UUID de la Carta Porte
   * @apiResponse 200 Carta Porte con ubicaciones, mercancias, figuras y autotransporte
   * @apiResponse 404 Carta Porte no encontrada
   */
  private async findOne(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;
      const cartaPorte = await this.cartaPorteService.findOne(id, tenantId);

      if (!cartaPorte) {
        res.status(404).json({ error: 'Carta Porte no encontrada' });
        return;
      }

      res.json({ data: cartaPorte } as CartaPorteResponse);
    } catch (error) {
      next(error);
    }
  }

  /**
   * PUT /:id - Actualizar Carta Porte
   * @apiOperation Actualizar Carta Porte (solo estado BORRADOR)
   * @apiParam id - UUID de la Carta Porte
   * @apiResponse 200 Carta Porte actualizada
   * @apiResponse 400 Solo se puede actualizar en estado BORRADOR
   * @apiResponse 404 Carta Porte no encontrada
   */
  private async update(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const userId = req.headers['x-user-id'] as string;
      if (!userId) {
        res.status(400).json({ error: 'User ID is required' });
        return;
      }

      const { id } = req.params;
      const dto: UpdateCartaPorteDto = req.body;

      // Verificar existencia y estado
      const existing = await this.cartaPorteService.findOne(id, tenantId);
      if (!existing) {
        res.status(404).json({ error: 'Carta Porte no encontrada' });
        return;
      }

      if (existing.estado !== EstadoCartaPorte.BORRADOR) {
        res.status(400).json({
          error: `Solo se puede actualizar Carta Porte en estado BORRADOR. Estado actual: ${existing.estado}`,
        });
        return;
      }

      const cartaPorte = await this.cartaPorteService.update(id, tenantId, dto, userId);

      res.json({ data: cartaPorte } as CartaPorteResponse);
    } catch (error) {
      if (error instanceof Error && error.message.includes('No se puede modificar')) {
        res.status(400).json({ error: error.message });
        return;
      }
      next(error);
    }
  }

  /**
   * DELETE /:id - Eliminar Carta Porte
   * @apiOperation Eliminar Carta Porte (solo estado BORRADOR)
   * @apiParam id - UUID de la Carta Porte
   * @apiResponse 204 Carta Porte eliminada
   * @apiResponse 400 Solo se puede eliminar en estado BORRADOR
   * @apiResponse 404 Carta Porte no encontrada
   */
  private async delete(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;

      // Verificar existencia y estado
      const existing = await this.cartaPorteService.findOne(id, tenantId);
      if (!existing) {
        res.status(404).json({ error: 'Carta Porte no encontrada' });
        return;
      }

      if (existing.estado !== EstadoCartaPorte.BORRADOR) {
        res.status(400).json({
          error: `Solo se puede eliminar Carta Porte en estado BORRADOR. Estado actual: ${existing.estado}`,
        });
        return;
      }

      const deleted = await this.cartaPorteService.delete(id, tenantId);

      if (!deleted) {
        res.status(404).json({ error: 'Carta Porte no encontrada' });
        return;
      }

      res.status(204).send();
    } catch (error) {
      if (error instanceof Error && error.message.includes('Solo se pueden eliminar')) {
        res.status(400).json({ error: error.message });
        return;
      }
      next(error);
    }
  }

  /**
   * POST /:id/validar - Validar campos SAT de Carta Porte
   * @apiOperation Validar Carta Porte para timbrado
   * @apiParam id - UUID de la Carta Porte
   * @apiResponse 200 Resultado de validacion (valid: boolean, errors: string[])
   * @apiResponse 404 Carta Porte no encontrada
   */
  private async validar(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;
      const { marcarValidada = false } = req.body;

      // Verificar existencia
      const existing = await this.cartaPorteService.findOne(id, tenantId);
      if (!existing) {
        res.status(404).json({ error: 'Carta Porte no encontrada' });
        return;
      }

      // Validar campos requeridos
      const validation = await this.cartaPorteService.validar(id, tenantId);

      // Si se solicita marcar como validada y la validacion es exitosa
      if (marcarValidada && validation.valid) {
        const updated = await this.cartaPorteService.marcarValidada(id, tenantId);
        res.json({
          data: {
            valid: validation.valid,
            errors: validation.errors,
            cartaPorte: updated,
          },
        });
        return;
      }

      res.json({ data: validation } as CartaPorteValidationResponse);
    } catch (error) {
      if (error instanceof Error && error.message.includes('no valida')) {
        res.status(400).json({ error: error.message });
        return;
      }
      next(error);
    }
  }

  /**
   * POST /:id/timbrar - Timbrar Carta Porte con PAC
   * @apiOperation Timbrar CFDI con Complemento Carta Porte
   * @apiParam id - UUID de la Carta Porte
   * @apiBody uuidCfdi - UUID del CFDI timbrado (retornado por PAC)
   * @apiBody fechaTimbrado - Fecha de timbrado
   * @apiBody xmlCfdi - XML del CFDI completo
   * @apiBody xmlCartaPorte - XML del complemento Carta Porte
   * @apiBody pdfUrl - URL del PDF generado (opcional)
   * @apiBody qrUrl - URL del codigo QR (opcional)
   * @apiResponse 200 Carta Porte timbrada exitosamente
   * @apiResponse 400 Carta Porte debe estar en estado VALIDADA
   * @apiResponse 404 Carta Porte no encontrada
   *
   * NOTA: Esta implementacion es solo la estructura.
   * La integracion real con PAC debe implementarse en el servicio.
   */
  private async timbrar(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;

      // Verificar existencia
      const existing = await this.cartaPorteService.findOne(id, tenantId);
      if (!existing) {
        res.status(404).json({ error: 'Carta Porte no encontrada' });
        return;
      }

      // Verificar estado
      if (existing.estado !== EstadoCartaPorte.VALIDADA) {
        res.status(400).json({
          error: `La Carta Porte debe estar en estado VALIDADA para timbrar. Estado actual: ${existing.estado}`,
        });
        return;
      }

      // Datos del timbrado (vienen del PAC o de proceso de timbrado)
      const dto: TimbrarCartaPorteDto = req.body;

      // Validar campos requeridos para timbrado
      if (!dto.uuidCfdi || !dto.fechaTimbrado || !dto.xmlCfdi || !dto.xmlCartaPorte) {
        res.status(400).json({
          error: 'Datos de timbrado incompletos. Se requiere: uuidCfdi, fechaTimbrado, xmlCfdi, xmlCartaPorte',
        });
        return;
      }

      const cartaPorte = await this.cartaPorteService.timbrar(id, tenantId, dto);

      res.json({
        data: cartaPorte,
        message: 'Carta Porte timbrada exitosamente',
      });
    } catch (error) {
      if (error instanceof Error && error.message.includes('debe estar validada')) {
        res.status(400).json({ error: error.message });
        return;
      }
      next(error);
    }
  }

  /**
   * POST /:id/cancelar - Cancelar Carta Porte ante SAT
   * @apiOperation Cancelar CFDI timbrado
   * @apiParam id - UUID de la Carta Porte
   * @apiBody motivoCancelacion - Motivo de cancelacion (requerido)
   * @apiBody uuidSustitucion - UUID del CFDI sustituto (opcional, para tipo 01)
   * @apiResponse 200 Carta Porte cancelada exitosamente
   * @apiResponse 400 Solo se pueden cancelar Cartas Porte timbradas
   * @apiResponse 404 Carta Porte no encontrada
   */
  private async cancelar(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;
      const dto: CancelarCartaPorteDto = req.body;

      // Verificar existencia
      const existing = await this.cartaPorteService.findOne(id, tenantId);
      if (!existing) {
        res.status(404).json({ error: 'Carta Porte no encontrada' });
        return;
      }

      // Verificar estado
      if (existing.estado !== EstadoCartaPorte.TIMBRADA) {
        res.status(400).json({
          error: `Solo se pueden cancelar Cartas Porte timbradas. Estado actual: ${existing.estado}`,
        });
        return;
      }

      // Validar motivo de cancelacion
      if (!dto.motivoCancelacion) {
        res.status(400).json({ error: 'motivoCancelacion es requerido' });
        return;
      }

      const cartaPorte = await this.cartaPorteService.cancelar(id, tenantId, dto);

      res.json({
        data: cartaPorte,
        message: 'Carta Porte cancelada exitosamente',
      });
    } catch (error) {
      if (error instanceof Error && error.message.includes('Solo se pueden cancelar')) {
        res.status(400).json({ error: error.message });
        return;
      }
      next(error);
    }
  }

  /**
   * GET /:id/xml - Descargar XML del CFDI
   * @apiOperation Obtener XML del CFDI timbrado
   * @apiParam id - UUID de la Carta Porte
   * @apiResponse 200 XML del CFDI (application/xml)
   * @apiResponse 400 Carta Porte no timbrada
   * @apiResponse 404 Carta Porte no encontrada
   */
  private async downloadXml(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;
      const cartaPorte = await this.cartaPorteService.findOne(id, tenantId);

      if (!cartaPorte) {
        res.status(404).json({ error: 'Carta Porte no encontrada' });
        return;
      }

      if (!cartaPorte.xmlCfdi) {
        res.status(400).json({
          error: 'La Carta Porte no tiene XML. Debe estar timbrada para descargar XML.',
        });
        return;
      }

      // Construir nombre del archivo
      const filename = cartaPorte.uuidCfdi
        ? `carta-porte-${cartaPorte.uuidCfdi}.xml`
        : `carta-porte-${cartaPorte.id}.xml`;

      res.setHeader('Content-Type', 'application/xml');
      res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
      res.send(cartaPorte.xmlCfdi);
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /:id/pdf - Descargar PDF de la Carta Porte
   * @apiOperation Obtener PDF del CFDI timbrado
   * @apiParam id - UUID de la Carta Porte
   * @apiResponse 200 Redireccion a URL del PDF o PDF generado
   * @apiResponse 400 Carta Porte no timbrada o PDF no disponible
   * @apiResponse 404 Carta Porte no encontrada
   *
   * NOTA: Esta implementacion es un placeholder.
   * La generacion real del PDF debe implementarse con una libreria PDF.
   */
  private async downloadPdf(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;
      const cartaPorte = await this.cartaPorteService.findOne(id, tenantId);

      if (!cartaPorte) {
        res.status(404).json({ error: 'Carta Porte no encontrada' });
        return;
      }

      if (cartaPorte.estado !== EstadoCartaPorte.TIMBRADA) {
        res.status(400).json({
          error: 'Solo se puede descargar PDF de Cartas Porte timbradas.',
        });
        return;
      }

      // Si existe URL del PDF, redirigir
      if (cartaPorte.pdfUrl) {
        res.redirect(cartaPorte.pdfUrl);
        return;
      }

      // Placeholder: En implementacion real, generar PDF aqui
      // Por ahora retornamos mensaje indicando que PDF no esta disponible
      res.status(501).json({
        error: 'Generacion de PDF no implementada',
        message: 'La Carta Porte esta timbrada pero la generacion de PDF no esta disponible. ' +
                 'Utilice el XML para generar una representacion impresa.',
        cartaPorteId: cartaPorte.id,
        uuidCfdi: cartaPorte.uuidCfdi,
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /expediente/:viajeId - Obtener expediente fiscal del viaje
   * @apiOperation Obtener todas las Cartas Porte de un viaje
   * @apiParam viajeId - UUID del viaje
   * @apiResponse 200 Expediente fiscal con estadisticas
   * @apiResponse 404 No se encontraron Cartas Porte para el viaje
   */
  private async getExpedienteFiscal(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { viajeId } = req.params;
      const cartasPorte = await this.cartaPorteService.findByViaje(viajeId, tenantId);

      // Calcular estadisticas
      const timbradas = cartasPorte.filter((cp) => cp.estado === EstadoCartaPorte.TIMBRADA).length;
      const pendientes = cartasPorte.filter(
        (cp) => cp.estado === EstadoCartaPorte.BORRADOR || cp.estado === EstadoCartaPorte.VALIDADA
      ).length;
      const canceladas = cartasPorte.filter((cp) => cp.estado === EstadoCartaPorte.CANCELADA).length;

      res.json({
        data: {
          viajeId,
          cartasPorte,
          total: cartasPorte.length,
          timbradas,
          pendientes,
          canceladas,
        },
      } as ExpedienteFiscalResponse);
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /pendientes-timbrar - Listar Cartas Porte listas para timbrar
   * @apiOperation Obtener Cartas Porte en estado BORRADOR o VALIDADA
   * @apiResponse 200 Lista de Cartas Porte pendientes de timbrar
   */
  private async getPendientesTimbrar(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const cartasPorte = await this.cartaPorteService.getPendientesTimbrar(tenantId);

      res.json({
        data: cartasPorte,
        total: cartasPorte.length,
      } as PendientesTimbrarResponse);
    } catch (error) {
      next(error);
    }
  }
}
