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

import { db } from "@/src/lib/db";
import type { Article, ArticleFamily, ArticleInput } from "@/src/types/article";

type ArticleRow = Omit<Article, "prezzo" | "quantita_minima" | "quantita" | "assegnata_macchine"> &
  RowDataPacket & {
    prezzo: string | number | null;
    quantita_minima: string | number | null;
    quantita: string | number | null;
    assegnata_macchine: string | number | null;
  };

type FamilyRow = ArticleFamily & RowDataPacket;

function toNumber(value: string | number | null | undefined): number {
  const parsed = Number(value ?? 0);
  return Number.isFinite(parsed) ? parsed : 0;
}

function normalizeArticle(row: ArticleRow): Article {
  return {
    id: row.id,
    codice: row.codice,
    descrizione: row.descrizione,
    barcode: row.barcode,
    id_famiglia: row.id_famiglia,
    famiglia: row.famiglia,
    id_magazzino: row.id_magazzino,
    prezzo: toNumber(row.prezzo),
    quantita_minima: toNumber(row.quantita_minima),
    quantita: toNumber(row.quantita),
    assegnata_macchine: toNumber(row.assegnata_macchine),
  };
}

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

function nullableNumber(value: unknown): number | null {
  if (value === null || value === undefined || value === "") {
    return null;
  }

  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : null;
}

function positiveNumber(value: unknown, fallback = 0): number {
  const parsed = Number(value ?? fallback);
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
}

function createCode(): string {
  return `ART-${crypto.randomUUID().slice(0, 6)}`;
}

interface ParsedArticle {
  codice: string;
  descrizione: string;
  barcode: string | null;
  id_famiglia: number | null;
  id_magazzino: number | null;
  prezzo: number;
  quantita_minima: number;
  quantita: number;
}

function parseArticleInput(input: Partial<ArticleInput>): ParsedArticle {
  const descrizione = cleanText(input.descrizione);

  if (!descrizione) {
    throw new Error("La descrizione dell'articolo e obbligatoria.");
  }

  return {
    codice: cleanText(input.codice) || createCode(),
    descrizione,
    barcode: cleanText(input.barcode) || null,
    id_famiglia: nullableNumber(input.id_famiglia),
    id_magazzino: nullableNumber(input.id_magazzino),
    prezzo: positiveNumber(input.prezzo),
    quantita_minima: positiveNumber(input.quantita_minima),
    quantita: positiveNumber(input.quantita),
  };
}

export interface ListArticlesOptions {
  famigliaId?: number;
  search?: string;
}

export async function listArticles(options: ListArticlesOptions = {}): Promise<Article[]> {
  const { famigliaId, search } = options;

  const params: Record<string, string | number> = {};
  const conditions: string[] = [];

  let query = `
    SELECT
      a.id,
      a.codice,
      a.descrizione,
      a.barcode,
      a.id_famiglia,
      f.nome AS famiglia,
      a.id_magazzino,
      a.prezzo,
      a.quantita_minima,
      COALESCE(SUM(g.quantita), 0) AS quantita,
      COALESCE(machine_totals.quantita, 0) AS assegnata_macchine
    FROM articoli a
    LEFT JOIN famiglie f ON f.id = a.id_famiglia
    LEFT JOIN giacenze g ON g.articolo_id = a.id
    LEFT JOIN (
      SELECT idart, SUM(qtain - qtaout) AS quantita
      FROM art_macchine_parti
      GROUP BY idart
    ) machine_totals ON machine_totals.idart = a.id
  `;

  if (famigliaId) {
    conditions.push("a.id_famiglia = :famigliaId");
    params.famigliaId = famigliaId;
  }

  if (search) {
    conditions.push("(a.descrizione LIKE :search OR a.codice LIKE :search OR a.barcode LIKE :search)");
    params.search = `%${search}%`;
  }

  if (conditions.length > 0) {
    query += " WHERE " + conditions.join(" AND ");
  }

  query += `
    GROUP BY
      a.id,
      a.codice,
      a.descrizione,
      a.barcode,
      a.id_famiglia,
      f.nome,
      a.id_magazzino,
      a.prezzo,
      a.quantita_minima,
      machine_totals.quantita
    ORDER BY a.descrizione ASC
  `;

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

  return rows.map(normalizeArticle);
}

