import { Router } from "express";

import { DEFAULT_VEHICLE_COLOR_KEY, getVehicleStatus } from "@tracker/shared";

import { env } from "../../config/env.js";
import { asyncHandler } from "../../lib/async-handler.js";
import { query, queryOne } from "../../lib/db.js";
import { HttpError } from "../../lib/http-error.js";
import { requireAuth, requireRole } from "../../middleware/auth.js";
import { validate } from "../../middleware/validate.js";
import { assertVehicleAccess } from "../permissions/access.js";
import {
  createVehicleSchema,
  deleteVehicleSchema,
  historyQuerySchema,
  updateVehicleInspectionSchema,
  updateVehicleSchema,
} from "./vehicles.schemas.js";

type VehicleRow = {
  id: string;
  name: string;
  plate_number: string | null;
  color_key: string | null;
  vehicle_type: "passenger" | "delivery";
  inspection_due_date: string | null;
  device_id: string | null;
  imei: string | null;
  phone_number: string | null;
  last_seen: string | null;
  last_device_time: string | null;
  device_battery: number | null;
  device_gsm_signal: number | null;
  device_ignition: boolean | null;
  device_charging: boolean | null;
  device_defense: boolean | null;
  device_gps_valid: boolean | null;
  device_satellite_count: number | null;
  device_position_type: string | null;
  lat: number | null;
  lon: number | null;
  speed: number | null;
  heading: number | null;
  device_time: string | null;
  server_time: string | null;
  ignition: boolean | null;
  battery: number | null;
  gsm_signal: number | null;
  satellite_count: number | null;
  gps_valid: boolean | null;
  charging: boolean | null;
  defense: boolean | null;
  position_type: string | null;
  first_history_at: string | null;
};

const vehicleListSql = `
  select
    v.id,
    v.name,
    v.plate_number,
    v.color_key,
    v.vehicle_type,
    v.inspection_due_date,
    d.id as device_id,
    d.imei,
    d.phone_number,
    d.last_seen,
    d.last_device_time,
    d.battery as device_battery,
    d.gsm_signal as device_gsm_signal,
    d.ignition as device_ignition,
    d.charging as device_charging,
    d.defense as device_defense,
    d.gps_valid as device_gps_valid,
    d.satellite_count as device_satellite_count,
    d.position_type as device_position_type,
    lp.lat,
    lp.lon,
    lp.speed,
    lp.heading,
    lp.device_time,
    lp.server_time,
    lp.ignition,
    lp.battery,
    lp.gsm_signal,
    lp.satellite_count,
    lp.gps_valid,
    lp.charging,
    lp.defense,
    lp.position_type,
    hp.first_history_at
  from vehicles v
  left join devices d on d.vehicle_id = v.id
  left join last_positions lp on lp.vehicle_id = v.id
  left join (
    select vehicle_id, min(server_time) as first_history_at
    from positions
    where vehicle_id is not null
    group by vehicle_id
  ) hp on hp.vehicle_id = v.id
`;

export const vehiclesRouter = Router();

vehiclesRouter.use(requireAuth);

const getInspectionStatus = (inspectionDueDate?: string | null) => {
  if (!inspectionDueDate) {
    return "missing" as const;
  }

  const today = new Date();
  const todayUtc = Date.UTC(today.getFullYear(), today.getMonth(), today.getDate());
  const due = new Date(`${inspectionDueDate}T00:00:00`);
  const dueUtc = Date.UTC(due.getFullYear(), due.getMonth(), due.getDate());

  if (Number.isNaN(dueUtc)) {
    return "missing" as const;
  }

  const dayDiff = Math.floor((dueUtc - todayUtc) / 86400000);
  return dayDiff <= 14 ? "due_soon" as const : "ok" as const;
};

