import { Router } from "express";
import { z } from "zod";
import { prisma } from "../prisma.js";
import { requireAuth } from "../middleware/auth.js";
import { HttpError } from "../lib/http-error.js";

export const inventoryRouter = Router();
inventoryRouter.use(requireAuth);

// ─── PIEZAS ──────────────────────────────────────────────────────────────────
 
inventoryRouter.get("/piezas", async (_req, res, next) => {
  try {
    const piezas = await prisma.piezas.findMany({ orderBy: { nombre: "asc" } });
    res.json({ piezas });
  } catch (err) {
    next(err);
  }
});

const piezaBodySchema = z.object({
  nombre: z.string().trim().min(1, "Nombre requerido").max(120),
  descripcion: z.preprocess(
    (v) => (v === "" ? null : v),
    z.string().trim().max(300).nullable().optional()
  ),
  stock_actual: z.coerce.number().int().min(0).optional(),
});

inventoryRouter.post("/piezas", async (req, res, next) => {
  try {
    const body = piezaBodySchema.parse(req.body);
    const pieza = await prisma.piezas.create({
      data: {
        nombre: body.nombre,
        descripcion: body.descripcion ?? null,
        stock_actual: body.stock_actual ?? 0,
      },
    });
    res.status(201).json({ pieza });
  } catch (err) {
    next(err);
  }
});

inventoryRouter.patch("/piezas/:id", async (req, res, next) => {
  try {
    const id = z.coerce.number().int().positive().parse(req.params.id);
    const body = piezaBodySchema.partial().parse(req.body);
    const existing = await prisma.piezas.findUnique({ where: { id } });
    if (!existing) throw new HttpError(404, "Pieza no encontrada");
    const pieza = await prisma.piezas.update({ where: { id }, data: body });
    res.json({ pieza });
  } catch (err) {
    next(err);
  }
});

inventoryRouter.delete("/piezas/:id", async (req, res, next) => {
  try {
    const id = z.coerce.number().int().positive().parse(req.params.id);
    const pieza = await prisma.piezas.findUnique({
      where: { id },
      include: { componentes_kit: true },
    });
    if (!pieza) throw new HttpError(404, "Pieza no encontrada");
    if (pieza.stock_actual > 0) {
      throw new HttpError(
        409,
        `No se puede eliminar: la pieza tiene ${pieza.stock_actual} unidades en stock`
      );
    }
    if (pieza.componentes_kit.length > 0) {
      const categorias = pieza.componentes_kit
        .map((c) => c.categoria_codigo)
        .join(", ");
      throw new HttpError(
        409,
        `No se puede eliminar: está asignada a los kits de ${categorias}`
      );
    }
    await prisma.piezas.delete({ where: { id } });
    res.json({ ok: true });
  } catch (err) {
    next(err);
  }
});

// ─── CATEGORÍAS (para selects) ────────────────────────────────────────────────

inventoryRouter.get("/categorias", async (_req, res, next) => {
  try {
    const categorias = await prisma.categorias_competencia.findMany({
      orderBy: { nombre_display: "asc" },
      select: { codigo: true, nombre_display: true },
    });
    res.json({ categorias });
  } catch (err) {
    next(err);
  }
});

// ─── COMPONENTES POR CATEGORÍA ────────────────────────────────────────────────

inventoryRouter.get("/componentes", async (_req, res, next) => {
  try {
    const categorias = await prisma.categorias_competencia.findMany({
      orderBy: { nombre_display: "asc" },
      include: {
        componentes_kit: {
          include: {
            piezas: { select: { id: true, nombre: true, stock_actual: true } },
          },
        },
      },
    });
    // Sort components by piece name in JS (simpler than nested Prisma orderBy)
    for (const cat of categorias) {
      cat.componentes_kit.sort((a, b) =>
        a.piezas.nombre.localeCompare(b.piezas.nombre)
      );
    }
    res.json({ categorias });
  } catch (err) {
    next(err);
  }
});

const componenteBodySchema = z.object({
  categoria_codigo: z.string().trim().min(1),
  pieza_id: z.coerce.number().int().positive(),
  cantidad_por_kit: z.coerce.number().int().min(1, "La cantidad debe ser al menos 1"),
});

inventoryRouter.put("/componentes", async (req, res, next) => {
  try {
    const body = componenteBodySchema.parse(req.body);
    const componente = await prisma.componentes_kit.upsert({
      where: {
        categoria_codigo_pieza_id: {
          categoria_codigo: body.categoria_codigo,
          pieza_id: body.pieza_id,
        },
      },
      create: {
        categoria_codigo: body.categoria_codigo,
        pieza_id: body.pieza_id,
        cantidad_por_kit: body.cantidad_por_kit,
      },
      update: { cantidad_por_kit: body.cantidad_por_kit },
      include: {
        piezas: { select: { id: true, nombre: true, stock_actual: true } },
      },
    });
    res.json({ componente });
  } catch (err) {
    next(err);
  }
});

