import { Request, Response, NextFunction, Router } from 'express';
import {
  RecargosService,
  RecargoSearchParams,
  CreateRecargoDto,
  UpdateRecargoDto,
  CreateFuelSurchargeDto,
  UpdateFuelSurchargeDto,
  ServiceContext,
} from '../services/recargos.service';
import { TipoRecargo } from '../entities';

/**
 * RecargosController
 *
 * Handles surcharge catalog and fuel surcharge operations including:
 * - CRUD operations for RecargoCatalogo
 * - CRUD operations for FuelSurcharge
 * - Surcharge calculations
 * - Fuel surcharge calculations
 * - Detention calculations
 */
export class RecargosController {
  public router: Router;

  constructor(private readonly recargosService: RecargosService) {
    this.router = Router();
    this.initializeRoutes();
  }

  private initializeRoutes(): void {
    // ============================================
    // RECARGOS CATALOGO ROUTES
    // ============================================

    // List and search
    this.router.get('/', this.findAll.bind(this));
    this.router.get('/vigentes', this.getRecargosVigentes.bind(this));
    this.router.get('/automaticos', this.getRecargosAutomaticos.bind(this));

    // Find by specific identifiers
    this.router.get('/codigo/:codigo', this.findByCodigo.bind(this));
    this.router.get('/tipo/:tipo', this.findByTipo.bind(this));

    // ============================================
    // FUEL SURCHARGE ROUTES
    // ============================================

    this.router.get('/fuel-surcharge', this.findAllFuelSurcharges.bind(this));
    this.router.get('/fuel-surcharge/vigente', this.getFuelSurchargeVigente.bind(this));
    this.router.get('/fuel-surcharge/:id', this.findFuelSurcharge.bind(this));
    this.router.post('/fuel-surcharge', this.createFuelSurcharge.bind(this));
    this.router.patch('/fuel-surcharge/:id', this.updateFuelSurcharge.bind(this));
    this.router.delete('/fuel-surcharge/:id', this.deleteFuelSurcharge.bind(this));

    // ============================================
    // CALCULATION ROUTES
    // ============================================

    this.router.post('/calcular', this.calcularRecargos.bind(this));
    this.router.post('/calcular-fuel', this.calculateFuelSurcharge.bind(this));
    this.router.post('/calcular-detention', this.calculateDetention.bind(this));
    this.router.post('/calcular-todos', this.calculateAllSurcharges.bind(this));

    // ============================================
    // SINGLE RECARGO OPERATIONS (must be after other routes)
    // ============================================

    this.router.get('/:id', this.findOne.bind(this));
    this.router.post('/', this.create.bind(this));
    this.router.patch('/:id', this.update.bind(this));
    this.router.post('/:id/activar', this.activar.bind(this));
    this.router.post('/:id/desactivar', this.desactivar.bind(this));
    this.router.delete('/:id', this.delete.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,
    };
  }

  // ============================================
  // RECARGOS CATALOGO - LIST AND SEARCH
  // ============================================

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

      const params: RecargoSearchParams = {
        tenantId: ctx.tenantId,
        search: req.query.search as string,
        tipo: req.query.tipo as TipoRecargo,
        activo: req.query.activo === 'true' ? true : req.query.activo === 'false' ? false : undefined,
        aplicaAutomatico:
          req.query.aplicaAutomatico === 'true'
            ? true
            : req.query.aplicaAutomatico === 'false'
            ? false
            : undefined,
        limit: parseInt(req.query.limit as string) || 50,
        offset: parseInt(req.query.offset as string) || 0,
      };

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

  /**
   * GET /vigentes
   * Get all active surcharges
   */
  private async getRecargosVigentes(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const recargos = await this.recargosService.getRecargosVigentes(ctx);
      res.json({ data: recargos, total: recargos.length });
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /automaticos
   * Get surcharges that apply automatically
   */
  private async getRecargosAutomaticos(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const recargos = await this.recargosService.getRecargosAutomaticos(ctx);
      res.json({ data: recargos, total: recargos.length });
    } catch (error) {
      next(error);
    }
  }

  // ============================================
  // RECARGOS CATALOGO - FIND BY IDENTIFIERS
  // ============================================

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

      const recargo = await this.recargosService.findOne(id, ctx.tenantId);
      if (!recargo) {
        res.status(404).json({ error: 'Recargo no encontrado' });
        return;
      }

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

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

      const recargo = await this.recargosService.findByCodigo(codigo, ctx.tenantId);
      if (!recargo) {
        res.status(404).json({ error: 'Recargo no encontrado' });
        return;
      }

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

