import { Request, Response, NextFunction, Router } from 'express';
import {
  OrdenesTransporteService,
  CreateOrdenTransporteDto,
  UpdateOrdenTransporteDto,
  AsignarUnidadDto,
} from '../services/ordenes-transporte.service';
import { EstadoOrdenTransporte, ModalidadServicio } from '../entities';

export class OrdenesTransporteController {
  public router: Router;

  constructor(private readonly otService: OrdenesTransporteService) {
    this.router = Router();
    this.initializeRoutes();
  }

  private initializeRoutes(): void {
    // CRUD básico
    this.router.get('/', this.findAll.bind(this));
    this.router.get('/pendientes', this.getOtsPendientes.bind(this));
    this.router.get('/programacion', this.getOtsParaProgramacion.bind(this));
    this.router.get('/cliente/:clienteId', this.getOtsCliente.bind(this));
    this.router.get('/:id', this.findOne.bind(this));
    this.router.get('/numero/:numeroOt', this.findByNumeroOt.bind(this));
    this.router.post('/', this.create.bind(this));
    this.router.patch('/:id', this.update.bind(this));

    // Operaciones de estado
    this.router.post('/:id/estado', this.cambiarEstado.bind(this));
    this.router.post('/:id/confirmar', this.confirmar.bind(this));
    this.router.post('/:id/asignar', this.asignar.bind(this));
    this.router.post('/:id/cancelar', this.cancelar.bind(this));

    // MAI-003: Nuevas operaciones de asignacion y tarifa
    this.router.post('/:id/asignar-unidad', this.asignarUnidad.bind(this));
    this.router.get('/:id/calcular-tarifa', this.calcularTarifa.bind(this));
    this.router.get('/:id/estadisticas', this.getEstadisticas.bind(this));
  }

  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 {
        search,
        estado,
        estados,
        clienteId,
        modalidad,
        fechaDesde,
        fechaHasta,
        limit,
        offset,
      } = req.query;

      const result = await this.otService.findAll({
        tenantId,
        search: search as string,
        estado: estado as EstadoOrdenTransporte,
        estados: estados ? (estados as string).split(',') as EstadoOrdenTransporte[] : undefined,
        clienteId: clienteId as string,
        modalidad: modalidad as ModalidadServicio,
        fechaDesde: fechaDesde ? new Date(fechaDesde as string) : undefined,
        fechaHasta: fechaHasta ? new Date(fechaHasta as string) : undefined,
        limit: limit ? parseInt(limit as string, 10) : undefined,
        offset: offset ? parseInt(offset as string, 10) : undefined,
      });

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

  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 ot = await this.otService.findOne(id, tenantId);

      if (!ot) {
        res.status(404).json({ error: 'Orden de transporte no encontrada' });
        return;
      }

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

  private async findByNumeroOt(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 { numeroOt } = req.params;
      const ot = await this.otService.findByNumeroOt(numeroOt, tenantId);

      if (!ot) {
        res.status(404).json({ error: 'Orden de transporte no encontrada' });
        return;
      }

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

  private async create(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;

      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const dto: CreateOrdenTransporteDto = req.body;
      const ot = await this.otService.create(tenantId, dto, userId);
      res.status(201).json({ data: ot });
    } catch (error) {
      next(error);
    }
  }