vehiclesRouter.get(
  "/",
  asyncHandler(async (request, response) => {
    const rows = request.user?.role === "admin"
      ? await query<VehicleRow>(`${vehicleListSql} order by v.name asc`)
      : await query<VehicleRow>(
          `
            ${vehicleListSql}
            inner join user_vehicle_permissions uvp on uvp.vehicle_id = v.id
            where uvp.user_id = $1
            order by v.name asc
          `,
          [request.user?.id],
        );

    response.json({
      items: rows.map((row) => ({
        id: row.id,
        name: row.name,
        plateNumber: row.plate_number,
        colorKey: row.color_key ?? DEFAULT_VEHICLE_COLOR_KEY,
        vehicleKind: row.vehicle_type,
        deviceId: row.device_id,
        imei: row.imei,
        phoneNumber: row.phone_number,
        lastSeen: row.last_seen,
        firstHistoryAt: row.first_history_at,
        status: getVehicleStatus(
          row.last_seen,
          env.DEVICE_ONLINE_SECONDS,
          env.DEVICE_DELAYED_SECONDS,
        ),
        inspectionDueDate: row.inspection_due_date,
        inspectionStatus: getInspectionStatus(row.inspection_due_date),
        lastPosition: row.lat != null && row.lon != null
          ? {
              lat: row.lat,
              lon: row.lon,
              speed: row.speed,
              heading: row.heading,
              deviceTime: row.device_time,
              serverTime: row.server_time ?? row.last_seen ?? new Date(0).toISOString(),
              ignition: row.ignition,
              battery: row.battery,
              gsmSignal: row.gsm_signal,
              satelliteCount: row.satellite_count,
              gpsValid: row.gps_valid,
              charging: row.charging,
              defense: row.defense,
              positionType: row.position_type,
            }
          : null,
        deviceState: row.device_id
          ? {
              phoneNumber: row.phone_number,
              protocolName: null,
              deviceTime: row.last_device_time,
              battery: row.device_battery,
              gsmSignal: row.device_gsm_signal,
              ignition: row.device_ignition,
              charging: row.device_charging,
              defense: row.device_defense,
              gpsValid: row.device_gps_valid,
              satelliteCount: row.device_satellite_count,
              positionType: row.device_position_type,
            }
          : null,
      })),
    });
  }),
);