inventoryRouter.delete("/componentes/:id", async (req, res, next) => {
  try {
    const id = z.coerce.number().int().positive().parse(req.params.id);
    const existing = await prisma.componentes_kit.findUnique({ where: { id } });
    if (!existing) throw new HttpError(404, "Componente no encontrado");
    await prisma.componentes_kit.delete({ where: { id } });
    res.json({ ok: true });
  } catch (err) {
    next(err);
  }
});

// ─── KITS ARMADOS ─────────────────────────────────────────────────────────────

inventoryRouter.get("/kits-armados", async (_req, res, next) => {
  try {
    const stock = await prisma.stock_kits_armados.findMany({
      include: {
        categorias_competencia: { select: { codigo: true, nombre_display: true } },
      },
      orderBy: { categorias_competencia: { nombre_display: "asc" } },
    });
    res.json({ stock });
  } catch (err) {
    next(err);
  }
});

inventoryRouter.post("/kits-armados", async (req, res, next) => {
  try {
    const body = z
      .object({
        categoria_codigo: z.string().trim().min(1),
        cantidad: z.coerce.number().int().min(1).max(1000),
      })
      .parse(req.body);

    const componentes = await prisma.componentes_kit.findMany({
      where: { categoria_codigo: body.categoria_codigo },
      include: { piezas: true },
    });

    if (componentes.length === 0) {
      throw new HttpError(
        400,
        "Esta categoría no tiene componentes configurados"
      );
    }

    const insuficientes: string[] = [];
    for (const c of componentes) {
      const needed = c.cantidad_por_kit * body.cantidad;
      if (c.piezas.stock_actual < needed) {
        insuficientes.push(
          `${c.piezas.nombre}: necesitas ${needed}, hay ${c.piezas.stock_actual}`
        );
      }
    }
    if (insuficientes.length > 0) {
      throw new HttpError(
        409,
        `Stock insuficiente: ${insuficientes.join(" | ")}`
      );
    }

    await prisma.$transaction(async (tx) => {
      for (const c of componentes) {
        await tx.piezas.update({
          where: { id: c.pieza_id },
          data: { stock_actual: { decrement: c.cantidad_por_kit * body.cantidad } },
        });
      }
      await tx.stock_kits_armados.upsert({
        where: { categoria_codigo: body.categoria_codigo },
        create: {
          categoria_codigo: body.categoria_codigo,
          cantidad_armados: body.cantidad,
        },
        update: { cantidad_armados: { increment: body.cantidad } },
      });
    });

    res.json({ ok: true });
  } catch (err) {
    next(err);
  }
});

inventoryRouter.patch("/kits-armados/:categoria", async (req, res, next) => {
  try {
    const categoria = z.string().trim().min(1).parse(req.params.categoria);
    const { cantidad } = z
      .object({ cantidad: z.coerce.number().int().min(0).max(10000) })
      .parse(req.body);

    const current = await prisma.stock_kits_armados.findUnique({
      where: { categoria_codigo: categoria },
    });
    const currentQty = Number(current?.cantidad_armados ?? 0);
    const delta = cantidad - currentQty;

    if (delta !== 0) {
      const componentes = await prisma.componentes_kit.findMany({
        where: { categoria_codigo: categoria },
        include: { piezas: true },
      });

      if (delta > 0) {
        if (componentes.length === 0) {
          throw new HttpError(400, "Esta categoría no tiene componentes configurados");
        }
        const insuficientes: string[] = [];
        for (const c of componentes) {
          const needed = c.cantidad_por_kit * delta;
          if (c.piezas.stock_actual < needed) {
            insuficientes.push(
              `${c.piezas.nombre}: necesitas ${needed}, hay ${c.piezas.stock_actual}`
            );
          }
        }
        if (insuficientes.length > 0) {
          throw new HttpError(409, `Stock insuficiente: ${insuficientes.join(" | ")}`);
        }
      }

      await prisma.$transaction(async (tx) => {
        for (const c of componentes) {
          const change = c.cantidad_por_kit * Math.abs(delta);
          await tx.piezas.update({
            where: { id: c.pieza_id },
            data: {
              stock_actual: delta > 0
                ? { decrement: change }
                : { increment: change },
            },
          });
        }
        await tx.stock_kits_armados.upsert({
          where: { categoria_codigo: categoria },
          create: { categoria_codigo: categoria, cantidad_armados: cantidad },
          update: { cantidad_armados: cantidad },
        });
      });
    }

    res.json({ ok: true });
  } catch (err) {
    next(err);
  }
});

