import { Request, Response, NextFunction, Router } from 'express';
import { TrackingService, CreateEventoDto, CreateGeocercaDto } from '../services/tracking.service';
import { TipoEventoTracking, TipoGeocerca } from '../entities';

export class TrackingController {
  public router: Router;

  constructor(private readonly trackingService: TrackingService) {
    this.router = Router();
    this.initializeRoutes();
  }

  private initializeRoutes(): void {
    // Eventos/Posiciones
    this.router.get('/eventos', this.findEventos.bind(this));
    this.router.get('/posiciones-actuales', this.getPosicionesActuales.bind(this));
    this.router.get('/unidad/:unidadId/ultima-posicion', this.getUltimaPosicion.bind(this));
    this.router.get('/unidad/:unidadId/historial', this.getHistorialPosiciones.bind(this));
    this.router.get('/viaje/:viajeId/ruta', this.getRutaViaje.bind(this));
    this.router.get('/viaje/:viajeId/eventos', this.getEventosViaje.bind(this));
    this.router.post('/eventos', this.registrarEvento.bind(this));
    this.router.post('/posicion', this.registrarPosicion.bind(this));

    // Geocercas
    this.router.get('/geocercas', this.findAllGeocercas.bind(this));
    this.router.get('/geocercas/:id', this.findGeocerca.bind(this));
    this.router.post('/geocercas', this.createGeocerca.bind(this));
    this.router.patch('/geocercas/:id', this.updateGeocerca.bind(this));
    this.router.delete('/geocercas/:id', this.deleteGeocerca.bind(this));
  }

  // ==================== Eventos/Posiciones ====================

  private async findEventos(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, viajeId, tipoEvento, fechaDesde, fechaHasta, limit, offset } = req.query;

      const result = await this.trackingService.findEventos({
        tenantId,
        unidadId: unidadId as string,
        viajeId: viajeId as string,
        tipoEvento: tipoEvento as TipoEventoTracking,
        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 registrarEvento(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 dto: CreateEventoDto = req.body;
      const evento = await this.trackingService.registrarEvento(tenantId, dto);
      res.status(201).json({ data: evento });
    } catch (error) {
      next(error);
    }
  }

  private async registrarPosicion(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, latitud, longitud, velocidad, rumbo, odometro, viajeId, operadorId } = req.body;

      if (!unidadId || latitud === undefined || longitud === undefined) {
        res.status(400).json({ error: 'unidadId, latitud y longitud son requeridos' });
        return;
      }

      const evento = await this.trackingService.registrarPosicion(tenantId, unidadId, {
        latitud,
        longitud,
        velocidad,
        rumbo,
        odometro,
        viajeId,
        operadorId,
      });

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

  private async getUltimaPosicion(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 posicion = await this.trackingService.getUltimaPosicion(unidadId, tenantId);

      if (!posicion) {
        res.status(404).json({ error: 'No hay posiciones registradas para esta unidad' });
        return;
      }

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

  private async getHistorialPosiciones(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 { fechaDesde, fechaHasta } = req.query;

      if (!fechaDesde || !fechaHasta) {
        res.status(400).json({ error: 'fechaDesde y fechaHasta son requeridos' });
        return;
      }

      const posiciones = await this.trackingService.getHistorialPosiciones(
        unidadId,
        tenantId,
        new Date(fechaDesde as string),
        new Date(fechaHasta as string)
      );

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

  private async getRutaViaje(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 { viajeId } = req.params;
      const ruta = await this.trackingService.getRutaViaje(viajeId, tenantId);
      res.json({ data: ruta });
    } catch (error) {
      next(error);
    }
  }

  private async getEventosViaje(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 { viajeId } = req.params;
      const eventos = await this.trackingService.getEventosViaje(viajeId, tenantId);
      res.json({ data: eventos });
    } catch (error) {
      next(error);
    }
  }

  private async getPosicionesActuales(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 { unidadIds } = req.query;
      const ids = unidadIds ? (unidadIds as string).split(',') : undefined;

      const posiciones = await this.trackingService.getPosicionesActuales(tenantId, ids);
      res.json({ data: posiciones });
    } catch (error) {
      next(error);
    }
  }

  // ==================== Geocercas ====================

  private async findAllGeocercas(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 { tipo, activa, clienteId, limit, offset } = req.query;

      const result = await this.trackingService.findAllGeocercas({
        tenantId,
        tipo: tipo as TipoGeocerca,
        activa: activa ? activa === 'true' : undefined,
        clienteId: clienteId as string,
        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 findGeocerca(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 geocerca = await this.trackingService.findGeocerca(id, tenantId);

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

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

  private async createGeocerca(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: CreateGeocercaDto = req.body;
      const geocerca = await this.trackingService.createGeocerca(tenantId, dto, userId);
      res.status(201).json({ data: geocerca });
    } catch (error) {
      next(error);
    }
  }

  private async updateGeocerca(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: Partial<CreateGeocercaDto> = req.body;
      const geocerca = await this.trackingService.updateGeocerca(id, tenantId, dto, userId);

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

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

  private async deleteGeocerca(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.trackingService.deleteGeocerca(id, tenantId);

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

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