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

import { db } from "@/src/lib/db";
import type {
  Macchina,
  MacchinaWithDetails,
  MacchinaCreateInput,
  MacchinaUpdateInput,
  TipoMacchina,
} from "@/src/types/macchina";

type MacchinaRow = Macchina & RowDataPacket;
type MacchinaWithDetailsRow = MacchinaWithDetails & RowDataPacket;

export async function listMacchine(): Promise<MacchinaWithDetails[]> {
  const [rows] = await db.execute<MacchinaWithDetailsRow[]>(`
    SELECT 
      m.id, 
      m.nome, 
      m.tipo, 
      m.id_padre, 
      m.id_stabilimento, 
      m.colore_testo,
      s.descrizione AS stabilimento_nome,
      p.nome AS padre_nome
    FROM macchine_parti m
    LEFT JOIN stabilimenti s ON s.id = m.id_stabilimento
    LEFT JOIN macchine_parti p ON p.id = m.id_padre
    ORDER BY ISNULL(m.id_padre) DESC, m.id_padre ASC, m.nome ASC
  `);

  return rows.map((row) => ({
    id: row.id,
    nome: row.nome,
    tipo: row.tipo as TipoMacchina,
    id_padre: row.id_padre,
    id_stabilimento: row.id_stabilimento,
    colore_testo: row.colore_testo || "#000000",
    stabilimento_nome: row.stabilimento_nome,
    padre_nome: row.padre_nome,
  }));
}

export async function getMacchinaById(id: number): Promise<MacchinaWithDetails | null> {
  const [rows] = await db.execute<MacchinaWithDetailsRow[]>(
    `SELECT 
      m.id, 
      m.nome, 
      m.tipo, 
      m.id_padre, 
      m.id_stabilimento, 
      m.colore_testo,
      s.descrizione AS stabilimento_nome,
      p.nome AS padre_nome
    FROM macchine_parti m
    LEFT JOIN stabilimenti s ON s.id = m.id_stabilimento
    LEFT JOIN macchine_parti p ON p.id = m.id_padre
    WHERE m.id = :id 
    LIMIT 1`,
    { id }
  );

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

  return {
    id: row.id,
    nome: row.nome,
    tipo: row.tipo as TipoMacchina,
    id_padre: row.id_padre,
    id_stabilimento: row.id_stabilimento,
    colore_testo: row.colore_testo || "#000000",
    stabilimento_nome: row.stabilimento_nome,
    padre_nome: row.padre_nome,
  };
}

export async function createMacchina(input: MacchinaCreateInput): Promise<Macchina> {
  const { nome, tipo, id_padre, id_stabilimento, colore_testo = "#000000" } = input;

  let stabilimentoId = id_stabilimento;

  if (tipo === "macchina" && !id_padre && !stabilimentoId) {
    throw new Error("Stabilimento obbligatorio per macchine di livello root");
  }

  if (id_padre && !stabilimentoId) {
    const [parentRows] = await db.execute<MacchinaRow[]>(
      "SELECT id_stabilimento FROM macchine_parti WHERE id = :id_padre LIMIT 1",
      { id_padre }
    );
    const parent = parentRows[0];
    if (parent?.id_stabilimento) {
      stabilimentoId = parent.id_stabilimento;
    }
  }

  const [result] = await db.execute<ResultSetHeader>(
    `INSERT INTO macchine_parti (nome, tipo, id_padre, id_stabilimento, colore_testo) 
     VALUES (:nome, :tipo, :id_padre, :id_stabilimento, :colore_testo)`,
    {
      nome,
      tipo,
      id_padre: id_padre || null,
      id_stabilimento: stabilimentoId || null,
      colore_testo,
    }
  );

  return {
    id: result.insertId,
    nome,
    tipo,
    id_padre: id_padre || null,
    id_stabilimento: stabilimentoId || null,
    colore_testo,
  };
}

export async function updateMacchina(
  id: number,
  input: MacchinaUpdateInput
): Promise<MacchinaWithDetails | null> {
  const current = await getMacchinaById(id);
  if (!current) return null;

  const nome = input.nome ?? current.nome;
  const tipo = input.tipo ?? current.tipo;
  const id_padre = input.id_padre !== undefined ? input.id_padre : current.id_padre;
  const colore_testo = input.colore_testo ?? current.colore_testo;

  let id_stabilimento = input.id_stabilimento !== undefined 
    ? input.id_stabilimento 
    : current.id_stabilimento;

  if (id_padre && !id_stabilimento) {
    const [parentRows] = await db.execute<MacchinaRow[]>(
      "SELECT id_stabilimento FROM macchine_parti WHERE id = :id_padre LIMIT 1",
      { id_padre }
    );
    const parent = parentRows[0];
    if (parent?.id_stabilimento) {
      id_stabilimento = parent.id_stabilimento;
    }
  }

  await db.execute(
    `UPDATE macchine_parti 
     SET nome = :nome, tipo = :tipo, id_padre = :id_padre, 
         id_stabilimento = :id_stabilimento, colore_testo = :colore_testo
     WHERE id = :id`,
    {
      id,
      nome,
      tipo,
      id_padre: id_padre || null,
      id_stabilimento: id_stabilimento || null,
      colore_testo,
    }
  );

  return getMacchinaById(id);
}

