import { Router } from 'express';
import { authenticate } from '../../middleware/auth';
import { requireRoles } from '../../middleware/rbac';
import { Role, ErrorCode, TripStatus } from '@saferoute/constants';
import { prisma } from '../../config/database';
import { sendSuccess, sendError } from '../../utils/response';
import { config } from '../../config';

export const gpsRoutes = Router();

gpsRoutes.post('/ingest', async (req, res) => {
  try {
    const apiKey = req.headers.authorization?.replace('Bearer ', '');
    if (!apiKey) { sendError(res, ErrorCode.GPS_DEVICE_NOT_REGISTERED, 'Device API key required.'); return; }

    const { deviceImei, vehicleId, latitude, longitude, speed, heading, ignitionStatus, recordedAt } = req.body;

    const device = await prisma.gpsDevice.findUnique({
      where: { deviceImei },
      include: { vehicle: { select: { id: true } } },
    });
    if (!device || device.status !== 'ACTIVE') {
      sendError(res, ErrorCode.GPS_DEVICE_NOT_REGISTERED, 'Device IMEI is not registered or inactive.');
      return;
    }

    if (device.deviceApiKey !== apiKey) {
      sendError(res, ErrorCode.GPS_DEVICE_NOT_REGISTERED, 'Invalid device API key.');
      return;
    }

    const resolvedVehicleId = device.vehicle?.id || vehicleId;
    if (!resolvedVehicleId) {
      sendError(res, ErrorCode.GPS_DEVICE_NOT_REGISTERED, 'Device is not assigned to any vehicle.');
      return;
    }

    let tripId: string | null = null;
    const activeTrip = await prisma.trip.findFirst({
      where: { vehicleId: resolvedVehicleId, status: { notIn: [TripStatus.TRIP_COMPLETED, TripStatus.CANCELLED, TripStatus.NOT_STARTED] } },
    });
    if (activeTrip) { tripId = activeTrip.id; }

    const log = await prisma.gpsLocationLog.create({
      data: {
        deviceId: device.id, vehicleId: resolvedVehicleId,
        tripId, latitude, longitude, speed, heading, ignitionStatus,
        recordedAt: new Date(recordedAt),
      },
    });

    await prisma.gpsDevice.update({ where: { id: device.id }, data: { lastSeenAt: new Date() } });

    sendSuccess(res, { logId: log.id, tripLinked: !!tripId }, 'Telemetry received');
  } catch (error) {
    sendError(res, ErrorCode.SERVER_ERROR, 'Failed to process GPS data.');
  }
});

gpsRoutes.get('/vehicles/:vehicleId/location/latest', authenticate, async (req, res) => {
  try {
    const log = await prisma.gpsLocationLog.findFirst({
      where: { vehicleId: req.params.vehicleId },
      orderBy: { recordedAt: 'desc' },
    });

    if (!log) { sendSuccess(res, null, 'No GPS data available for this vehicle.'); return; }

    const staleThreshold = config.gps.staleThresholdMinutes * 60 * 1000;
    const isStale = (Date.now() - log.recordedAt.getTime()) > staleThreshold;

    sendSuccess(res, {
      vehicleId: log.vehicleId, latitude: log.latitude, longitude: log.longitude,
      speed: log.speed, heading: log.heading, recordedAt: log.recordedAt.toISOString(),
      lastUpdatedAt: log.receivedAt.toISOString(), isStale,
      ...(isStale && { warning: 'Location may be outdated' }),
    });
  } catch (error) {
    sendError(res, ErrorCode.SERVER_ERROR, 'Failed to get location.');
  }
});

gpsRoutes.get('/vehicles/:vehicleId/location/history', authenticate, async (req, res) => {
  try {
    const limit = parseInt(req.query.limit as string) || 100;
    const from = req.query.from ? new Date(req.query.from as string) : new Date(Date.now() - 24 * 60 * 60 * 1000);
    const to = req.query.to ? new Date(req.query.to as string) : new Date();

    const logs = await prisma.gpsLocationLog.findMany({
      where: { vehicleId: req.params.vehicleId, recordedAt: { gte: from, lte: to } },
      orderBy: { recordedAt: 'asc' }, take: limit,
      select: { latitude: true, longitude: true, speed: true, heading: true, recordedAt: true },
    });
    sendSuccess(res, { vehicleId: req.params.vehicleId, from: from.toISOString(), to: to.toISOString(), points: logs });
  } catch (error) {
    sendError(res, ErrorCode.SERVER_ERROR, 'Failed to get location history.');
  }
});

