import bcrypt from "bcryptjs";
import { Router } from "express";

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 { createUserSchema, deleteUserSchema, updateUserSchema } from "./users.schemas.js";

export const usersRouter = Router();

usersRouter.use(requireAuth, requireRole("admin"));

usersRouter.get(
  "/",
  asyncHandler(async (_request, response) => {
    const users = await query<{
      id: string;
      email: string;
      first_name: string;
      last_name: string;
      role: "admin" | "user";
      can_edit_vehicle_inspection: boolean;
      can_view_stats: boolean;
      created_at: string;
      vehicle_ids: string[] | null;
    }>(
      `
        select
          u.id,
          u.email,
          u.first_name,
          u.last_name,
          u.role,
          u.can_edit_vehicle_inspection,
          u.can_view_stats,
          u.created_at,
          coalesce(array_agg(uvp.vehicle_id order by uvp.vehicle_id) filter (where uvp.vehicle_id is not null), '{}') as vehicle_ids
        from users u
        left join user_vehicle_permissions uvp on uvp.user_id = u.id
        group by u.id, u.email, u.first_name, u.last_name, u.role, u.can_edit_vehicle_inspection, u.can_view_stats, u.created_at
        order by u.created_at desc
      `,
    );

    response.json({
      items: users.map((user) => ({
        id: user.id,
        email: user.email,
        firstName: user.first_name,
        lastName: user.last_name,
        role: user.role,
        canEditVehicleInspection: user.can_edit_vehicle_inspection,
        canViewStats: user.can_view_stats,
        createdAt: user.created_at,
        vehicleIds: user.vehicle_ids ?? [],
      })),
    });
  }),
);

usersRouter.post(
  "/",
  validate(createUserSchema),
  asyncHandler(async (request, response) => {
    const existing = await queryOne<{ id: string }>("select id from users where email = $1", [request.body.email]);
    if (existing) {
      throw new HttpError(409, "User already exists");
    }

    const passwordHash = await bcrypt.hash(request.body.password, 10);
    const created = await queryOne<{ id: string; email: string; first_name: string; last_name: string; role: "admin" | "user"; can_edit_vehicle_inspection: boolean; can_view_stats: boolean; created_at: string }>(
      `
        insert into users (email, first_name, last_name, password_hash, role, can_edit_vehicle_inspection, can_view_stats)
        values ($1, $2, $3, $4, $5, $6, $7)
        returning id, email, first_name, last_name, role, can_edit_vehicle_inspection, can_view_stats, created_at
      `,
      [request.body.email, request.body.firstName, request.body.lastName, passwordHash, request.body.role, request.body.canEditVehicleInspection, request.body.canViewStats],
    );

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

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

    response.status(201).json({
      item: {
        id: created.id,
        email: created.email,
        firstName: created.first_name,
        lastName: created.last_name,
        role: created.role,
        canEditVehicleInspection: created.can_edit_vehicle_inspection,
        canViewStats: created.can_view_stats,
        createdAt: created.created_at,
        vehicleIds: request.body.vehicleIds,
      },
    });
  }),
);

usersRouter.patch(
  "/:id",
  validate(updateUserSchema),
  asyncHandler(async (request, response) => {
    const userId = request.params.id;
    const existing = await queryOne<{ id: string; email: string; first_name: string; last_name: string; role: "admin" | "user"; can_edit_vehicle_inspection: boolean; can_view_stats: boolean; created_at: string }>(
      "select id, email, first_name, last_name, role, can_edit_vehicle_inspection, can_view_stats, created_at from users where id = $1",
      [userId],
    );

    if (!existing) {
      throw new HttpError(404, "User not found");
    }

    if (request.user?.id === userId && request.body.role !== "admin") {
      throw new HttpError(400, "Nie możesz odebrać sobie roli admina");
    }

    const passwordHash = request.body.password ? await bcrypt.hash(request.body.password, 10) : null;

    await query(
      `
        update users
        set
          first_name = $2,
          last_name = $3,
          role = $4,
          password_hash = coalesce($5, password_hash),
          can_edit_vehicle_inspection = $6,
          can_view_stats = $7,
          updated_at = now()
        where id = $1
      `,
      [userId, request.body.firstName, request.body.lastName, request.body.role, passwordHash, request.body.canEditVehicleInspection, request.body.canViewStats],
    );

    await query("delete from user_vehicle_permissions where user_id = $1", [userId]);

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

    response.json({
      item: {
        id: existing.id,
        email: existing.email,
        firstName: request.body.firstName,
        lastName: request.body.lastName,
        role: request.body.role,
        canEditVehicleInspection: request.body.canEditVehicleInspection,
        canViewStats: request.body.canViewStats,
        createdAt: existing.created_at,
        vehicleIds: request.body.vehicleIds,
      },
    });
  }),
);

usersRouter.delete(
  "/:id",
  validate(deleteUserSchema),
  asyncHandler(async (request, response) => {
    if (request.user?.id === request.params.id) {
      throw new HttpError(400, "Nie możesz usunąć własnego konta");
    }

    const removed = await queryOne<{ id: string }>(
      `
        delete from users
        where id = $1
        returning id
      `,
      [request.params.id],
    );

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

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