export async function deleteMacchina(
  id: number,
  idMagazzinoRientro?: number,
  user: string = "system"
): Promise<boolean> {
  // Recupera gli articoli assegnati a questa macchina
  const [articoliRows] = await db.execute<(RowDataPacket & { 
    idart: number; 
    qtain: number; 
    qtaout: number;
    descrizione: string;
    codice: string;
  })[]>(
    `SELECT idart, qtain, qtaout, descrizione, codice 
     FROM art_macchine_parti 
     WHERE idmacchina = :id AND (qtain - qtaout) > 0`,
    { id }
  );

  // Se c'è un magazzino di rientro, fai il carico degli articoli
  if (idMagazzinoRientro && articoliRows.length > 0) {
    for (const art of articoliRows) {
      const quantita = art.qtain - art.qtaout;
      if (quantita <= 0) continue;

      // Verifica se l'articolo esiste ancora nel catalogo
      const [existsRows] = await db.execute<(RowDataPacket & { id: number })[]>(
        "SELECT id FROM articoli WHERE id = :idart LIMIT 1",
        { idart: art.idart }
      );

      if (existsRows.length > 0) {
        // L'articolo esiste, registra il movimento di carico
        await db.execute(
          `INSERT INTO movimenti (id_articolo, articolo_codice, articolo_descrizione, quantita, tipo_movimento, causale, utente, id_magazzino_destinazione)
           VALUES (:id_articolo, :articolo_codice, :articolo_descrizione, :quantita, 1, :causale, :utente, :id_magazzino_destinazione)`,
          {
            id_articolo: art.idart,
            articolo_codice: art.codice,
            articolo_descrizione: art.descrizione,
            quantita,
            causale: "Rientro da eliminazione macchina",
            utente: user,
            id_magazzino_destinazione: idMagazzinoRientro,
          }
        );

        // Aggiorna la giacenza
        const [existingGiacenza] = await db.execute<(RowDataPacket & { id: number })[]>(
          "SELECT id FROM giacenze WHERE articolo_id = :articoloId AND id_magazzino = :magazzinoId",
          { articoloId: art.idart, magazzinoId: idMagazzinoRientro }
        );

        if (existingGiacenza.length > 0) {
          await db.execute(
            "UPDATE giacenze SET quantita = quantita + :quantita WHERE articolo_id = :articoloId AND id_magazzino = :magazzinoId",
            { quantita, articoloId: art.idart, magazzinoId: idMagazzinoRientro }
          );
        } else {
          await db.execute(
            "INSERT INTO giacenze (articolo_id, id_magazzino, quantita) VALUES (:articoloId, :magazzinoId, :quantita)",
            { articoloId: art.idart, magazzinoId: idMagazzinoRientro, quantita }
          );
        }
      }
      // Se l'articolo non esiste più, non facciamo nulla (gli articoli vengono persi)
    }
  }

  // Elimina gli articoli assegnati a questa macchina
  await db.execute(
    "DELETE FROM art_macchine_parti WHERE idmacchina = :id",
    { id }
  );

  // Elimina ricorsivamente i figli
  const [childRows] = await db.execute<MacchinaRow[]>(
    "SELECT id FROM macchine_parti WHERE id_padre = :id",
    { id }
  );

  if (childRows.length > 0) {
    for (const child of childRows) {
      await deleteMacchina(child.id, idMagazzinoRientro, user);
    }
  }

  const [result] = await db.execute<ResultSetHeader>(
    "DELETE FROM macchine_parti WHERE id = :id",
    { id }
  );

  return result.affectedRows > 0;
}

export async function cloneMacchina(
  id: number,
  newParentId: number | null = null
): Promise<Macchina | null> {
  const original = await getMacchinaById(id);
  if (!original) return null;

  let copyName = `${original.nome} (copia)`;
  let counter = 1;

  const [existingRows] = await db.execute<MacchinaRow[]>(
    "SELECT nome FROM macchine_parti WHERE nome LIKE :pattern",
    { pattern: `${original.nome} (copia%` }
  );

  const existingNames = new Set(existingRows.map((r) => r.nome));
  while (existingNames.has(copyName)) {
    copyName = `${original.nome} (copia ${counter})`;
    counter++;
  }

  let stabilimentoId = original.id_stabilimento;
  if (newParentId) {
    const [parentRows] = await db.execute<MacchinaRow[]>(
      "SELECT id_stabilimento FROM macchine_parti WHERE id = :id LIMIT 1",
      { id: newParentId }
    );
    if (parentRows[0]?.id_stabilimento) {
      stabilimentoId = parentRows[0].id_stabilimento;
    }
  }

  const newMacchina = await createMacchina({
    nome: copyName,
    tipo: original.tipo,
    id_padre: newParentId ?? original.id_padre,
    id_stabilimento: stabilimentoId,
    colore_testo: original.colore_testo,
  });

  const [children] = await db.execute<MacchinaRow[]>(
    "SELECT id FROM macchine_parti WHERE id_padre = :id",
    { id }
  );

  for (const child of children) {
    await cloneMacchina(child.id, newMacchina.id);
  }

  return newMacchina;
}

export async function getChildrenCount(id: number): Promise<number> {
  const [rows] = await db.execute<(RowDataPacket & { count: number })[]>(
    "SELECT COUNT(*) as count FROM macchine_parti WHERE id_padre = :id",
    { id }
  );
  return rows[0]?.count ?? 0;
}

export async function getAllDescendantIds(id: number): Promise<number[]> {
  const ids: number[] = [];
  
  const [children] = await db.execute<MacchinaRow[]>(
    "SELECT id FROM macchine_parti WHERE id_padre = :id",
    { id }
  );

  for (const child of children) {
    ids.push(child.id);
    const descendantIds = await getAllDescendantIds(child.id);
    ids.push(...descendantIds);
  }

  return ids;
}