vehiclesRouter.get(
  "/stats/overview",
  asyncHandler(async (request, response) => {
    const isAdmin = request.user?.role === "admin";
    const scopeJoin = isAdmin
      ? ""
      : "inner join user_vehicle_permissions uvp on uvp.vehicle_id = v.id";
    const topSpeedWhere = isAdmin
      ? "where p.vehicle_id is not null and p.speed is not null"
      : "where uvp.user_id = $1 and p.vehicle_id is not null and p.speed is not null";
    const distanceWhere = isAdmin
      ? "where p.vehicle_id is not null"
      : "where uvp.user_id = $1 and p.vehicle_id is not null";
    const params = isAdmin ? [] : [request.user?.id];

    const topSpeed = await queryOne<{
      vehicle_id: string;
      vehicle_name: string;
      plate_number: string | null;
      color_key: string | null;
      vehicle_type: "passenger" | "delivery";
      speed: number;
      server_time: string;
    }>(
      `
        with ranked_speeds as (
          select
            p.vehicle_id,
            v.name as vehicle_name,
            v.plate_number,
            v.color_key,
            v.vehicle_type,
            p.speed,
            p.server_time,
            row_number() over (partition by p.vehicle_id order by p.speed desc, p.server_time desc) as vehicle_rank
          from positions p
          inner join vehicles v on v.id = p.vehicle_id
          ${scopeJoin}
          ${topSpeedWhere}
        )
        select
          vehicle_id,
          vehicle_name,
          plate_number,
          color_key,
          vehicle_type,
          speed,
          server_time
        from ranked_speeds
        where vehicle_rank = 1
        order by speed desc, server_time desc
        limit 1
      `.replace(/\n\s+\n/g, "\n"),
      params,
    );

    const topSpeeds = await query<{
      vehicle_id: string;
      vehicle_name: string;
      plate_number: string | null;
      color_key: string | null;
      vehicle_type: "passenger" | "delivery";
      speed: number;
      server_time: string;
    }>(
      `
        with ranked_speeds as (
          select
            p.vehicle_id,
            v.name as vehicle_name,
            v.plate_number,
            v.color_key,
            v.vehicle_type,
            p.speed,
            p.server_time,
            row_number() over (partition by p.vehicle_id order by p.speed desc, p.server_time desc) as vehicle_rank
          from positions p
          inner join vehicles v on v.id = p.vehicle_id
          ${scopeJoin}
          ${topSpeedWhere}
        )
        select
          vehicle_id,
          vehicle_name,
          plate_number,
          color_key,
          vehicle_type,
          speed,
          server_time
        from ranked_speeds
        where vehicle_rank = 1
        order by speed desc, server_time desc
        limit 5
      `.replace(/\n\s+\n/g, "\n"),
      params,
    );

    const distanceRows = await query<{
      vehicle_id: string;
      vehicle_name: string;
      plate_number: string | null;
      color_key: string | null;
      vehicle_type: "passenger" | "delivery";
      total_meters: number;
      distance_day: string;
    }>(
      `
        with scoped_positions as (
          select
            p.vehicle_id,
            v.name as vehicle_name,
            v.plate_number,
            v.color_key,
            v.vehicle_type,
            date_trunc('day', p.server_time at time zone 'Europe/Warsaw')::date as distance_day,
            p.server_time,
            p.geom,
            lag(p.geom) over (
              partition by p.vehicle_id, date_trunc('day', p.server_time at time zone 'Europe/Warsaw')::date
              order by p.server_time asc
            ) as prev_geom,
            lag(p.server_time) over (
              partition by p.vehicle_id, date_trunc('day', p.server_time at time zone 'Europe/Warsaw')::date
              order by p.server_time asc
            ) as prev_server_time
          from positions p
          inner join vehicles v on v.id = p.vehicle_id
          ${scopeJoin}
          ${distanceWhere}
        ),
        vehicle_day_distances as (
          select
            vehicle_id,
            vehicle_name,
            plate_number,
            color_key,
            vehicle_type,
            distance_day,
            coalesce(sum(
              case
                when prev_geom is null or prev_server_time is null then 0
                when extract(epoch from (server_time - prev_server_time)) > 1800 then 0
                else least(st_distance(geom, prev_geom), 2000)
              end
            ), 0) as total_meters
          from scoped_positions
          group by vehicle_id, vehicle_name, plate_number, color_key, vehicle_type, distance_day
        ),
        ranked_vehicle_days as (
          select
            vehicle_id,
            vehicle_name,
            plate_number,
            color_key,
            vehicle_type,
            distance_day,
            total_meters,
            row_number() over (partition by vehicle_id order by total_meters desc, distance_day desc) as vehicle_rank
          from vehicle_day_distances
        )
        select
          vehicle_id,
          vehicle_name,
          plate_number,
          color_key,
          vehicle_type,
          total_meters,
          distance_day::text
        from ranked_vehicle_days
        where vehicle_rank = 1
        order by total_meters desc, distance_day desc, vehicle_name asc
        limit 5
      `.replace(/\n\s+\n/g, "\n"),
      params,
    );

    const mapSpeedRecord = (
      row: {
        vehicle_id: string;
        vehicle_name: string;
        plate_number: string | null;
        color_key: string | null;
        vehicle_type: "passenger" | "delivery";
        speed: number;
        server_time: string;
      } | null | undefined,
    ) => row
      ? {
          vehicleId: row.vehicle_id,
          vehicleName: row.vehicle_name,
          plateNumber: row.plate_number,
          colorKey: row.color_key ?? DEFAULT_VEHICLE_COLOR_KEY,
          vehicleKind: row.vehicle_type,
          value: row.speed,
          unit: "km/h" as const,
          recordedAt: row.server_time,
          recordedDay: null,
        }
      : null;

    const mapDistanceRecord = (row: {
      vehicle_id: string;
      vehicle_name: string;
      plate_number: string | null;
      color_key: string | null;
      vehicle_type: "passenger" | "delivery";
      total_meters: number;
      distance_day: string;
    } | null | undefined) => row
      ? {
          vehicleId: row.vehicle_id,
          vehicleName: row.vehicle_name,
          plateNumber: row.plate_number,
          colorKey: row.color_key ?? DEFAULT_VEHICLE_COLOR_KEY,
          vehicleKind: row.vehicle_type,
          value: Number((row.total_meters / 1000).toFixed(1)),
          unit: "km" as const,
          recordedAt: row.distance_day,
          recordedDay: row.distance_day,
        }
      : null;

    response.json({
      item: {
        topSpeed: mapSpeedRecord(topSpeed),
        topDistance: mapDistanceRecord(distanceRows[0]),
        topSpeeds: topSpeeds.map((row) => mapSpeedRecord(row)).filter(Boolean),
        topDistances: distanceRows.map((row) => mapDistanceRecord(row)).filter(Boolean),
      },
    });
  }),
);

