"use client";

import { useCallback, useEffect, useRef, useState } from "react";
import { api, type ConsumosRealesDeOrden, type LineaConsumoReal, type Orden, type ReglaConsumo } from "@/lib/api";
import { admiteCaptura, muestraEjecucion, reglaAEnviar } from "@/lib/costos-reglas";
import { usePuede } from "@/components/sesion-proveedor";
import { leerNombre } from "@/lib/producto";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { toast } from "sonner";

const numero = new Intl.NumberFormat("es-GT", { maximumFractionDigits: 4 });

/** Una diferencia con signo: positivo = se gastó de más. */
function conSigno(v: number | null): string {
  if (null === v) return "—";
  return (v > 0 ? "+" : "") + numero.format(v);
}

/**
 * La ejecución de la orden (P3 y P10 de #24981): lo que de verdad se produjo y lo que de verdad se gastó, capturado con la
 * materia prima ya despachada y ANTES de registrar la producción. Guardar no mueve inventario y se puede corregir las veces
 * que haga falta; el cierre usa lo capturado.
 *
 * Con la orden ya producida la tarjeta queda de sólo lectura y deja ver las desviaciones: por uso (se gastó de más o de
 * menos que lo que correspondía con lo producido) y por rendimiento (lo que correspondía contra lo planificado).
 */