gpsRoutes.get('/devices', authenticate, requireRoles(Role.OWNER, Role.CTO, Role.HARDWARE_TECHNICIAN), async (_req, res) => {
  try {
    const devices = await prisma.gpsDevice.findMany({
      include: { vehicle: { select: { id: true, vehicleNumber: true, school: { select: { id: true, name: true } } } } },
    });
    sendSuccess(res, devices);
  } catch (error) {
    sendError(res, ErrorCode.SERVER_ERROR, 'Failed to list devices.');
  }
});

gpsRoutes.post('/devices', authenticate, requireRoles(Role.OWNER, Role.CTO, Role.HARDWARE_TECHNICIAN), async (req, res) => {
  try {
    const { deviceImei, vehicleId, firmwareVersion } = req.body;

    const { v4: uuidv4 } = await import('uuid');
    const deviceApiKey = uuidv4() + '-' + uuidv4();

    const device = await prisma.gpsDevice.create({
      data: { deviceImei, firmwareVersion, deviceApiKey },
    });

    if (vehicleId) {
      await prisma.vehicle.update({
        where: { id: vehicleId },
        data: { gpsDeviceId: device.id },
      });
    }

    sendSuccess(res, { id: device.id, deviceImei: device.deviceImei, deviceApiKey }, 'Device registered', 201);
  } catch (error: any) {
    if (error.code === 'P2002') {
      sendError(res, ErrorCode.VALIDATION_ERROR, 'Device IMEI already registered.');
    } else {
      sendError(res, ErrorCode.SERVER_ERROR, 'Failed to register device.');
    }
  }
});

gpsRoutes.patch('/devices/:deviceId', authenticate, requireRoles(Role.OWNER, Role.HARDWARE_TECHNICIAN), async (req, res) => {
  try {
    const { vehicleId, status, firmwareVersion } = req.body;

    const updateData: Record<string, unknown> = {};
    if (status) updateData.status = status;
    if (firmwareVersion) updateData.firmwareVersion = firmwareVersion;

    const device = await prisma.gpsDevice.update({
      where: { id: req.params.deviceId },
      data: updateData,
    });

    if (vehicleId !== undefined) {
      await prisma.vehicle.updateMany({
        where: { gpsDeviceId: device.id },
        data: { gpsDeviceId: null },
      });

      if (vehicleId) {
        await prisma.vehicle.update({
          where: { id: vehicleId },
          data: { gpsDeviceId: device.id },
        });
      }
    }

    sendSuccess(res, { id: device.id, status: device.status }, 'Device updated');
  } catch (error) {
    sendError(res, ErrorCode.SERVER_ERROR, 'Failed to update device.');
  }
});

gpsRoutes.get('/devices/:deviceId/health', authenticate, requireRoles(Role.OWNER, Role.CTO, Role.HARDWARE_TECHNICIAN), async (req, res) => {
  try {
    const device = await prisma.gpsDevice.findUnique({ where: { id: req.params.deviceId } });
    if (!device) { sendError(res, ErrorCode.RESOURCE_NOT_FOUND, 'Device not found.'); return; }

    const isOnline = device.lastSeenAt && (Date.now() - device.lastSeenAt.getTime()) < config.gps.staleThresholdMinutes * 60 * 1000;
    const lastLog = await prisma.gpsLocationLog.findFirst({
      where: { deviceId: device.id }, orderBy: { recordedAt: 'desc' },
      select: { recordedAt: true, receivedAt: true },
    });

    sendSuccess(res, {
      deviceId: device.id, deviceImei: device.deviceImei, status: device.status,
      isOnline, lastSeenAt: device.lastSeenAt?.toISOString(),
      lastLog: lastLog ? { recordedAt: lastLog.recordedAt.toISOString(), receivedAt: lastLog.receivedAt.toISOString() } : null,
    });
  } catch (error) {
    sendError(res, ErrorCode.SERVER_ERROR, 'Failed to get device health.');
  }
});