vehiclesRouter.post(
  "/",
  requireRole("admin"),
  validate(createVehicleSchema),
  asyncHandler(async (request, response) => {
    const created = await queryOne<{
      id: string;
      name: string;
      plate_number: string | null;
      color_key: string;
      vehicle_type: "passenger" | "delivery";
      inspection_due_date: string | null;
    }>(
      `
        insert into vehicles (name, plate_number, color_key, vehicle_type, inspection_due_date)
        values ($1, $2, $3, $4, $5)
        returning id, name, plate_number, color_key, vehicle_type, inspection_due_date
      `,
      [
        request.body.name,
        request.body.plateNumber ?? null,
        request.body.colorKey ?? DEFAULT_VEHICLE_COLOR_KEY,
        request.body.vehicleKind ?? "passenger",
        request.body.inspectionDueDate ?? null,
      ],
    );

    if (!created) {
      throw new HttpError(500, "Failed to create vehicle");
    }

    if (request.body.deviceId) {
      await query(
        `
          update devices
          set vehicle_id = $2, updated_at = now()
          where id = $1
        `,
        [request.body.deviceId, created.id],
      );
    }

    for (const userId of request.body.userIds) {
      await query(
        `
          insert into user_vehicle_permissions (user_id, vehicle_id)
          values ($1, $2)
          on conflict do nothing
        `,
        [userId, created.id],
      );
    }

    response.status(201).json({
      item: {
        id: created.id,
        name: created.name,
        plateNumber: created.plate_number,
        colorKey: created.color_key,
        vehicleKind: created.vehicle_type,
        inspectionDueDate: created.inspection_due_date,
        inspectionStatus: getInspectionStatus(created.inspection_due_date),
      },
    });
  }),
);

vehiclesRouter.patch(
  "/:id",
  requireRole("admin"),
  validate(updateVehicleSchema),
  asyncHandler(async (request, response) => {
    const updated = await queryOne<{
      id: string;
      name: string;
      plate_number: string | null;
      color_key: string;
      vehicle_type: "passenger" | "delivery";
      inspection_due_date: string | null;
    }>(
      `
        update vehicles
        set
          name = $2,
          plate_number = $3,
          color_key = $4,
          vehicle_type = $5,
          inspection_due_date = $6,
          updated_at = now()
        where id = $1
        returning id, name, plate_number, color_key, vehicle_type, inspection_due_date
      `,
      [
        request.params.id,
        request.body.name,
        request.body.plateNumber ?? null,
        request.body.colorKey,
        request.body.vehicleKind,
        request.body.inspectionDueDate ?? null,
      ],
    );

    if (!updated) {
      throw new HttpError(404, "Vehicle not found");
    }

    response.json({
      item: {
        id: updated.id,
        name: updated.name,
        plateNumber: updated.plate_number,
        colorKey: updated.color_key,
        vehicleKind: updated.vehicle_type,
        inspectionDueDate: updated.inspection_due_date,
        inspectionStatus: getInspectionStatus(updated.inspection_due_date),
      },
    });
  }),
);