// ─── PLAYERAS ─────────────────────────────────────────────────────────────────

inventoryRouter.get("/playeras/stats", async (_req, res, next) => {
  try {
    type StatsRow = {
      talla: string;
      stock_actual: number;
      reservadas_pendientes: bigint;
      ya_empacadas: bigint;
    };
    const rows = await prisma.$queryRaw<StatsRow[]>`
      SELECT
        ps.talla,
        ps.stock_actual,
        COALESCE((
          SELECT COUNT(*) FROM crea_inscripciones ci
          LEFT JOIN kits k ON k.id = ci.kit_id
          WHERE ci.talla_playera = ps.talla
            AND (k.estado IS NULL OR k.estado = 'asignado')
        ), 0) AS reservadas_pendientes,
        COALESCE((
          SELECT COUNT(*) FROM crea_inscripciones ci
          JOIN kits k ON k.id = ci.kit_id
          WHERE ci.talla_playera = ps.talla
            AND k.estado IN ('empacado','enviado','recibido','recogido_en_bootcamp')
        ), 0) AS ya_empacadas
      FROM playeras_stock ps
      ORDER BY FIELD(ps.talla, '14','16','XS','S','M','L','XL','XXL')
    `;
    const stats = rows.map((r) => ({
      talla: r.talla,
      stock_actual: r.stock_actual,
      reservadas_pendientes: Number(r.reservadas_pendientes),
      ya_empacadas: Number(r.ya_empacadas),
    }));
    res.json({ stats });
  } catch (err) {
    next(err);
  }
});

inventoryRouter.get("/playeras", async (_req, res, next) => {
  try {
    const TALLA_ORDER = ["14", "16", "XS", "S", "M", "L", "XL", "XXL"];
    const playeras = await prisma.playeras_stock.findMany();
    playeras.sort((a, b) => {
      const ai = TALLA_ORDER.indexOf(a.talla);
      const bi = TALLA_ORDER.indexOf(b.talla);
      if (ai === -1 && bi === -1) return a.talla.localeCompare(b.talla);
      if (ai === -1) return 1;
      if (bi === -1) return -1;
      return ai - bi;
    });
    res.json({ playeras });
  } catch (err) {
    next(err);
  }
});

inventoryRouter.patch("/playeras/:talla", async (req, res, next) => {
  try {
    const talla = z.string().trim().min(1).max(10).parse(req.params.talla);
    const { stock_actual } = z
      .object({ stock_actual: z.coerce.number().int().min(0) })
      .parse(req.body);
    const existing = await prisma.playeras_stock.findUnique({
      where: { talla },
    });
    if (!existing) throw new HttpError(404, "Talla no encontrada");
    const updated = await prisma.playeras_stock.update({
      where: { talla },
      data: { stock_actual },
    });
    res.json({ playera: updated });
  } catch (err) {
    next(err);
  }
});

// ─── MERMAS ───────────────────────────────────────────────────────────────────

inventoryRouter.get("/mermas", async (_req, res, next) => {
  try {
    const mermas = await prisma.mermas.findMany({
      include: {
        piezas: { select: { id: true, nombre: true } },
      },
      orderBy: { registrado_en: "desc" },
      take: 50,
    });
    res.json({ mermas });
  } catch (err) {
    next(err);
  }
});

const mermaBodySchema = z
  .object({
    pieza_id: z.coerce.number().int().positive().optional().nullable(),
    talla_playera: z.string().trim().max(10).optional().nullable(),
    cantidad: z.coerce.number().int().min(1),
    motivo: z.preprocess(
      (v) => (v === "" ? null : v),
      z.string().trim().max(300).nullable().optional()
    ),
    registrado_por: z.preprocess(
      (v) => (v === "" ? null : v),
      z.string().trim().max(120).nullable().optional()
    ),
  })
  .refine(
    (d) =>
      (d.pieza_id != null && d.talla_playera == null) ||
      (d.pieza_id == null && d.talla_playera != null),
    { message: "Debes indicar pieza o playera, no ambas ni ninguna" }
  );

