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

import { db } from "@/src/lib/db";
import type {
  Stabilimento,
  Magazzino,
  StabilimentoWithMagazzini,
  StabilimentoInput,
  MagazzinoInput,
} from "@/src/types/stabilimento";

type StabilimentoRow = Stabilimento & RowDataPacket;
type MagazzinoRow = Magazzino & RowDataPacket;

export async function listStabilimenti(): Promise<Stabilimento[]> {
  const [rows] = await db.execute<StabilimentoRow[]>(
    "SELECT id, descrizione FROM stabilimenti ORDER BY descrizione ASC"
  );

  return rows.map((row) => ({
    id: row.id,
    descrizione: row.descrizione,
  }));
}

export async function getStabilimentoById(id: number): Promise<Stabilimento | null> {
  const [rows] = await db.execute<StabilimentoRow[]>(
    "SELECT id, descrizione FROM stabilimenti WHERE id = :id LIMIT 1",
    { id }
  );

  const row = rows[0];
  if (!row) return null;

  return {
    id: row.id,
    descrizione: row.descrizione,
  };
}

export async function listMagazzini(stabilimentoId?: number): Promise<Magazzino[]> {
  let query = `
    SELECT m.id, m.descrizione, m.id_stabilimento, s.descrizione AS stabilimento
    FROM magazzini m
    LEFT JOIN stabilimenti s ON s.id = m.id_stabilimento
  `;

  const params: Record<string, number> = {};

  if (stabilimentoId) {
    query += " WHERE m.id_stabilimento = :stabilimentoId";
    params.stabilimentoId = stabilimentoId;
  }

  query += " ORDER BY s.descrizione ASC, m.descrizione ASC";

  const [rows] = await db.execute<MagazzinoRow[]>(query, params);

  return rows.map((row) => ({
    id: row.id,
    descrizione: row.descrizione,
    id_stabilimento: row.id_stabilimento,
    stabilimento: row.stabilimento,
  }));
}

export async function getStabilimentiWithMagazzini(): Promise<StabilimentoWithMagazzini[]> {
  const stabilimenti = await listStabilimenti();
  const magazzini = await listMagazzini();

  return stabilimenti.map((stab) => ({
    ...stab,
    magazzini: magazzini.filter((mag) => mag.id_stabilimento === stab.id),
  }));
}

export async function getMagazzinoById(id: number): Promise<Magazzino | null> {
  const [rows] = await db.execute<MagazzinoRow[]>(
    `SELECT m.id, m.descrizione, m.id_stabilimento, s.descrizione AS stabilimento
     FROM magazzini m
     LEFT JOIN stabilimenti s ON s.id = m.id_stabilimento
     WHERE m.id = :id LIMIT 1`,
    { id }
  );

  const row = rows[0];
  if (!row) return null;

  return {
    id: row.id,
    descrizione: row.descrizione,
    id_stabilimento: row.id_stabilimento,
    stabilimento: row.stabilimento,
  };
}

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

function parseStabilimentoInput(input: Partial<StabilimentoInput>): StabilimentoInput {
  const descrizione = cleanText(input.descrizione);
  if (!descrizione) {
    throw new Error("La descrizione dello stabilimento è obbligatoria.");
  }
  return { descrizione };
}

function parseMagazzinoInput(input: Partial<MagazzinoInput>): MagazzinoInput {
  const descrizione = cleanText(input.descrizione);
  const id_stabilimento = Number(input.id_stabilimento);

  if (!descrizione) {
    throw new Error("La descrizione del magazzino è obbligatoria.");
  }
  if (!Number.isInteger(id_stabilimento) || id_stabilimento <= 0) {
    throw new Error("Seleziona uno stabilimento valido per il magazzino.");
  }

  return { descrizione, id_stabilimento };
}

export async function createStabilimento(input: Partial<StabilimentoInput>): Promise<Stabilimento> {
  const { descrizione } = parseStabilimentoInput(input);

  const [result] = await db.execute<ResultSetHeader>(
    "INSERT INTO stabilimenti (descrizione) VALUES (:descrizione)",
    { descrizione }
  );

  const created = await getStabilimentoById(result.insertId);
  if (!created) {
    throw new Error("Errore nella creazione dello stabilimento.");
  }
  return created;
}

