import { Request, Response, NextFunction, Router } from 'express';
import { AsignacionService, CreateAsignacionDto } from '../services/asignacion.service';

/**
 * Controller for managing unit-operator assignments (asignaciones)
 * Handles assignment of operators to fleet units
 */
export class AsignacionController {
  public router: Router;

  constructor(private readonly asignacionService: AsignacionService) {
    this.router = Router();
    this.initializeRoutes();
  }

  private initializeRoutes(): void {
    // CRUD and query routes
    this.router.get('/', this.findAll.bind(this));
    this.router.get('/activa/unidad/:unidadId', this.getAsignacionActivaUnidad.bind(this));
    this.router.get('/activa/operador/:operadorId', this.getAsignacionActivaOperador.bind(this));
    this.router.get('/historial/unidad/:unidadId', this.getHistorialUnidad.bind(this));
    this.router.get('/historial/operador/:operadorId', this.getHistorialOperador.bind(this));
    this.router.get('/disponibilidad', this.validateDisponibilidad.bind(this));
    this.router.get('/:id', this.findOne.bind(this));
    this.router.post('/', this.create.bind(this));

    // Operations
    this.router.post('/:id/finalizar', this.finalizar.bind(this));
    this.router.post('/transferir', this.transferir.bind(this));
  }

  /**
   * GET /asignaciones
   * Find all assignments with optional filters
   * Query params: unidadId, operadorId, activa
   */
  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 { unidadId, operadorId, activa } = req.query;

      let asignaciones;

      if (activa === 'true') {
        asignaciones = await this.asignacionService.findActivas(tenantId);
      } else if (unidadId) {
        asignaciones = await this.asignacionService.findByUnidad(tenantId, unidadId as string);
      } else if (operadorId) {
        asignaciones = await this.asignacionService.findByOperador(tenantId, operadorId as string);
      } else {
        // Default: return all active assignments
        asignaciones = await this.asignacionService.findActivas(tenantId);
      }

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

  /**
   * GET /asignaciones/:id
   * Find an assignment by ID
   */
  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 asignacion = await this.asignacionService.findById(tenantId, id);

      if (!asignacion) {
        res.status(404).json({ error: 'Asignación no encontrada' });
        return;
      }

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

