import bcrypt from "bcrypt";
import type { ResultSetHeader, RowDataPacket } from "mysql2";

import { db } from "@/src/lib/db";
import { isUserRole } from "@/src/lib/permissions";
import type { DbUser, UserInput, UserPublic, UserRole } from "@/src/types/user";

type UserRow = DbUser & RowDataPacket;

const BCRYPT_ROUNDS = 10;

function toPublic(row: UserRow): UserPublic {
  return {
    id: row.id,
    utente: row.utente,
    mail: row.mail,
    ruolo: row.ruolo,
    attivo: row.attivo,
    last_login: row.last_login,
  };
}

function cleanText(value: unknown): string {
  return String(value ?? "").trim();
}

function parseUserInput(input: Partial<UserInput>, requirePassword: boolean): UserInput {
  const utente = cleanText(input.utente);
  const mail = cleanText(input.mail);
  const ruolo = cleanText(input.ruolo) as UserRole;
  const attivo = input.attivo === 0 || input.attivo === 1 ? input.attivo : 1;
  const password = input.password ? String(input.password) : "";

  if (!utente) {
    throw new Error("Il nome utente è obbligatorio.");
  }
  if (!mail || !mail.includes("@")) {
    throw new Error("Inserire un indirizzo email valido.");
  }
  if (!isUserRole(ruolo)) {
    throw new Error("Ruolo non valido. Scegli Amministratore, Tecnico o Operatore.");
  }
  if (requirePassword && password.length < 6) {
    throw new Error("La password deve avere almeno 6 caratteri.");
  }
  if (!requirePassword && password && password.length < 6) {
    throw new Error("La password deve avere almeno 6 caratteri.");
  }

  return { utente, mail, ruolo, attivo, password: password || undefined };
}

export async function listUsers(): Promise<UserPublic[]> {
  const [rows] = await db.execute<UserRow[]>(
    `SELECT id, utente, mail, ruolo, attivo, last_login
     FROM utenti
     ORDER BY utente ASC`,
  );

  return rows.map(toPublic);
}

export async function getUserById(id: number): Promise<UserPublic | null> {
  const [rows] = await db.execute<UserRow[]>(
    `SELECT id, utente, mail, ruolo, attivo, last_login
     FROM utenti WHERE id = :id LIMIT 1`,
    { id },
  );

  const row = rows[0];
  return row ? toPublic(row) : null;
}

async function assertUniqueUtenteMail(
  utente: string,
  mail: string,
  excludeId?: number,
): Promise<void> {
  const params: Record<string, string | number> = { utente, mail };
  let query = `
    SELECT id FROM utenti
    WHERE (utente = :utente OR mail = :mail)
  `;
  if (excludeId) {
    query += " AND id != :excludeId";
    params.excludeId = excludeId;
  }
  query += " LIMIT 1";

  const [rows] = await db.execute<RowDataPacket[]>(query, params);
  if (rows[0]) {
    throw new Error("Nome utente o email già in uso.");
  }
}

export async function createUser(input: Partial<UserInput>): Promise<UserPublic> {
  const parsed = parseUserInput(input, true);
  await assertUniqueUtenteMail(parsed.utente, parsed.mail);

  const hash = await bcrypt.hash(parsed.password!, BCRYPT_ROUNDS);

  const [result] = await db.execute<ResultSetHeader>(
    `INSERT INTO utenti (utente, mail, password, ruolo, attivo)
     VALUES (:utente, :mail, :password, :ruolo, :attivo)`,
    {
      utente: parsed.utente,
      mail: parsed.mail,
      password: hash,
      ruolo: parsed.ruolo,
      attivo: parsed.attivo,
    },
  );

  const created = await getUserById(result.insertId);
  if (!created) {
    throw new Error("Errore nella creazione dell'utente.");
  }
  return created;
}

export async function updateUser(
  id: number,
  input: Partial<UserInput>,
): Promise<UserPublic> {
  const existing = await getUserById(id);
  if (!existing) {
    throw new Error("Utente non trovato.");
  }

  const parsed = parseUserInput(
    {
      utente: input.utente ?? existing.utente,
      mail: input.mail ?? existing.mail,
      ruolo: input.ruolo ?? existing.ruolo,
      attivo: input.attivo ?? existing.attivo,
      password: input.password,
    },
    false,
  );

  await assertUniqueUtenteMail(parsed.utente, parsed.mail, id);

  if (parsed.password) {
    const hash = await bcrypt.hash(parsed.password, BCRYPT_ROUNDS);
    await db.execute(
      `UPDATE utenti
       SET utente = :utente, mail = :mail, ruolo = :ruolo, attivo = :attivo, password = :password
       WHERE id = :id`,
      {
        utente: parsed.utente,
        mail: parsed.mail,
        ruolo: parsed.ruolo,
        attivo: parsed.attivo,
        password: hash,
        id,
      },
    );
  } else {
    await db.execute(
      `UPDATE utenti
       SET utente = :utente, mail = :mail, ruolo = :ruolo, attivo = :attivo
       WHERE id = :id`,
      {
        utente: parsed.utente,
        mail: parsed.mail,
        ruolo: parsed.ruolo,
        attivo: parsed.attivo,
        id,
      },
    );
  }

  const updated = await getUserById(id);
  if (!updated) {
    throw new Error("Errore nell'aggiornamento dell'utente.");
  }
  return updated;
}

export async function deleteUser(id: number): Promise<void> {
  const existing = await getUserById(id);
  if (!existing) {
    throw new Error("Utente non trovato.");
  }

  await db.execute("DELETE FROM utenti WHERE id = :id", { id });
}