  private async update(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;

      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;
      const dto: UpdateOrdenTransporteDto = req.body;
      const ot = await this.otService.update(id, tenantId, dto, userId);

      if (!ot) {
        res.status(404).json({ error: 'Orden de transporte no encontrada' });
        return;
      }

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

  private async cambiarEstado(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;

      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;
      const { estado } = req.body;

      if (!estado) {
        res.status(400).json({ error: 'Estado es requerido' });
        return;
      }

      const ot = await this.otService.cambiarEstado(id, tenantId, estado, userId);

      if (!ot) {
        res.status(404).json({ error: 'Orden de transporte no encontrada' });
        return;
      }

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

  private async confirmar(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;

      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;
      const ot = await this.otService.confirmar(id, tenantId, userId);

      if (!ot) {
        res.status(404).json({ error: 'Orden de transporte no encontrada' });
        return;
      }

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

  private async asignar(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;

      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;
      const { viajeId } = req.body;

      if (!viajeId) {
        res.status(400).json({ error: 'viajeId es requerido' });
        return;
      }

      const ot = await this.otService.asignar(id, tenantId, viajeId, userId);

      if (!ot) {
        res.status(404).json({ error: 'Orden de transporte no encontrada' });
        return;
      }

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

  private async cancelar(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;

      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;
      const { motivo } = req.body;

      if (!motivo) {
        res.status(400).json({ error: 'Motivo de cancelación es requerido' });
        return;
      }

      const ot = await this.otService.cancelar(id, tenantId, motivo, userId);

      if (!ot) {
        res.status(404).json({ error: 'Orden de transporte no encontrada' });
        return;
      }

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

  private async getOtsPendientes(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 ots = await this.otService.getOtsPendientes(tenantId);
      res.json({ data: ots });
    } catch (error) {
      next(error);
    }
  }

  private async getOtsCliente(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 { clienteId } = req.params;
      const { limit } = req.query;

      const ots = await this.otService.getOtsCliente(
        clienteId,
        tenantId,
        limit ? parseInt(limit as string, 10) : undefined
      );
      res.json({ data: ots });
    } catch (error) {
      next(error);
    }
  }

  private async getOtsParaProgramacion(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 { fecha } = req.query;

      if (!fecha) {
        res.status(400).json({ error: 'Fecha es requerida' });
        return;
      }

      const ots = await this.otService.getOtsParaProgramacion(tenantId, new Date(fecha as string));
      res.json({ data: ots });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /:id/asignar-unidad
   * Asignar unidad y operador a una OT, creando el viaje asociado
   */
  private async asignarUnidad(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;

      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      if (!userId) {
        res.status(400).json({ error: 'User ID is required' });
        return;
      }

      const { id } = req.params;
      const { unidadId, operadorId, remolqueId, fechaSalidaProgramada, fechaLlegadaProgramada } = req.body;

      if (!unidadId) {
        res.status(400).json({ error: 'unidadId es requerido' });
        return;
      }

      if (!operadorId) {
        res.status(400).json({ error: 'operadorId es requerido' });
        return;
      }

      const dto: AsignarUnidadDto = {
        unidadId,
        operadorId,
        remolqueId,
        fechaSalidaProgramada: fechaSalidaProgramada ? new Date(fechaSalidaProgramada) : undefined,
        fechaLlegadaProgramada: fechaLlegadaProgramada ? new Date(fechaLlegadaProgramada) : undefined,
      };

      const result = await this.otService.asignarUnidad(id, dto, { tenantId, userId });

      if (!result) {
        res.status(404).json({ error: 'Orden de transporte no encontrada' });
        return;
      }

      res.json({
        data: result.orden,
        viajeId: result.viajeId,
        message: `Orden asignada exitosamente a viaje ${result.viajeId}`,
      });
    } catch (error: any) {
      if (error.message.includes('no disponible') ||
          error.message.includes('documentos vencidos') ||
          error.message.includes('No se puede asignar')) {
        res.status(422).json({ error: error.message });
        return;
      }
      next(error);
    }
  }

  /**
   * GET /:id/calcular-tarifa
   * Calcular tarifa para una OT basada en lane y recargos
   */
  private async calcularTarifa(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string || 'system';

      if (!tenantId) {
        res.status(400).json({ error: 'Tenant ID is required' });
        return;
      }

      const { id } = req.params;

      const tarifa = await this.otService.calcularTarifa(id, { tenantId, userId });

      if (!tarifa) {
        res.status(404).json({ error: 'Orden de transporte no encontrada' });
        return;
      }

      res.json({ data: tarifa });
    } catch (error: any) {
      if (error.message.includes('no encontrada')) {
        res.status(404).json({ error: error.message });
        return;
      }
      next(error);
    }
  }

  /**
   * GET /:id/estadisticas
   * Obtener estadisticas de ordenes de transporte
   */
  private async getEstadisticas(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 { fechaDesde, fechaHasta } = req.query;

      // Por defecto, ultimos 30 dias
      const to = fechaHasta ? new Date(fechaHasta as string) : new Date();
      const from = fechaDesde
        ? new Date(fechaDesde as string)
        : new Date(to.getTime() - 30 * 24 * 60 * 60 * 1000);

      const stats = await this.otService.getStatistics(tenantId, { from, to });

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