  /**
   * GET /asignaciones/activa/unidad/:unidadId
   * Get the current active assignment for a unit
   */
  private async getAsignacionActivaUnidad(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 { unidadId } = req.params;
      const asignacion = await this.asignacionService.getAsignacionActual(tenantId, unidadId);

      if (!asignacion) {
        res.status(404).json({
          error: 'No hay asignación activa para esta unidad',
          unidadId,
        });
        return;
      }

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

  /**
   * GET /asignaciones/activa/operador/:operadorId
   * Get the current active assignment for an operator
   */
  private async getAsignacionActivaOperador(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 { operadorId } = req.params;
      const asignaciones = await this.asignacionService.findByOperador(tenantId, operadorId);
      const asignacionActiva = asignaciones.find(a => a.activa);

      if (!asignacionActiva) {
        res.status(404).json({
          error: 'No hay asignación activa para este operador',
          operadorId,
        });
        return;
      }

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

  /**
   * GET /asignaciones/historial/unidad/:unidadId
   * Get assignment history for a unit
   */
  private async getHistorialUnidad(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 { unidadId } = req.params;
      const historial = await this.asignacionService.getHistorial(tenantId, unidadId);
      res.json({ data: historial, total: historial.length });
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /asignaciones/historial/operador/:operadorId
   * Get assignment history for an operator
   */
  private async getHistorialOperador(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 { operadorId } = req.params;
      const asignaciones = await this.asignacionService.findByOperador(tenantId, operadorId);

      // Calculate duration for each assignment
      const historial = asignaciones.map(asignacion => {
        const fechaFin = asignacion.fechaFin || new Date();
        const duracionMs = fechaFin.getTime() - asignacion.fechaInicio.getTime();
        const duracionDias = Math.ceil(duracionMs / (1000 * 60 * 60 * 24));

        return {
          asignacion,
          duracionDias,
        };
      });

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

  /**
   * POST /asignaciones
   * Create a new assignment (assign operator to unit)
   * Body: { unidadId, operadorId, remolqueId?, motivo?, fechaInicio? }
   */
  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 { unidadId, operadorId, remolqueId, motivo, fechaInicio } = req.body;

      // Validate required fields
      if (!unidadId) {
        res.status(400).json({ error: 'ID de unidad es requerido' });
        return;
      }
      if (!operadorId) {
        res.status(400).json({ error: 'ID de operador es requerido' });
        return;
      }

      const dto: CreateAsignacionDto = {
        remolqueId,
        motivo,
        fechaInicio: fechaInicio ? new Date(fechaInicio) : undefined,
      };

      const asignacion = await this.asignacionService.create(
        tenantId,
        unidadId,
        operadorId,
        dto,
        userId
      );

      res.status(201).json({
        data: asignacion,
        message: 'Asignación creada exitosamente',
      });
    } catch (error) {
      if (error instanceof Error) {
        if (error.message.includes('No se puede crear la asignacion')) {
          res.status(400).json({ error: error.message });
          return;
        }
        if (error.message.includes('ya tiene')) {
          res.status(409).json({ error: error.message });
          return;
        }
        if (error.message.includes('no encontrad')) {
          res.status(404).json({ error: error.message });
          return;
        }
      }
      next(error);
    }
  }

  /**
   * POST /asignaciones/:id/finalizar
   * End an active assignment
   * Body: { motivo }
   */
  private async finalizar(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 finalización es requerido' });
        return;
      }

      const asignacion = await this.asignacionService.terminar(tenantId, id, motivo, userId);
      res.json({
        data: asignacion,
        message: 'Asignación finalizada exitosamente',
      });
    } catch (error) {
      if (error instanceof Error) {
        if (error.message.includes('no encontrada')) {
          res.status(404).json({ error: error.message });
          return;
        }
        if (error.message.includes('ya esta terminada')) {
          res.status(400).json({ error: error.message });
          return;
        }
        if (error.message.includes('en viaje')) {
          res.status(409).json({ error: error.message });
          return;
        }
      }
      next(error);
    }
  }

  /**
   * POST /asignaciones/transferir
   * Transfer a unit to a new operator (ends current assignment and creates new one)
   * Body: { unidadId, nuevoOperadorId, motivo }
   */
  private async transferir(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 { unidadId, nuevoOperadorId, motivo } = req.body;

      // Validate required fields
      if (!unidadId) {
        res.status(400).json({ error: 'ID de unidad es requerido' });
        return;
      }
      if (!nuevoOperadorId) {
        res.status(400).json({ error: 'ID del nuevo operador es requerido' });
        return;
      }
      if (!motivo) {
        res.status(400).json({ error: 'Motivo de transferencia es requerido' });
        return;
      }

      const asignacion = await this.asignacionService.transferir(
        tenantId,
        unidadId,
        nuevoOperadorId,
        motivo,
        userId
      );

      res.status(201).json({
        data: asignacion,
        message: 'Unidad transferida exitosamente al nuevo operador',
      });
    } catch (error) {
      if (error instanceof Error) {
        if (error.message.includes('no encontrad')) {
          res.status(404).json({ error: error.message });
          return;
        }
        if (error.message.includes('en viaje')) {
          res.status(409).json({ error: error.message });
          return;
        }
        if (error.message.includes('No se puede crear')) {
          res.status(400).json({ error: error.message });
          return;
        }
      }
      next(error);
    }
  }

  /**
   * GET /asignaciones/disponibilidad
   * Validate availability of unit and operator for assignment
   * Query params: unidadId, operadorId
   */
  private async validateDisponibilidad(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 { unidadId, operadorId } = req.query;

      if (!unidadId || !operadorId) {
        res.status(400).json({
          error: 'Se requieren unidadId y operadorId para validar disponibilidad',
        });
        return;
      }

      const disponibilidad = await this.asignacionService.validateDisponibilidad(
        tenantId,
        unidadId as string,
        operadorId as string
      );

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