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,
  updateVehicleSchema,
} from "./vehicles.schemas.js";

type VehicleRow = {
  id: string;
  name: string;
  plate_number: string | null;
  color_key: string | null;
  vehicle_type: "passenger" | "delivery";
  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,
    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);

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,
        ),
        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.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";
    }>(
      `
        insert into vehicles (name, plate_number, color_key, vehicle_type)
        values ($1, $2, $3, $4)
        returning id, name, plate_number, color_key, vehicle_type
      `,
      [
        request.body.name,
        request.body.plateNumber ?? null,
        request.body.colorKey ?? DEFAULT_VEHICLE_COLOR_KEY,
        request.body.vehicleKind ?? "passenger",
      ],
    );

    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,
      },
    });
  }),
);

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";
    }>(
      `
        update vehicles
        set
          name = $2,
          plate_number = $3,
          color_key = $4,
          vehicle_type = $5,
          updated_at = now()
        where id = $1
        returning id, name, plate_number, color_key, vehicle_type
      `,
      [
        request.params.id,
        request.body.name,
        request.body.plateNumber ?? null,
        request.body.colorKey,
        request.body.vehicleKind,
      ],
    );

    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,
      },
    });
  }),
);

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,
    });
  }),
);
