import { Request, Response, NextFunction, Router } from 'express';
import {
  FacturaTransporteService,
  FacturaSearchParams,
  CreateFacturaDto,
  UpdateFacturaDto,
  CreateLineaFacturaDto,
  RecargoAplicarDto,
  DescuentoAplicarDto,
} from '../services/factura-transporte.service';
import { EstadoFactura } from '../entities';

/**
 * Service Context
 */
export interface ServiceContext {
  tenantId: string;
  userId: string;
}

/**
 * FacturaTransporteController
 *
 * Handles transport invoice operations including:
 * - CRUD operations for invoices
 * - CFDI timbrado (SAT stamping)
 * - Payment registration
 * - PDF/XML download
 * - Pending collection tracking
 *
 * IVA Rate: 16% (Mexico)
 */
export class FacturaTransporteController {
  public router: Router;

  constructor(private readonly facturaService: FacturaTransporteService) {
    this.router = Router();
    this.initializeRoutes();
  }

  private initializeRoutes(): void {
    // List and search
    this.router.get('/', this.findAll.bind(this));
    this.router.get('/pendientes-cobro', this.getPendientesCobro.bind(this));

    // Find by specific identifiers
    this.router.get('/folio/:folio', this.findByFolio.bind(this));
    this.router.get('/cliente/:clienteId', this.findByCliente.bind(this));
    this.router.get('/viaje/:viajeId', this.findByViaje.bind(this));

    // Single invoice operations
    this.router.get('/:id', this.findOne.bind(this));
    this.router.get('/:id/pdf', this.downloadPdf.bind(this));
    this.router.get('/:id/xml', this.downloadXml.bind(this));

    // Create and update
    this.router.post('/', this.create.bind(this));
    this.router.patch('/:id', this.update.bind(this));

    // Line items
    this.router.post('/:id/lineas', this.addLinea.bind(this));
    this.router.delete('/:id/lineas/:lineaId', this.removeLinea.bind(this));

    // Apply surcharges and discounts
    this.router.post('/:id/recargos', this.applyRecargos.bind(this));
    this.router.post('/:id/descuentos', this.applyDescuentos.bind(this));

    // Status operations
    this.router.post('/:id/timbrar', this.timbrar.bind(this));
    this.router.post('/:id/cancelar', this.cancelar.bind(this));
    this.router.post('/:id/enviar', this.enviar.bind(this));
    this.router.post('/:id/registrar-pago', this.registrarPago.bind(this));

    // Recalculate totals
    this.router.post('/:id/recalcular', this.recalcularTotales.bind(this));

    // Account statement
    this.router.get('/cliente/:clienteId/estado-cuenta', this.getEstadoCuenta.bind(this));
  }

  /**
   * Extract service context from request headers
   */
  private getContext(req: Request): ServiceContext {
    return {
      tenantId: req.headers['x-tenant-id'] as string,
      userId: req.headers['x-user-id'] as string,
    };
  }

  // ============================================
  // LIST AND SEARCH
  // ============================================

  /**
   * GET /
   * Find all invoices with filters
   */
  private async findAll(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);

      const params: FacturaSearchParams = {
        tenantId: ctx.tenantId,
        search: req.query.search as string,
        clienteId: req.query.clienteId as string,
        estado: req.query.estado as EstadoFactura,
        moneda: req.query.moneda as string,
        limit: parseInt(req.query.limit as string) || 50,
        offset: parseInt(req.query.offset as string) || 0,
      };

      // Parse date filters
      if (req.query.fechaDesde) {
        params.fechaDesde = new Date(req.query.fechaDesde as string);
      }
      if (req.query.fechaHasta) {
        params.fechaHasta = new Date(req.query.fechaHasta as string);
      }

