import { Request, Response, NextFunction, Router } from 'express';
import {
  UbicacionCartaPorteService,
  CreateUbicacionDto,
  UpdateUbicacionDto,
} from '../services/ubicacion-carta-porte.service';

/**
 * Controlador para gestion de ubicaciones de Carta Porte
 * CFDI 3.1 Compliance - Manejo de origenes, destinos y puntos intermedios
 */
export class UbicacionCartaPorteController {
  public router: Router;

  constructor(private readonly ubicacionService: UbicacionCartaPorteService) {
    this.router = Router();
    this.initializeRoutes();
  }

  private initializeRoutes(): void {
    // CRUD basico
    this.router.get('/', this.findAll.bind(this));
    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.delete('/:id', this.delete.bind(this));

    // Operaciones por carta porte
    this.router.get('/carta-porte/:cartaPorteId', this.findByCartaParte.bind(this));
    this.router.post('/carta-porte/:cartaPorteId/reorder', this.reorder.bind(this));

    // Validaciones y consultas especificas
    this.router.get('/carta-porte/:cartaPorteId/validar', this.validarSecuencia.bind(this));
    this.router.get('/carta-porte/:cartaPorteId/origen', this.getOrigen.bind(this));
    this.router.get('/carta-porte/:cartaPorteId/destino', this.getDestino.bind(this));
    this.router.get('/carta-porte/:cartaPorteId/distancia-total', this.getDistanciaTotal.bind(this));
  }

  /**
   * GET / - Obtiene todas las ubicaciones (requiere cartaPorteId en query)
   */
  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 { cartaPorteId } = req.query;

      if (!cartaPorteId) {
        res.status(400).json({ error: 'cartaPorteId es requerido en query params' });
        return;
      }

      const ubicaciones = await this.ubicacionService.findByCartaParte(
        tenantId,
        cartaPorteId as string
      );

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

  /**
   * GET /:id - Obtiene una ubicacion por 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 ubicacion = await this.ubicacionService.findById(tenantId, id);

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

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

  /**
   * POST / - Crea una nueva ubicacion
   */
  private async create(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 { cartaPorteId, ...ubicacionData } = req.body;

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

      const dto: CreateUbicacionDto = ubicacionData;
      const ubicacion = await this.ubicacionService.create(tenantId, cartaPorteId, dto);

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

  /**
   * PATCH /:id - Actualiza una ubicacion existente
   */
  private async update(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 dto: UpdateUbicacionDto = req.body;
      const ubicacion = await this.ubicacionService.update(tenantId, id, dto);

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

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

  /**
   * DELETE /:id - Elimina una ubicacion
   */
  private async delete(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 deleted = await this.ubicacionService.delete(tenantId, id);

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

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

  /**
   * GET /carta-porte/:cartaPorteId - Obtiene ubicaciones de una Carta Porte
   */
  private async findByCartaParte(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 { cartaPorteId } = req.params;
      const ubicaciones = await this.ubicacionService.findByCartaParte(tenantId, cartaPorteId);

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

  /**
   * POST /carta-porte/:cartaPorteId/reorder - Reordena las ubicaciones
   */
  private async reorder(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 { cartaPorteId } = req.params;
      const { orden } = req.body;

      if (!Array.isArray(orden) || orden.length === 0) {
        res.status(400).json({ error: 'orden es requerido y debe ser un array de IDs' });
        return;
      }

      const ubicaciones = await this.ubicacionService.reorder(tenantId, cartaPorteId, orden);

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

  /**
   * GET /carta-porte/:cartaPorteId/validar - Valida secuencia de ubicaciones
   */
  private async validarSecuencia(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 { cartaPorteId } = req.params;
      const validacion = await this.ubicacionService.validateSecuencia(tenantId, cartaPorteId);

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

  /**
   * GET /carta-porte/:cartaPorteId/origen - Obtiene la ubicacion de origen
   */
  private async getOrigen(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 { cartaPorteId } = req.params;
      const origen = await this.ubicacionService.getOrigen(tenantId, cartaPorteId);

      if (!origen) {
        res.status(404).json({ error: 'Origen no encontrado' });
        return;
      }

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

  /**
   * GET /carta-porte/:cartaPorteId/destino - Obtiene la ubicacion de destino final
   */
  private async getDestino(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 { cartaPorteId } = req.params;
      const destino = await this.ubicacionService.getDestinoFinal(tenantId, cartaPorteId);

      if (!destino) {
        res.status(404).json({ error: 'Destino no encontrado' });
        return;
      }

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

  /**
   * GET /carta-porte/:cartaPorteId/distancia-total - Obtiene distancia total del recorrido
   */
  private async getDistanciaTotal(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 { cartaPorteId } = req.params;
      const distancia = await this.ubicacionService.getDistanciaTotal(tenantId, cartaPorteId);

      res.json({
        data: {
          distanciaTotalKm: distancia,
        },
      });
    } catch (error) {
      next(error);
    }
  }
}