  /**
   * GET /tipo/:tipo
   * Find surcharges by type
   */
  private async findByTipo(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const tipo = req.params.tipo as TipoRecargo;

      // Validate tipo
      if (!Object.values(TipoRecargo).includes(tipo)) {
        res.status(400).json({
          error: 'Tipo de recargo invalido',
          tiposValidos: Object.values(TipoRecargo),
        });
        return;
      }

      const recargos = await this.recargosService.findByTipo(tipo, ctx.tenantId);
      res.json({ data: recargos, total: recargos.length, tipo });
    } catch (error) {
      next(error);
    }
  }

  // ============================================
  // RECARGOS CATALOGO - CREATE AND UPDATE
  // ============================================

  /**
   * POST /
   * Create a new surcharge
   */
  private async create(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const dto: CreateRecargoDto = req.body;

      const recargo = await this.recargosService.create(dto, ctx);
      res.status(201).json({ data: recargo });
    } catch (error) {
      next(error);
    }
  }

  /**
   * PATCH /:id
   * Update surcharge
   */
  private async update(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;
      const dto: UpdateRecargoDto = req.body;

      const recargo = await this.recargosService.update(id, dto, ctx);
      if (!recargo) {
        res.status(404).json({ error: 'Recargo no encontrado' });
        return;
      }

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

  /**
   * POST /:id/activar
   * Activate surcharge
   */
  private async activar(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;

      const recargo = await this.recargosService.activar(id, ctx);
      if (!recargo) {
        res.status(404).json({ error: 'Recargo no encontrado' });
        return;
      }

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

  /**
   * POST /:id/desactivar
   * Deactivate surcharge
   */
  private async desactivar(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;

      const recargo = await this.recargosService.desactivar(id, ctx);
      if (!recargo) {
        res.status(404).json({ error: 'Recargo no encontrado' });
        return;
      }

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

  /**
   * DELETE /:id
   * Delete surcharge
   */
  private async delete(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;

      const deleted = await this.recargosService.delete(id, ctx);
      if (!deleted) {
        res.status(404).json({ error: 'Recargo no encontrado' });
        return;
      }

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

  // ============================================
  // FUEL SURCHARGE OPERATIONS
  // ============================================

  /**
   * GET /fuel-surcharge
   * Find all fuel surcharges
   */
  private async findAllFuelSurcharges(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const limit = parseInt(req.query.limit as string) || 50;
      const offset = parseInt(req.query.offset as string) || 0;

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

  /**
   * GET /fuel-surcharge/vigente
   * Get current active fuel surcharge
   */
  private async getFuelSurchargeVigente(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const fuelSurcharge = await this.recargosService.getFuelSurchargeVigente(ctx.tenantId);

      if (!fuelSurcharge) {
        res.json({ data: null, message: 'No hay fuel surcharge vigente configurado' });
        return;
      }

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

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

      const fuelSurcharge = await this.recargosService.findFuelSurcharge(id, ctx.tenantId);
      if (!fuelSurcharge) {
        res.status(404).json({ error: 'Fuel surcharge no encontrado' });
        return;
      }

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

  /**
   * POST /fuel-surcharge
   * Create a new fuel surcharge period
   */
  private async createFuelSurcharge(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const dto: CreateFuelSurchargeDto = {
        ...req.body,
        fechaInicio: new Date(req.body.fechaInicio),
        fechaFin: new Date(req.body.fechaFin),
      };

      const fuelSurcharge = await this.recargosService.createFuelSurcharge(dto, ctx);
      res.status(201).json({ data: fuelSurcharge });
    } catch (error) {
      next(error);
    }
  }

  /**
   * PATCH /fuel-surcharge/:id
   * Update fuel surcharge
   */
  private async updateFuelSurcharge(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;
      const dto: UpdateFuelSurchargeDto = {
        ...req.body,
        fechaInicio: req.body.fechaInicio ? new Date(req.body.fechaInicio) : undefined,
        fechaFin: req.body.fechaFin ? new Date(req.body.fechaFin) : undefined,
      };

      const fuelSurcharge = await this.recargosService.updateFuelSurcharge(id, dto, ctx);
      if (!fuelSurcharge) {
        res.status(404).json({ error: 'Fuel surcharge no encontrado' });
        return;
      }

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

  /**
   * DELETE /fuel-surcharge/:id
   * Delete fuel surcharge
   */
  private async deleteFuelSurcharge(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;

      const deleted = await this.recargosService.deleteFuelSurcharge(id, ctx);
      if (!deleted) {
        res.status(404).json({ error: 'Fuel surcharge no encontrado' });
        return;
      }

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

  // ============================================
  // CALCULATION OPERATIONS
  // ============================================

  /**
   * POST /calcular
   * Calculate surcharges for a base amount
   */
  private async calcularRecargos(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { montoBase, valorDeclarado, incluirFuelSurcharge } = req.body;

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

      const resultado = await this.recargosService.calcularRecargos(montoBase, ctx, {
        valorDeclarado,
        incluirFuelSurcharge: incluirFuelSurcharge !== false,
      });

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

  /**
   * POST /calcular-fuel
   * Calculate fuel surcharge based on distance and fuel price
   */
  private async calculateFuelSurcharge(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { distanciaKm, precioLitro, rendimientoKmPorLitro } = req.body;

      if (!distanciaKm || distanciaKm <= 0) {
        res.status(400).json({ error: 'La distancia debe ser mayor a cero' });
        return;
      }

      if (!precioLitro || precioLitro <= 0) {
        res.status(400).json({ error: 'El precio por litro debe ser mayor a cero' });
        return;
      }

      const resultado = await this.recargosService.calculateFuelSurcharge(
        ctx.tenantId,
        distanciaKm,
        precioLitro,
        rendimientoKmPorLitro
      );

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

  /**
   * POST /calcular-detention
   * Calculate detention charge based on waiting hours
   */
  private async calculateDetention(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { horasEspera, tarifaBase } = req.body;

      if (horasEspera === undefined || horasEspera < 0) {
        res.status(400).json({ error: 'Las horas de espera deben ser un numero positivo' });
        return;
      }

      const resultado = await this.recargosService.calculateDetention(
        ctx.tenantId,
        horasEspera,
        tarifaBase
      );

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

  /**
   * POST /calcular-todos
   * Calculate all applicable surcharges for a shipment
   */
  private async calculateAllSurcharges(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { tarifaBase, distanciaKm, precioLitro, horasEspera, tiposAdicionales } = req.body;

      if (!tarifaBase || tarifaBase <= 0) {
        res.status(400).json({ error: 'La tarifa base debe ser mayor a cero' });
        return;
      }

      const resultado = await this.recargosService.calculateAllSurcharges(ctx.tenantId, {
        tarifaBase,
        distanciaKm,
        precioLitro,
        horasEspera,
        tiposAdicionales,
      });

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