      const result = await this.facturaService.findAll(params);
      res.json(result);
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /pendientes-cobro
   * Get invoices pending collection
   */
  private async getPendientesCobro(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const limit = parseInt(req.query.limit as string) || 100;
      const offset = parseInt(req.query.offset as string) || 0;

      const result = await this.facturaService.getFacturasPendientes(ctx.tenantId, limit, offset);
      res.json(result);
    } catch (error) {
      next(error);
    }
  }

  // ============================================
  // FIND BY IDENTIFIERS
  // ============================================

  /**
   * GET /:id
   * Find invoice by ID
   */
  private async findOne(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;

      const factura = await this.facturaService.findById(ctx.tenantId, id);
      if (!factura) {
        res.status(404).json({ error: 'Factura no encontrada' });
        return;
      }

      res.json({ data: factura });
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /folio/:folio
   * Find invoice by folio
   */
  private async findByFolio(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { folio } = req.params;
      const serie = req.query.serie as string;

      // Search by folio (and optionally serie)
      const params: FacturaSearchParams = {
        tenantId: ctx.tenantId,
        search: folio,
        limit: 10,
        offset: 0,
      };

      const result = await this.facturaService.findAll(params);

      // Filter for exact folio match
      const factura = result.data.find(f => {
        if (serie) {
          return f.folio === folio && f.serie === serie;
        }
        return f.folio === folio || f.folioCompleto === folio;
      });

      if (!factura) {
        res.status(404).json({ error: 'Factura no encontrada' });
        return;
      }

      res.json({ data: factura });
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /cliente/:clienteId
   * Get invoices by client
   */
  private async findByCliente(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { clienteId } = req.params;
      const limit = parseInt(req.query.limit as string) || 50;
      const offset = parseInt(req.query.offset as string) || 0;

      const result = await this.facturaService.findByCliente(ctx.tenantId, clienteId, limit, offset);
      res.json(result);
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /viaje/:viajeId
   * Get invoice for a trip
   */
  private async findByViaje(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { viajeId } = req.params;

      // Search for invoices containing this trip
      const params: FacturaSearchParams = {
        tenantId: ctx.tenantId,
        limit: 100,
        offset: 0,
      };

      const result = await this.facturaService.findAll(params);

      // Filter for invoices containing this viajeId
      const facturas = result.data.filter(f =>
        f.viajeIds && f.viajeIds.includes(viajeId)
      );

      res.json({
        data: facturas,
        total: facturas.length,
        viajeId,
      });
    } catch (error) {
      next(error);
    }
  }

  // ============================================
  // CREATE AND UPDATE
  // ============================================

  /**
   * POST /
   * Create a new invoice
   */
  private async create(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const dto: CreateFacturaDto = {
        ...req.body,
        fechaEmision: new Date(req.body.fechaEmision),
        fechaVencimiento: req.body.fechaVencimiento
          ? new Date(req.body.fechaVencimiento)
          : undefined,
      };

      const factura = await this.facturaService.create(ctx.tenantId, dto, ctx.userId);
      res.status(201).json({ data: factura });
    } catch (error) {
      next(error);
    }
  }

  /**
   * PATCH /:id
   * Update invoice (only if in BORRADOR state)
   */
  private async update(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;
      const dto: UpdateFacturaDto = {
        ...req.body,
        fechaVencimiento: req.body.fechaVencimiento
          ? new Date(req.body.fechaVencimiento)
          : undefined,
      };

      const factura = await this.facturaService.update(ctx.tenantId, id, dto);
      if (!factura) {
        res.status(404).json({ error: 'Factura no encontrada' });
        return;
      }

      res.json({ data: factura });
    } catch (error) {
      next(error);
    }
  }

  // ============================================
  // LINE ITEMS
  // ============================================

  /**
   * POST /:id/lineas
   * Add line item to invoice
   */
  private async addLinea(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;
      const dto: CreateLineaFacturaDto = req.body;

      const linea = await this.facturaService.addLinea(ctx.tenantId, id, dto);
      res.status(201).json({ data: linea });
    } catch (error) {
      next(error);
    }
  }

  /**
   * DELETE /:id/lineas/:lineaId
   * Remove line item from invoice
   */
  private async removeLinea(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id, lineaId } = req.params;

      const deleted = await this.facturaService.removeLinea(ctx.tenantId, id, lineaId);
      if (!deleted) {
        res.status(404).json({ error: 'Linea no encontrada' });
        return;
      }

      res.status(204).send();
    } catch (error) {
      next(error);
    }
  }

  // ============================================
  // SURCHARGES AND DISCOUNTS
  // ============================================

  /**
   * POST /:id/recargos
   * Apply surcharges to invoice (IVA 16%)
   */
  private async applyRecargos(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;
      const recargos: RecargoAplicarDto[] = req.body.recargos;

      const factura = await this.facturaService.applyRecargos(ctx.tenantId, id, recargos);
      res.json({ data: factura });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /:id/descuentos
   * Apply discounts to invoice
   */
  private async applyDescuentos(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;
      const descuentos: DescuentoAplicarDto[] = req.body.descuentos;

      const factura = await this.facturaService.applyDescuentos(ctx.tenantId, id, descuentos);
      res.json({ data: factura });
    } catch (error) {
      next(error);
    }
  }

  // ============================================
  // STATUS OPERATIONS
  // ============================================

  /**
   * POST /:id/timbrar
   * Stamp invoice with CFDI (SAT Mexico)
   */
  private async timbrar(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;
      const { uuidCfdi, xmlCfdi, pdfUrl } = req.body;

      if (!uuidCfdi || !xmlCfdi) {
        res.status(400).json({ error: 'Se requiere uuidCfdi y xmlCfdi' });
        return;
      }

      const factura = await this.facturaService.timbrar(ctx.tenantId, id, {
        uuidCfdi,
        xmlCfdi,
        pdfUrl,
      });

      res.json({ data: factura });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /:id/cancelar
   * Cancel invoice
   */
  private async cancelar(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;
      const { motivo } = req.body;

      if (!motivo) {
        res.status(400).json({ error: 'Se requiere motivo de cancelacion' });
        return;
      }

      const factura = await this.facturaService.cancelar(ctx.tenantId, id, motivo);
      res.json({ data: factura });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /:id/enviar
   * Send invoice by email
   */
  private async enviar(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;
      const { email, mensaje } = req.body;

      const factura = await this.facturaService.findById(ctx.tenantId, id);
      if (!factura) {
        res.status(404).json({ error: 'Factura no encontrada' });
        return;
      }

      if (factura.estado === EstadoFactura.BORRADOR) {
        res.status(400).json({ error: 'No se puede enviar una factura en estado BORRADOR' });
        return;
      }

      // TODO: Implement email sending service integration
      // For now, just return success with the email details
      res.json({
        data: {
          facturaId: id,
          folioCompleto: factura.folioCompleto,
          email: email || factura.clienteRfc,
          mensaje: mensaje || 'Factura enviada correctamente',
          enviado: true,
          fechaEnvio: new Date(),
        },
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /:id/registrar-pago
   * Register payment for invoice
   */
  private async registrarPago(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;
      const { montoPago, fechaPago, referencia, formaPago } = req.body;

      if (!montoPago || montoPago <= 0) {
        res.status(400).json({ error: 'El monto de pago debe ser mayor a cero' });
        return;
      }

      const factura = await this.facturaService.registrarPago(
        ctx.tenantId,
        id,
        montoPago,
        fechaPago ? new Date(fechaPago) : new Date()
      );

      res.json({
        data: {
          factura,
          pago: {
            monto: montoPago,
            fecha: fechaPago || new Date(),
            referencia,
            formaPago,
          },
        },
      });
    } catch (error) {
      next(error);
    }
  }

  // ============================================
  // DOCUMENT DOWNLOAD
  // ============================================

  /**
   * GET /:id/pdf
   * Download invoice PDF
   */
  private async downloadPdf(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;

      const factura = await this.facturaService.findById(ctx.tenantId, id);
      if (!factura) {
        res.status(404).json({ error: 'Factura no encontrada' });
        return;
      }

      if (!factura.pdfUrl) {
        res.status(404).json({ error: 'PDF no disponible para esta factura' });
        return;
      }

      // Return PDF URL or redirect
      res.json({
        data: {
          facturaId: id,
          folioCompleto: factura.folioCompleto,
          pdfUrl: factura.pdfUrl,
        },
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /:id/xml
   * Download invoice XML (CFDI)
   */
  private async downloadXml(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;

      const factura = await this.facturaService.findById(ctx.tenantId, id);
      if (!factura) {
        res.status(404).json({ error: 'Factura no encontrada' });
        return;
      }

      if (!factura.xmlCfdi) {
        res.status(404).json({ error: 'XML CFDI no disponible para esta factura' });
        return;
      }

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

  // ============================================
  // ACCOUNT STATEMENTS
  // ============================================

  /**
   * GET /cliente/:clienteId/estado-cuenta
   * Get client account statement
   */
  private async getEstadoCuenta(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { clienteId } = req.params;

      const estadoCuenta = await this.facturaService.getEstadoCuenta(ctx.tenantId, clienteId);
      res.json({ data: estadoCuenta });
    } catch (error) {
      next(error);
    }
  }

  // ============================================
  // UTILITY OPERATIONS
  // ============================================

  /**
   * POST /:id/recalcular
   * Recalculate invoice totals (subtotal, IVA 16%, total)
   */
  private async recalcularTotales(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;

      const factura = await this.facturaService.calculateTotals(ctx.tenantId, id);
      if (!factura) {
        res.status(404).json({ error: 'Factura no encontrada' });
        return;
      }

      res.json({ data: factura });
    } catch (error) {
      next(error);
    }
  }
}
