import { Request, Response, NextFunction, Router } from 'express';
import {
  TarifasService,
  TarifaSearchParams,
  CreateTarifaDto,
  UpdateTarifaDto,
  CotizarParams,
  FindTarifaByLaneParams,
  CalcularCostoEnvioParams,
  ServiceContext,
} from '../services/tarifas.service';
import { LanesService, LaneSearchParams, CreateLaneDto, UpdateLaneDto } from '../services/lanes.service';
import { RecargosService, CreateRecargoDto, UpdateRecargoDto, CreateFuelSurchargeDto, UpdateFuelSurchargeDto } from '../services/recargos.service';
import { TipoRecargo } from '../entities';

export class TarifasController {
  public router: Router;

  constructor(
    private readonly tarifasService: TarifasService,
    private readonly lanesService: LanesService,
    private readonly recargosService: RecargosService
  ) {
    this.router = Router();
    this.initializeRoutes();
  }

  private initializeRoutes(): void {
    // Tarifas
    this.router.get('/tarifas', this.findAllTarifas.bind(this));
    this.router.get('/tarifas/vigentes', this.findTarifasVigentes.bind(this));
    this.router.get('/tarifas/por-vencer', this.getTarifasPorVencer.bind(this));
    this.router.get('/tarifas/:id', this.findOneTarifa.bind(this));
    this.router.post('/tarifas', this.createTarifa.bind(this));
    this.router.put('/tarifas/:id', this.updateTarifa.bind(this));
    this.router.patch('/tarifas/:id/activar', this.activarTarifa.bind(this));
    this.router.patch('/tarifas/:id/desactivar', this.desactivarTarifa.bind(this));
    this.router.post('/tarifas/:id/clonar', this.clonarTarifa.bind(this));
    this.router.delete('/tarifas/:id', this.deleteTarifa.bind(this));

    // Find tarifa by lane
    this.router.post('/tarifas/find-by-lane', this.findTarifaByLane.bind(this));

    // Cotizacion
    this.router.post('/tarifas/:id/cotizar', this.cotizarTarifa.bind(this));
    this.router.post('/cotizar-envio', this.calcularCostoEnvio.bind(this));

    // Lanes
    this.router.get('/lanes', this.findAllLanes.bind(this));
    this.router.get('/lanes/ciudades-origen', this.getOrigenCiudades.bind(this));
    this.router.get('/lanes/ciudades-destino', this.getDestinoCiudades.bind(this));
    this.router.get('/lanes/estados-origen', this.getOrigenEstados.bind(this));
    this.router.get('/lanes/estados-destino', this.getDestinoEstados.bind(this));
    this.router.get('/lanes/:id', this.findOneLane.bind(this));
    this.router.get('/lanes/:id/tarifas', this.getTarifasLane.bind(this));
    this.router.get('/lanes/:id/inversa', this.getLaneInversa.bind(this));
    this.router.post('/lanes', this.createLane.bind(this));
    this.router.post('/lanes/:id/crear-inversa', this.crearLaneInversa.bind(this));
    this.router.put('/lanes/:id', this.updateLane.bind(this));
    this.router.patch('/lanes/:id/activar', this.activarLane.bind(this));
    this.router.patch('/lanes/:id/desactivar', this.desactivarLane.bind(this));
    this.router.delete('/lanes/:id', this.deleteLane.bind(this));

    // Recargos
    this.router.get('/recargos', this.findAllRecargos.bind(this));
    this.router.get('/recargos/vigentes', this.getRecargosVigentes.bind(this));
    this.router.get('/recargos/automaticos', this.getRecargosAutomaticos.bind(this));
    this.router.get('/recargos/:id', this.findOneRecargo.bind(this));
    this.router.post('/recargos', this.createRecargo.bind(this));
    this.router.put('/recargos/:id', this.updateRecargo.bind(this));
    this.router.patch('/recargos/:id/activar', this.activarRecargo.bind(this));
    this.router.patch('/recargos/:id/desactivar', this.desactivarRecargo.bind(this));
    this.router.delete('/recargos/:id', this.deleteRecargo.bind(this));
    this.router.post('/recargos/calcular', this.calcularRecargos.bind(this));

    // Fuel Surcharge
    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.findOneFuelSurcharge.bind(this));
    this.router.post('/fuel-surcharge', this.createFuelSurcharge.bind(this));
    this.router.put('/fuel-surcharge/:id', this.updateFuelSurcharge.bind(this));
    this.router.patch('/fuel-surcharge/:id/desactivar', this.desactivarFuelSurcharge.bind(this));
    this.router.delete('/fuel-surcharge/:id', this.deleteFuelSurcharge.bind(this));
  }

  private getContext(req: Request): ServiceContext {
    return {
      tenantId: req.headers['x-tenant-id'] as string,
      userId: req.headers['x-user-id'] as string,
    };
  }

  // ============================================
  // TARIFAS
  // ============================================

  private async findAllTarifas(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const params: TarifaSearchParams = {
        tenantId: ctx.tenantId,
        search: req.query.search as string,
        clienteId: req.query.clienteId as string,
        laneId: req.query.laneId as string,
        tipoTarifa: req.query.tipoTarifa as any,
        activa: req.query.activa === 'true' ? true : req.query.activa === 'false' ? false : undefined,
        vigente: req.query.vigente === 'true',
        moneda: req.query.moneda as string,
        limit: parseInt(req.query.limit as string) || 50,
        offset: parseInt(req.query.offset as string) || 0,
      };

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

  private async findTarifasVigentes(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const params: TarifaSearchParams = {
        tenantId: ctx.tenantId,
        vigente: true,
        limit: parseInt(req.query.limit as string) || 100,
        offset: parseInt(req.query.offset as string) || 0,
      };

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

  private async getTarifasPorVencer(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const diasAntelacion = parseInt(req.query.dias as string) || 30;

      const tarifas = await this.tarifasService.getTarifasPorVencer(ctx.tenantId, diasAntelacion);
      res.json({ data: tarifas, total: tarifas.length });
    } catch (error) {
      next(error);
    }
  }

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

      const tarifa = await this.tarifasService.findOne(id, ctx.tenantId);
      if (!tarifa) {
        res.status(404).json({ error: 'Tarifa no encontrada' });
        return;
      }

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

  private async createTarifa(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const dto: CreateTarifaDto = req.body;

      const tarifa = await this.tarifasService.createWithValidation(dto, ctx);
      res.status(201).json({ data: tarifa });
    } catch (error) {
      next(error);
    }
  }

  private async updateTarifa(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;
      const dto: UpdateTarifaDto = req.body;

      const tarifa = await this.tarifasService.updateWithHistory(id, dto, ctx);
      if (!tarifa) {
        res.status(404).json({ error: 'Tarifa no encontrada' });
        return;
      }

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

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

      const tarifa = await this.tarifasService.activar(id, ctx.tenantId);
      if (!tarifa) {
        res.status(404).json({ error: 'Tarifa no encontrada' });
        return;
      }

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

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

      const tarifa = await this.tarifasService.desactivar(id, ctx.tenantId);
      if (!tarifa) {
        res.status(404).json({ error: 'Tarifa no encontrada' });
        return;
      }

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

  private async clonarTarifa(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;
      const { nuevoCodigo, nuevaFechaInicio, nuevaFechaFin } = req.body;

      const tarifa = await this.tarifasService.clonarTarifa(
        id,
        ctx.tenantId,
        nuevoCodigo,
        new Date(nuevaFechaInicio),
        nuevaFechaFin ? new Date(nuevaFechaFin) : undefined,
        ctx.userId
      );

      if (!tarifa) {
        res.status(404).json({ error: 'Tarifa origen no encontrada' });
        return;
      }

      res.status(201).json({ data: tarifa });
    } catch (error) {
      next(error);
    }
  }

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

      const deleted = await this.tarifasService.delete(id, ctx.tenantId);
      if (!deleted) {
        res.status(404).json({ error: 'Tarifa no encontrada' });
        return;
      }

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

  private async findTarifaByLane(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const params: FindTarifaByLaneParams = req.body;

      const tarifa = await this.tarifasService.findTarifaByLane(params, ctx);
      res.json({ data: tarifa });
    } catch (error) {
      next(error);
    }
  }

  private async cotizarTarifa(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;
      const params: CotizarParams = req.body;

      const cotizacion = await this.tarifasService.cotizar(id, ctx.tenantId, params);
      res.json({ data: cotizacion });
    } catch (error) {
      next(error);
    }
  }

  private async calcularCostoEnvio(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const params: CalcularCostoEnvioParams = req.body;

      const resultado = await this.tarifasService.calcularCostoEnvio(params, ctx);
      res.json({ data: resultado });
    } catch (error) {
      next(error);
    }
  }

  // ============================================
  // LANES
  // ============================================

  private async findAllLanes(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const params: LaneSearchParams = {
        tenantId: ctx.tenantId,
        search: req.query.search as string,
        origenCiudad: req.query.origenCiudad as string,
        origenEstado: req.query.origenEstado as string,
        destinoCiudad: req.query.destinoCiudad as string,
        destinoEstado: req.query.destinoEstado as string,
        activo: req.query.activo === 'true' ? true : req.query.activo === 'false' ? false : undefined,
        limit: parseInt(req.query.limit as string) || 50,
        offset: parseInt(req.query.offset as string) || 0,
      };

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

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

      const lane = await this.lanesService.findOne(id, ctx.tenantId);
      if (!lane) {
        res.status(404).json({ error: 'Lane no encontrada' });
        return;
      }

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

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

      const tarifas = await this.tarifasService.getTarifasLane(id, ctx.tenantId);
      res.json({ data: tarifas, total: tarifas.length });
    } catch (error) {
      next(error);
    }
  }

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

      const lane = await this.lanesService.getLaneInversa(id, ctx.tenantId);
      res.json({ data: lane });
    } catch (error) {
      next(error);
    }
  }

  private async getOrigenCiudades(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const ciudades = await this.lanesService.getOrigenCiudades(ctx.tenantId);
      res.json({ data: ciudades });
    } catch (error) {
      next(error);
    }
  }

  private async getDestinoCiudades(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const ciudades = await this.lanesService.getDestinoCiudades(ctx.tenantId);
      res.json({ data: ciudades });
    } catch (error) {
      next(error);
    }
  }

  private async getOrigenEstados(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const estados = await this.lanesService.getOrigenEstados(ctx.tenantId);
      res.json({ data: estados });
    } catch (error) {
      next(error);
    }
  }

  private async getDestinoEstados(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const estados = await this.lanesService.getDestinoEstados(ctx.tenantId);
      res.json({ data: estados });
    } catch (error) {
      next(error);
    }
  }

  private async createLane(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const dto: CreateLaneDto = req.body;

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

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

      const lane = await this.lanesService.crearLaneInversa(id, ctx.tenantId, ctx.userId);
      if (!lane) {
        res.status(404).json({ error: 'Lane origen no encontrada' });
        return;
      }

      res.status(201).json({ data: lane });
    } catch (error) {
      next(error);
    }
  }

  private async updateLane(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { id } = req.params;
      const dto: UpdateLaneDto = req.body;

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

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

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

      const lane = await this.lanesService.activar(id, ctx.tenantId);
      if (!lane) {
        res.status(404).json({ error: 'Lane no encontrada' });
        return;
      }

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

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

      const lane = await this.lanesService.desactivar(id, ctx.tenantId);
      if (!lane) {
        res.status(404).json({ error: 'Lane no encontrada' });
        return;
      }

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

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

      const deleted = await this.lanesService.delete(id, ctx.tenantId);
      if (!deleted) {
        res.status(404).json({ error: 'Lane no encontrada' });
        return;
      }

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

  // ============================================
  // RECARGOS
  // ============================================

  private async findAllRecargos(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const result = await this.recargosService.findAll({
        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,
      });

      res.json(result);
    } catch (error) {
      next(error);
    }
  }

  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);
    }
  }

  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);
    }
  }

  private async findOneRecargo(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);
    }
  }

  private async createRecargo(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);
    }
  }

  private async updateRecargo(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);
    }
  }

  private async activarRecargo(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);
    }
  }

  private async desactivarRecargo(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);
    }
  }

  private async deleteRecargo(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);
    }
  }

  private async calcularRecargos(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const { montoBase, valorDeclarado, incluirFuelSurcharge } = req.body;

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

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

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

  private async findAllFuelSurcharges(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const ctx = this.getContext(req);
      const result = await this.recargosService.findAllFuelSurcharges(
        ctx.tenantId,
        parseInt(req.query.limit as string) || 50,
        parseInt(req.query.offset as string) || 0
      );

      res.json(result);
    } catch (error) {
      next(error);
    }
  }

  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);
      res.json({ data: fuelSurcharge });
    } catch (error) {
      next(error);
    }
  }

  private async findOneFuelSurcharge(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);
    }
  }

  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);
    }
  }

  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);
    }
  }

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

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

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

  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);
    }
  }
}