export async function listFamilies(): Promise<ArticleFamily[]> {
  const [rows] = await db.execute<FamilyRow[]>(
    "SELECT id, nome FROM famiglie ORDER BY nome ASC",
  );

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

export async function createArticle(input: Partial<ArticleInput>): Promise<Article> {
  const article = parseArticleInput(input);
  const connection = await db.getConnection();

  try {
    await connection.beginTransaction();

    const [result] = await connection.execute<ResultSetHeader>(
      `INSERT INTO articoli
       (codice, descrizione, barcode, id_famiglia, id_magazzino, prezzo, quantita_minima)
       VALUES (:codice, :descrizione, :barcode, :id_famiglia, :id_magazzino, :prezzo, :quantita_minima)`,
      {
        codice: article.codice,
        descrizione: article.descrizione,
        barcode: article.barcode,
        id_famiglia: article.id_famiglia,
        id_magazzino: article.id_magazzino,
        prezzo: article.prezzo,
        quantita_minima: article.quantita_minima,
      },
    );

    if (article.id_magazzino && article.quantita && article.quantita > 0) {
      await connection.execute<ResultSetHeader>(
        `INSERT INTO giacenze (articolo_id, id_magazzino, quantita)
         VALUES (:articoloId, :idMagazzino, :quantita)
         ON DUPLICATE KEY UPDATE quantita = quantita + VALUES(quantita)`,
        {
          articoloId: result.insertId,
          idMagazzino: article.id_magazzino,
          quantita: article.quantita,
        },
      );
    }

    await connection.commit();
    return getArticleById(result.insertId);
  } catch (error) {
    await connection.rollback();
    throw error;
  } finally {
    connection.release();
  }
}

export async function updateArticle(id: number, input: Partial<ArticleInput>): Promise<Article> {
  const article = parseArticleInput(input);

  await db.execute<ResultSetHeader>(
    `UPDATE articoli
     SET codice = :codice,
         descrizione = :descrizione,
         barcode = :barcode,
         id_famiglia = :id_famiglia,
         id_magazzino = :id_magazzino,
         prezzo = :prezzo,
         quantita_minima = :quantita_minima
     WHERE id = :id`,
    {
      codice: article.codice,
      descrizione: article.descrizione,
      barcode: article.barcode,
      id_famiglia: article.id_famiglia,
      id_magazzino: article.id_magazzino,
      prezzo: article.prezzo,
      quantita_minima: article.quantita_minima,
      id,
    },
  );

  return getArticleById(id);
}

export async function deleteArticle(id: number): Promise<void> {
  await db.execute<ResultSetHeader>("DELETE FROM articoli WHERE id = :id", { id });
}

export async function getArticleById(id: number): Promise<Article> {
  const [rows] = await db.execute<ArticleRow[]>(
    `SELECT
       a.id,
       a.codice,
       a.descrizione,
       a.barcode,
       a.id_famiglia,
       f.nome AS famiglia,
       a.id_magazzino,
       a.prezzo,
       a.quantita_minima,
       COALESCE(SUM(g.quantita), 0) AS quantita,
       COALESCE(machine_totals.quantita, 0) AS assegnata_macchine
     FROM articoli a
     LEFT JOIN famiglie f ON f.id = a.id_famiglia
     LEFT JOIN giacenze g ON g.articolo_id = a.id
     LEFT JOIN (
       SELECT idart, SUM(qtain - qtaout) AS quantita
       FROM art_macchine_parti
       GROUP BY idart
     ) machine_totals ON machine_totals.idart = a.id
     WHERE a.id = :id
     GROUP BY
       a.id,
       a.codice,
       a.descrizione,
       a.barcode,
       a.id_famiglia,
       f.nome,
       a.id_magazzino,
       a.prezzo,
       a.quantita_minima,
       machine_totals.quantita
     LIMIT 1`,
    { id },
  );

  const row = rows[0];

  if (!row) {
    throw new Error("Articolo non trovato.");
  }

  return normalizeArticle(row);
}

