import bcrypt from "bcryptjs";

import type { UserRole } from "@rental/shared";

import { query } from "../../db/pool.js";
import { ConflictError, NotFoundError } from "../../utils/app-error.js";

type UserRow = {
  id: string;
  name: string;
  email: string;
  role: UserRole;
  is_active: boolean;
  created_at: Date;
  updated_at: Date;
};

const mapUser = (row: UserRow) => ({
  id: row.id,
  name: row.name,
  email: row.email,
  role: row.role,
  isActive: row.is_active,
  createdAt: row.created_at.toISOString(),
  updatedAt: row.updated_at.toISOString()
});

const getUserRow = async (id: string) => {
  const result = await query<UserRow>(
    `
      select id, name, email, role, is_active, created_at, updated_at
      from users
      where id = $1
    `,
    [id]
  );

  const user = result.rows[0];

  if (!user) {
    throw new NotFoundError("User not found");
  }

  return user;
};

const countActiveAdmins = async () => {
  const result = await query<{ count: number }>(
    "select count(*)::int as count from users where role = 'admin' and is_active = true"
  );

  return result.rows[0]?.count ?? 0;
};

const ensureAdminSafety = async (current: UserRow, nextRole: UserRole, nextActive: boolean) => {
  if (current.role !== "admin") {
    return;
  }

  if (nextRole === "admin" && nextActive) {
    return;
  }

  const admins = await countActiveAdmins();

  if (admins <= 1) {
    throw new ConflictError("At least one active admin must remain in the system");
  }
};

export const listUsers = async () => {
  const result = await query<UserRow>(
    `
      select id, name, email, role, is_active, created_at, updated_at
      from users
      order by created_at asc
    `
  );

  return result.rows.map(mapUser);
};

export const createUser = async (payload: {
  name: string;
  email: string;
  password: string;
  role: UserRole;
  isActive: boolean;
}) => {
  const passwordHash = await bcrypt.hash(payload.password, 10);

  const result = await query<UserRow>(
    `
      insert into users (name, email, password_hash, role, is_active)
      values ($1, $2, $3, $4, $5)
      returning id, name, email, role, is_active, created_at, updated_at
    `,
    [payload.name.trim(), payload.email.trim().toLowerCase(), passwordHash, payload.role, payload.isActive]
  );

  const row = result.rows[0];

  if (!row) {
    throw new Error("User insert did not return a row");
  }

  return mapUser(row);
};

export const updateUser = async (
  id: string,
  payload: Partial<{
    name: string;
    email: string;
    password: string;
    role: UserRole;
    isActive: boolean;
  }>
) => {
  const current = await getUserRow(id);
  const nextRole = payload.role ?? current.role;
  const nextActive = payload.isActive ?? current.is_active;

  await ensureAdminSafety(current, nextRole, nextActive);

  const passwordHashResult = payload.password ? await bcrypt.hash(payload.password, 10) : null;

  const result = await query<UserRow>(
    `
      update users
      set
        name = $2,
        email = $3,
        password_hash = coalesce($4, password_hash),
        role = $5,
        is_active = $6,
        updated_at = now()
      where id = $1
      returning id, name, email, role, is_active, created_at, updated_at
    `,
    [
      id,
      payload.name?.trim() ?? current.name,
      payload.email?.trim().toLowerCase() ?? current.email,
      passwordHashResult,
      nextRole,
      nextActive
    ]
  );

  const row = result.rows[0];

  if (!row) {
    throw new Error("User update did not return a row");
  }

  return mapUser(row);
};

export const deleteUser = async (id: string) => {
  const current = await getUserRow(id);
  await ensureAdminSafety(current, current.role, false);

  const result = await query<{ id: string }>("delete from users where id = $1 returning id", [id]);

  if (!result.rowCount) {
    throw new NotFoundError("User not found");
  }
};