vehiclesRouter.patch(
  "/:id/inspection",
  validate(updateVehicleInspectionSchema),
  asyncHandler(async (request, response) => {
    if (!request.user) {
      throw new HttpError(401, "Missing authenticated user");
    }

    const vehicleId = String(request.params.id);

    if (request.user.role !== "admin") {
      if (!request.user.canEditVehicleInspection) {
        throw new HttpError(403, "Insufficient permissions");
      }
      await assertVehicleAccess(request.user, vehicleId);
    }

    const updated = await queryOne<{
      id: string;
      name: string;
      plate_number: string | null;
      color_key: string;
      vehicle_type: "passenger" | "delivery";
      inspection_due_date: string | null;
    }>(
      `
        update vehicles
        set
          inspection_due_date = $2,
          updated_at = now()
        where id = $1
        returning id, name, plate_number, color_key, vehicle_type, inspection_due_date
      `,
      [vehicleId, request.body.inspectionDueDate ?? null],
    );

    if (!updated) {
      throw new HttpError(404, "Vehicle not found");
    }

    response.json({
      item: {
        id: updated.id,
        name: updated.name,
        plateNumber: updated.plate_number,
        colorKey: updated.color_key,
        vehicleKind: updated.vehicle_type,
        inspectionDueDate: updated.inspection_due_date,
        inspectionStatus: getInspectionStatus(updated.inspection_due_date),
      },
    });
  }),
);

vehiclesRouter.delete(
  "/:id",
  requireRole("admin"),
  validate(deleteVehicleSchema),
  asyncHandler(async (request, response) => {
    const removed = await queryOne<{ id: string }>(
      `
        delete from vehicles
        where id = $1
        returning id
      `,
      [request.params.id],
    );

    if (!removed) {
      throw new HttpError(404, "Vehicle not found");
    }

    response.status(204).send();
  }),
);

vehiclesRouter.get(
  "/:id/history",
  validate(historyQuerySchema),
  asyncHandler(async (request, response) => {
    if (!request.user) {
      throw new HttpError(401, "Missing authenticated user");
    }

    const vehicleId = String(request.params.id);
    await assertVehicleAccess(request.user, vehicleId);

    const queryParams = request.query as unknown as {
      from?: string;
      to?: string;
      limit: number;
    };
    const from = queryParams.from ? new Date(queryParams.from) : new Date(Date.now() - 1000 * 60 * 60 * 24);
    const to = queryParams.to ? new Date(queryParams.to) : new Date();

    const rows = await query<{
      id: number;
      lat: number;
      lon: number;
      speed: number | null;
      heading: number | null;
      device_time: string | null;
      server_time: string;
      ignition: boolean | null;
      battery: number | null;
      gsm_signal: number | null;
      satellite_count: number | null;
      gps_valid: boolean | null;
      charging: boolean | null;
      defense: boolean | null;
      position_type: string | null;
    }>(
      `
        select
          id,
          lat,
          lon,
          speed,
          heading,
          device_time,
          server_time,
          ignition,
          battery,
          gsm_signal,
          satellite_count,
          gps_valid,
          charging,
          defense,
          position_type
        from positions
        where vehicle_id = $1
          and server_time between $2 and $3
        order by server_time desc
        limit $4
      `,
      [vehicleId, from.toISOString(), to.toISOString(), queryParams.limit],
    );

    response.json({
      items: rows,
    });
  }),
);