inventoryRouter.post("/mermas", async (req, res, next) => {
  try {
    const body = mermaBodySchema.parse(req.body);

    await prisma.$transaction(async (tx) => {
      if (body.pieza_id != null) {
        const pieza = await tx.piezas.findUnique({
          where: { id: body.pieza_id },
        });
        if (!pieza) throw new HttpError(404, "Pieza no encontrada");
        if (pieza.stock_actual < body.cantidad) {
          throw new HttpError(
            409,
            `Stock insuficiente: hay ${pieza.stock_actual}, merma solicitada ${body.cantidad}`
          );
        }
        await tx.piezas.update({
          where: { id: body.pieza_id },
          data: { stock_actual: { decrement: body.cantidad } },
        });
      } else if (body.talla_playera != null) {
        const playera = await tx.playeras_stock.findUnique({
          where: { talla: body.talla_playera },
        });
        if (!playera) throw new HttpError(404, "Talla no encontrada");
        if (playera.stock_actual < body.cantidad) {
          throw new HttpError(
            409,
            `Stock insuficiente: hay ${playera.stock_actual}, merma solicitada ${body.cantidad}`
          );
        }
        await tx.playeras_stock.update({
          where: { talla: body.talla_playera },
          data: { stock_actual: { decrement: body.cantidad } },
        });
      }

      await tx.mermas.create({
        data: {
          pieza_id: body.pieza_id ?? null,
          talla_playera: body.talla_playera ?? null,
          cantidad: body.cantidad,
          motivo: body.motivo ?? null,
          registrado_por: body.registrado_por ?? null,
        },
      });
    });

    res.status(201).json({ ok: true });
  } catch (err) {
    next(err);
  }
});

// ─── EXPORTS CSV ──────────────────────────────────────────────────────────────

function csvEscape(v: unknown): string {
  if (v === null || v === undefined) return "";
  const s = v instanceof Date ? v.toISOString().slice(0, 10) : String(v);
  if (/[",\n\r]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
  return s;
}

function buildCsv(headers: string[], rows: unknown[][]): string {
  const lines = [
    headers.join(","),
    ...rows.map((r) => r.map(csvEscape).join(",")),
  ];
  return "﻿" + lines.join("\r\n") + "\r\n";
}

inventoryRouter.get("/piezas/export", async (_req, res, next) => {
  try {
    const piezas = await prisma.piezas.findMany({ orderBy: { nombre: "asc" } });
    const headers = ["id", "nombre", "descripcion", "stock_actual"];
    const rows = piezas.map((p) => [p.id, p.nombre, p.descripcion, p.stock_actual]);
    const csv = buildCsv(headers, rows);
    const filename = `piezas_${new Date().toISOString().slice(0, 10)}.csv`;
    res.setHeader("Content-Type", "text/csv; charset=utf-8");
    res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
    res.send(csv);
  } catch (err) {
    next(err);
  }
});

inventoryRouter.get("/componentes/export", async (req, res, next) => {
  try {
    const { categoria_codigo } = z
      .object({ categoria_codigo: z.string().trim().min(1).optional() })
      .parse(req.query);

    const categorias = await prisma.categorias_competencia.findMany({
      where: categoria_codigo ? { codigo: categoria_codigo } : undefined,
      orderBy: { nombre_display: "asc" },
      include: {
        componentes_kit: {
          include: {
            piezas: { select: { id: true, nombre: true, stock_actual: true } },
          },
          orderBy: { piezas: { nombre: "asc" } },
        },
      },
    });

    const headers = ["categoria_codigo", "categoria", "pieza_id", "pieza", "stock_pieza", "cantidad_por_kit"];
    const rows: unknown[][] = [];
    for (const cat of categorias) {
      for (const c of cat.componentes_kit) {
        rows.push([
          cat.codigo,
          cat.nombre_display,
          c.piezas.id,
          c.piezas.nombre,
          c.piezas.stock_actual,
          c.cantidad_por_kit,
        ]);
      }
    }

    const csv = buildCsv(headers, rows);
    const filename = `componentes_${categoria_codigo ?? "todos"}_${new Date().toISOString().slice(0, 10)}.csv`;
    res.setHeader("Content-Type", "text/csv; charset=utf-8");
    res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
    res.send(csv);
  } catch (err) {
    next(err);
  }
});

inventoryRouter.get("/kits-armados/export", async (_req, res, next) => {
  try {
    const stock = await prisma.stock_kits_armados.findMany({
      include: {
        categorias_competencia: { select: { codigo: true, nombre_display: true } },
      },
      orderBy: { categorias_competencia: { nombre_display: "asc" } },
    });

    const headers = ["categoria_codigo", "categoria", "cantidad_armados"];
    const rows = stock.map((s) => [
      s.categoria_codigo,
      s.categorias_competencia.nombre_display,
      s.cantidad_armados,
    ]);

    const csv = buildCsv(headers, rows);
    const filename = `kits_armados_${new Date().toISOString().slice(0, 10)}.csv`;
    res.setHeader("Content-Type", "text/csv; charset=utf-8");
    res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
    res.send(csv);
  } catch (err) {
    next(err);
  }
});