export function EjecucionOrden({ orden, onCambio }: { orden: Orden; onCambio: () => void }) {
  const puedeVer = usePuede("ver");
  const puedeCapturar = usePuede("registrar_produccion");
  const editable = admiteCaptura(orden.estado) && puedeCapturar;
  // ANULADA incluida, de sólo lectura: anular no borra lo capturado y es parte de la auditoría de la orden.
  const visible = muestraEjecucion(orden.estado);

  const [datos, setDatos] = useState<ConsumosRealesDeOrden | null>(null);
  const [falloLectura, setFalloLectura] = useState(false);
  const [trabajando, setTrabajando] = useState(false);
  // Lo que se está escribiendo: por «producto|componente» el real y la regla; por producto, las unidades producidas.
  const [reales, setReales] = useState<Record<string, string>>({});
  const [reglas, setReglas] = useState<Record<string, ReglaConsumo>>({});
  // Renglones que la persona ya tocó y no se guardaron: una relectura (p. ej. al cambiar el estado de la orden
  // por guardar la producción) no debe pisarlos con lo que todavía dice el servidor.
  const sucias = useRef<Set<string>>(new Set());
  const [producidas, setProducidas] = useState<Record<string, string>>(() =>
    Object.fromEntries(orden.detalles.map((d) => [d.plu, "EN_EJECUCION" === orden.estado ? String(d.cantidadProducida) : ""])),
  );

  const cargar = useCallback(async () => {
    if (!visible || !puedeVer) return;
    try {
      const d = await api<ConsumosRealesDeOrden>(`/api/ordenes/${orden.id}/consumos`);
      setDatos(d);
      setFalloLectura(false);
      setReales((p) => Object.fromEntries(d.formula.map((l) => [clave(l), sucias.current.has(clave(l)) && clave(l) in p ? p[clave(l)] : null === l.real ? "" : String(l.real)])));
      setReglas((p) => Object.fromEntries(d.formula.map((l) => [clave(l), sucias.current.has(clave(l)) && clave(l) in p ? p[clave(l)] : l.regla])));
    } catch (error) {
      setFalloLectura(true);
      toast.error(error instanceof Error ? error.message : "No se pudo leer el consumo real.");
    }
  }, [orden.id, orden.estado, visible, puedeVer]);

  useEffect(() => {
    void cargar();
  }, [cargar]);

  if (!visible || !puedeVer) return null;

  /** Un renglón queda «sucio» sólo mientras difiere de lo que dice el servidor: si se devuelve al valor original, se limpia. */
  function marcar(l: LineaConsumoReal, real: string, regla: ReglaConsumo) {
    const igual = real === (null === l.real ? "" : String(l.real)) && regla === l.regla;
    if (igual) sucias.current.delete(clave(l));
    else sucias.current.add(clave(l));
  }

  async function guardarProduccion() {
    setTrabajando(true);
    try {
      await api(`/api/ordenes/${orden.id}/ejecucion`, {
        metodo: "PUT",
        cuerpo: {
          producidas: orden.detalles
            .filter((d) => "" !== (producidas[d.plu] ?? "").trim())
            .map((d) => ({ plu: d.plu, cantidad: Number(producidas[d.plu]) })),
        },
      });
      toast.success("Producción real guardada. No movió inventario.");
      onCambio();
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "No se pudo guardar la producción.", { duration: 10000 });
    } finally {
      setTrabajando(false);
    }
  }

  async function guardarConsumos() {
    if (null === datos) return;
    // Sólo lo que cambió: reenviar una línea intacta le cambiaría quién y cuándo la capturó.
    const cambiadas = datos.formula.filter(
      (l) => (reales[clave(l)] ?? "").trim() !== (null === l.real ? "" : String(l.real)) || (reglas[clave(l)] ?? l.regla) !== l.regla,
    );
    if (0 === cambiadas.length) {
      toast.info("No hay cambios que guardar.");
      return;
    }
    setTrabajando(true);
    try {
      const respuesta = await api<ConsumosRealesDeOrden>(`/api/ordenes/${orden.id}/consumos`, {
        metodo: "PUT",
        cuerpo: {
          consumos: cambiadas.map((l) => {
            const texto = (reales[clave(l)] ?? "").trim();
            return {
              pluProducto: l.pluProducto,
              pluComponente: l.pluComponente,
              // Sólo si cambió: el servidor rechaza FIJO_LOTE como valor entrante.
              regla: reglaAEnviar(l.regla, reglas[clave(l)]),
              // Vacío = «no se capturó»: el servidor lo guarda como null y el cierre usa lo esperado.
              real: "" === texto ? null : Number(texto),
            };
          }),
        },
      });
      setDatos(respuesta);
      sucias.current.clear();
      setReales(Object.fromEntries(respuesta.formula.map((l) => [clave(l), null === l.real ? "" : String(l.real)])));
      setReglas(Object.fromEntries(respuesta.formula.map((l) => [clave(l), l.regla])));
      toast.success("Consumo real guardado. No movió inventario.");
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "No se pudo guardar el consumo real.", { duration: 10000 });
    } finally {
      setTrabajando(false);
    }
  }

  return (
    <Card className="min-w-0">
      <CardHeader>
        <CardTitle className="text-base">Ejecución: lo producido y lo gastado de verdad</CardTitle>
        <CardDescription>
          {editable
            ? "Se captura con la materia prima despachada y antes de registrar la producción. Guardar no mueve inventario y se puede corregir; el cierre usa lo capturado."
            : "Lo que se capturó antes de cerrar, contra lo esperado."}
        </CardDescription>
      </CardHeader>
      <CardContent className="space-y-5">
        {editable ? (
          <div className="space-y-3">
            <div className="grid gap-3 sm:grid-cols-2">
              {orden.detalles.map((d) => (
                <div key={d.plu} className="space-y-1">
                  <Label htmlFor={`ej-${d.plu}`}>{leerNombre(d.descripcion ?? d.plu).nombre}: unidades producidas</Label>
                  <Input
                    id={`ej-${d.plu}`}
                    type="number"
                    min={0}
                    step="any"
                    value={producidas[d.plu] ?? ""}
                    onChange={(e) => setProducidas((p) => ({ ...p, [d.plu]: e.target.value }))}
                  />
                  <p className="text-xs text-muted-foreground">Planificado: {numero.format(d.cantidadPlanificada)}</p>
                </div>
              ))}
            </div>
            <Button size="sm" variant="outline" disabled={trabajando || orden.detalles.every((d) => "" === (producidas[d.plu] ?? "").trim())} onClick={() => void guardarProduccion()}>
              Guardar producción real
            </Button>
          </div>
        ) : null}

        {falloLectura ? (
          <p className="text-sm text-amber-600">No se pudo leer el consumo real: no se sabe qué hay capturado. Recargue la página.</p>
        ) : null === datos ? (
          <p className="text-sm text-muted-foreground">Cargando…</p>
        ) : 0 === datos.formula.length ? (
          <p className="text-sm text-muted-foreground">Esta orden no tiene materiales de fórmula (por ejemplo un reempaque).</p>
        ) : (
          <>
            <Table>
              <TableHeader>
                <TableRow>
                  <TableHead>Material</TableHead>
                  <TableHead className="text-right">Esperado (planificado)</TableHead>
                  <TableHead className="text-right">Esperado (con lo producido)</TableHead>
                  <TableHead className="text-right">Real</TableHead>
                  <TableHead>Regla</TableHead>
                  <TableHead className="text-right">Desviación por rendimiento</TableHead>
                  <TableHead className="text-right">Desviación por uso</TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {datos.formula.map((l) => (
                  <TableRow key={clave(l)}>
                    <TableCell>
                      <div className="font-medium">{l.pluComponente}</div>
                      <div className="text-xs text-muted-foreground">{l.descripcion}</div>
                      {orden.detalles.length > 1 ? (
                        <div className="text-xs text-muted-foreground">
                          Producto: {leerNombre(orden.detalles.find((d) => d.plu === l.pluProducto)?.descripcion ?? l.pluProducto).nombre}
                        </div>
                      ) : null}
                    </TableCell>
                    <TableCell className="text-right tabular-nums">{numero.format(l.esperadoPlanificado)}</TableCell>
                    <TableCell className="text-right tabular-nums">{null === l.esperadoSegunProduccion ? "—" : numero.format(l.esperadoSegunProduccion)}</TableCell>
                    <TableCell className="text-right">
                      {editable ? (
                        <Input
                          aria-label={`Real de ${l.pluComponente}`}
                          className="ml-auto w-28 text-right"
                          type="number"
                          min={0}
                          step="any"
                          value={reales[clave(l)] ?? ""}
                          placeholder="sin capturar"
                          onChange={(e) => {
                            marcar(l, e.target.value, reglas[clave(l)] ?? l.regla);
                            setReales((p) => ({ ...p, [clave(l)]: e.target.value }));
                          }}
                        />
                      ) : (
                        <span className="tabular-nums">{null === l.real ? "—" : numero.format(l.real)}</span>
                      )}
                    </TableCell>
                    <TableCell>
                      {editable ? (
                        <select
                          aria-label={`Regla de ${l.pluComponente}`}
                          className="h-9 rounded-md border bg-transparent px-2 text-sm"
                          value={reglas[clave(l)] ?? l.regla}
                          onChange={(e) => {
                            marcar(l, reales[clave(l)] ?? "", e.target.value as ReglaConsumo);
                            setReglas((p) => ({ ...p, [clave(l)]: e.target.value as ReglaConsumo }));
                          }}
                        >
                          <option value="PROPORCIONAL">Proporcional a lo producido</option>
                          <option value="CAPTURADO">Lo capturado manda</option>
                          {"FIJO_LOTE" === l.regla ? <option value="FIJO_LOTE" disabled>Fijo por lote</option> : null}
                        </select>
                      ) : (
                        <span className="text-sm">{"CAPTURADO" === l.regla ? "Lo capturado manda" : "FIJO_LOTE" === l.regla ? "Fijo por lote" : "Proporcional"}</span>
                      )}
                    </TableCell>
                    <TableCell className="text-right tabular-nums">{conSigno(l.desviacionPorRendimiento)}</TableCell>
                    <TableCell className="text-right tabular-nums">{conSigno(l.desviacionPorUso)}</TableCell>
                  </TableRow>
                ))}
              </TableBody>
            </Table>
            {editable ? (
              <Button size="sm" disabled={trabajando} onClick={() => void guardarConsumos()}>
                Guardar consumo real
              </Button>
            ) : null}
            {datos.adicionales.length > 0 ? (
              <p className="text-xs text-muted-foreground">
                Además hay {datos.adicionales.length} renglón(es) de materia prima adicional; se ven en su propia tarjeta.
              </p>
            ) : null}
          </>
        )}
      </CardContent>
    </Card>
  );
}

function clave(l: LineaConsumoReal): string {
  return `${l.pluProducto}|${l.pluComponente}`;
}