export async function updateStabilimento(
  id: number,
  input: Partial<StabilimentoInput>
): Promise<Stabilimento> {
  const existing = await getStabilimentoById(id);
  if (!existing) {
    throw new Error("Stabilimento non trovato.");
  }

  const { descrizione } = parseStabilimentoInput(input);

  await db.execute(
    "UPDATE stabilimenti SET descrizione = :descrizione WHERE id = :id",
    { descrizione, id }
  );

  const updated = await getStabilimentoById(id);
  if (!updated) {
    throw new Error("Errore nell'aggiornamento dello stabilimento.");
  }
  return updated;
}

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

  const [magRows] = await db.execute<RowDataPacket[]>(
    "SELECT COUNT(*) AS cnt FROM magazzini WHERE id_stabilimento = :id",
    { id }
  );
  if (Number(magRows[0]?.cnt) > 0) {
    throw new Error(
      "Impossibile eliminare: esistono magazzini associati. Elimina prima i magazzini."
    );
  }

  const [macRows] = await db.execute<RowDataPacket[]>(
    "SELECT COUNT(*) AS cnt FROM macchine_parti WHERE id_stabilimento = :id",
    { id }
  );
  if (Number(macRows[0]?.cnt) > 0) {
    throw new Error(
      "Impossibile eliminare: esistono macchine associate a questo stabilimento."
    );
  }

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

export async function createMagazzino(input: Partial<MagazzinoInput>): Promise<Magazzino> {
  const parsed = parseMagazzinoInput(input);

  const stabilimento = await getStabilimentoById(parsed.id_stabilimento);
  if (!stabilimento) {
    throw new Error("Stabilimento non trovato.");
  }

  const [result] = await db.execute<ResultSetHeader>(
    "INSERT INTO magazzini (descrizione, id_stabilimento) VALUES (:descrizione, :id_stabilimento)",
    {
      descrizione: parsed.descrizione,
      id_stabilimento: parsed.id_stabilimento,
    }
  );

  const created = await getMagazzinoById(result.insertId);
  if (!created) {
    throw new Error("Errore nella creazione del magazzino.");
  }
  return created;
}

export async function updateMagazzino(
  id: number,
  input: Partial<MagazzinoInput>
): Promise<Magazzino> {
  const existing = await getMagazzinoById(id);
  if (!existing) {
    throw new Error("Magazzino non trovato.");
  }

  const parsed = parseMagazzinoInput(input);

  const stabilimento = await getStabilimentoById(parsed.id_stabilimento);
  if (!stabilimento) {
    throw new Error("Stabilimento non trovato.");
  }

  await db.execute(
    "UPDATE magazzini SET descrizione = :descrizione, id_stabilimento = :id_stabilimento WHERE id = :id",
    {
      descrizione: parsed.descrizione,
      id_stabilimento: parsed.id_stabilimento,
      id,
    }
  );

  const updated = await getMagazzinoById(id);
  if (!updated) {
    throw new Error("Errore nell'aggiornamento del magazzino.");
  }
  return updated;
}

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

  const [giacRows] = await db.execute<RowDataPacket[]>(
    "SELECT COUNT(*) AS cnt FROM giacenze WHERE id_magazzino = :id",
    { id }
  );
  if (Number(giacRows[0]?.cnt) > 0) {
    throw new Error(
      "Impossibile eliminare: il magazzino contiene giacenze di articoli."
    );
  }

  const [movRows] = await db.execute<RowDataPacket[]>(
    `SELECT COUNT(*) AS cnt FROM movimenti
     WHERE id_magazzino_origine = :id OR id_magazzino_destinazione = :id`,
    { id }
  );
  if (Number(movRows[0]?.cnt) > 0) {
    throw new Error(
      "Impossibile eliminare: il magazzino è referenziato in movimenti di magazzino."
    );
  }

  const [artRows] = await db.execute<RowDataPacket[]>(
    "SELECT COUNT(*) AS cnt FROM articoli WHERE id_magazzino = :id",
    { id }
  );
  if (Number(artRows[0]?.cnt) > 0) {
    throw new Error(
      "Impossibile eliminare: articoli del catalogo sono collegati a questo magazzino."
    );
  }

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