"use client";

import { useCallback, useEffect, useState } from "react";
import { Calculator, CheckCircle2, CircleAlert, Pencil } from "lucide-react";
import { api, type CostosDeOrden, type Orden, type RenglonCosto } from "@/lib/api";
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 {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { toast } from "sonner";
import {
  faltaBaseReal,
  faseDeCosto,
  guardarBaseDeshabilitado,
  puedeAplicarReal,
  puedeBaseEstimada,
  puedeBaseReal,
  puedeEstimar,
  type FaseCosto,
} from "@/lib/costos-reglas";

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

/** El nombre del producto de la orden, o su código si la orden no lo trae. */
function nombreDe(orden: Orden, plu: string): string {
  return leerNombre(orden.detalles.find((d) => d.plu === plu)?.descripcion ?? plu).nombre;
}

/** `—` cuando todavía no hay número, que no es lo mismo que cero. */
function q(valor: number | null | undefined): string {
  return null === valor || undefined === valor ? "—" : dinero.format(valor);
}

/**
 * El costo de la orden por componentes: materiales, mano de obra, gastos e indirectos.
 *
 * ⚠️ **La pregunta que esta tarjeta tiene que contestar antes que ninguna otra es si estos
 * números llegaron al inventario.** Un total prolijo en pantalla no significa que el
 * producto terminado haya entrado valuado así: sólo entra si el costo estaba completo al
 * registrar la producción, y después el documento ya no se toca. Por eso el estado va
 * arriba del todo y no como una nota al pie.
 */
export function CostosOrden({
  orden,
  onCambio,
}: {
  orden: Orden;
  onCambio: () => void;
}) {
  const [costos, setCostos] = useState<CostosDeOrden | null>(null);
  const [trabajando, setTrabajando] = useState(false);
  // Si una lectura falla, lo que se mostraba ya no es de fiar (la orden pudo cambiar de estado): se descarta y se avisa.
  const [falloLectura, setFalloLectura] = useState(false);

  const puedeVer = usePuede("ver");
  const puedeEstimarPermiso = usePuede("editar");
  const puedeAplicar = usePuede("registrar_produccion");

  const cargar = useCallback(async () => {
    if (!puedeVer) return;
    try {
      setCostos(await api<CostosDeOrden>(`/api/ordenes/${orden.id}/costos`));
      setFalloLectura(false);
    } catch (error) {
      setCostos(null);
      setFalloLectura(true);
      toast.error(error instanceof Error ? error.message : "No se pudo leer el costo de la orden.");
    }
    // `orden.estado` entre las dependencias por lo mismo que en consumos adicionales:
    // registrar producción cambia el costo del otro lado y la tarjeta tiene que enterarse.
  }, [orden.id, orden.estado, puedeVer]);

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

  async function ejecutar(ruta: string, cuerpo: unknown, exito: string) {
    setTrabajando(true);
    try {
      setCostos(
        await api<CostosDeOrden>(`/api/ordenes/${orden.id}/costos${ruta}`, {
          metodo: "POST",
          cuerpo,
        }),
      );
      toast.success(exito);
      onCambio();
      return true;
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "No se pudo completar la operación.", {
        duration: 10000,
      });
      void cargar();
      return false;
    } finally {
      setTrabajando(false);
    }
  }

  if (!puedeVer) return null;
  if (falloLectura) {
    return (
      <Card className="min-w-0">
        <CardContent className="py-4 text-sm text-amber-600">
          No se pudo leer el costo de la orden, así que no se sabe qué costo tiene ahora. Recargue la página antes de fiarse de lo que vio antes.
        </CardContent>
      </Card>
    );
  }
  if (costos === null) return null;

  const sinEstimar = costos.renglones.length === 0;
  // «Producida» incluye la que se produjo y después se anuló: conserva su costo congelado y su auditoría.
  const producida =
    orden.estado === "PRODUCIDA" || orden.estado === "TERMINADA" || null !== orden.cerradoEn || null !== orden.produccionDocumento;
  // Qué se puede hacer lo decide `costos-reglas` (la misma regla del servidor): una orden ANULADA o ya CERRADA con costo
  // congelado es de sólo lectura aunque tenga documento de producción.
  const fase = faseDeCosto(orden.estado, orden.cerradoEn);

  // Sin catálogo de tarifas no hay nada que mostrar y la tarjeta sólo estorbaría: el
  // módulo tiene que seguir funcionando igual para quien nunca configuró costos.
  // Sin renglones no se sabe si es que no hay tarifas o que la orden nunca se estimó (heredada, o estimada antes de cargar tarifas):
  // quien puede estimar conserva el botón.
  if (sinEstimar && !producida && !(puedeEstimarPermiso && puedeEstimar(fase, costos.renglones, costos.yaEstimada))) return null;

  // Lo que falta para poder cerrar: la base REAL de cada componente manual (el cierre la exige); de una estimación tardía,
  // la ESTIMADA que quedó pendiente.
  const faltaReal = costos.renglones.filter((r) =>
    faltaBaseReal(fase, orden.estado, r, orden.detalles.find((d) => d.plu === r.pluProducto)?.cantidadProducida ?? 0),
  );
  const faltaEstimada = costos.renglones.filter((r) => puedeBaseEstimada(fase, r) && r.basePendiente);

  return (
    <Card className="min-w-0">
      <CardHeader>
        <div className="flex flex-wrap items-start justify-between gap-3">
          <div className="min-w-0">
            <CardTitle className="text-base">Costo de la orden</CardTitle>
            <CardDescription>
              Materiales, mano de obra y gastos de planta, estimado contra real.
            </CardDescription>
          </div>
          <div className="flex flex-wrap gap-2">
            {puedeEstimarPermiso && puedeEstimar(fase, costos.renglones, costos.yaEstimada) ? (
              <Button
                size="sm"
                variant="outline"
                disabled={trabajando}
                onClick={() =>
                  void ejecutar("/estimar", {}, sinEstimar ? "Costo estimado." : "Estimación rehecha.")
                }
              >
                <Calculator className="size-4" />
                {sinEstimar ? "Estimar costo" : "Volver a estimar"}
              </Button>
            ) : null}
            {puedeAplicar && puedeAplicarReal(fase, costos.total.real) ? (
              <Button
                size="sm"
                disabled={trabajando}
                onClick={() => void ejecutar("/real", {}, "Costo real aplicado.")}
              >
                Aplicar costo real
              </Button>
            ) : null}
          </div>
        </div>
      </CardHeader>

      <CardContent className="space-y-4">
        <EstadoDelInventario costos={costos} cerrada={null !== orden.cerradoEn} yaProducida={producida} />

        {sinEstimar ? (
          <p className="py-4 text-center text-sm text-muted-foreground">
            Esta orden no tiene costo estimado. Sin él, el producto terminado entra al
            inventario valuado sólo a materia prima.
          </p>
        ) : (
          <>
            <Table>
              <TableHeader>
                <TableRow>
                  <TableHead>Bloque</TableHead>
                  <TableHead className="text-right">Estimado</TableHead>
                  <TableHead className="text-right">Real</TableHead>
                  <TableHead className="text-right">Diferencia</TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {Object.entries(costos.bloques).map(([clave, b]) => (
                  <TableRow key={clave}>
                    <TableCell>{b.etiqueta}</TableCell>
                    <TableCell className="text-right tabular-nums">{q(b.estimado)}</TableCell>
                    <TableCell className="text-right tabular-nums">{q(b.real)}</TableCell>
                    <TableCell className="text-right tabular-nums">
                      <Diferencia valor={b.diferencia} />
                    </TableCell>
                  </TableRow>
                ))}
                <TableRow className="border-t-2 font-medium">
                  <TableCell>Total</TableCell>
                  <TableCell className="text-right tabular-nums">{q(costos.total.estimado)}</TableCell>
                  <TableCell className="text-right tabular-nums">{q(costos.total.real)}</TableCell>
                  <TableCell className="text-right tabular-nums">
                    <Diferencia valor={costos.total.diferencia} />
                  </TableCell>
                </TableRow>
              </TableBody>
            </Table>

            <AvisosDeCosto costos={costos} />

            {costos.productos.length > 0 ? (
              <div className="grid gap-3 sm:grid-cols-2">
                {costos.productos.map((p) => (
                  <div key={p.plu} className="rounded-lg border p-3">
                    <div className="text-sm font-medium">
                      {leerNombre(
                        orden.detalles.find((d) => d.plu === p.plu)?.descripcion ?? p.plu,
                      ).nombre}
                    </div>
                    <div className="text-xs text-muted-foreground">
                      {numero.format(p.producida)} unidades
                    </div>
                    <dl className="mt-2 space-y-1 text-sm">
                      <div className="flex justify-between gap-2">
                        <dt className="text-muted-foreground">Sólo materia prima</dt>
                        <dd className="tabular-nums">{q(p.costoUnitarioMateriales)}</dd>
                      </div>
                      <div className="flex justify-between gap-2">
                        <dt className="text-muted-foreground">Componentes estimado</dt>
                        <dd className="tabular-nums">{q(p.componentesEstimado)}</dd>
                      </div>
                      <div className="flex justify-between gap-2">
                        <dt className="text-muted-foreground">Componentes real</dt>
                        <dd className="tabular-nums">{null === p.componentesReal ? "—" : q(p.componentesReal)}</dd>
                      </div>
                      <div className="flex justify-between gap-2">
                        <dt className="text-muted-foreground">Diferencia de componentes</dt>
                        <dd className="tabular-nums">
                          <Diferencia valor={p.componentesDiferencia} />
                        </dd>
                      </div>
                      <div className="flex justify-between gap-2 font-medium">
                        <dt>Costo por unidad</dt>
                        <dd className="tabular-nums">{q(p.costoUnitarioConComponentes)}</dd>
                      </div>
                    </dl>
                  </div>
                ))}
              </div>
            ) : null}

            <Renglones
              costos={costos}
              fase={fase}
              orden={orden}
              puedeCapturar={puedeAplicar}
              trabajando={trabajando}
              ejecutar={ejecutar}
            />

            {faltaReal.length > 0 ? (
              <p className="text-sm text-amber-600">
                Falta la base real de {faltaReal.map((r) => `${r.codigo} (${nombreDe(orden, r.pluProducto)})`).join(", ")} (botón
                «Base» en el detalle por componente): sin ella{" "}
                {"LEGADO" === fase ? "no se puede aplicar el costo real." : "no se puede registrar la producción."}
              </p>
            ) : null}
            {faltaEstimada.length > 0 ? (
              <p className="text-sm text-amber-600">
                Falta la base estimada de {faltaEstimada.map((r) => `${r.codigo} (${nombreDe(orden, r.pluProducto)})`).join(", ")}:
                el total estimado está incompleto hasta que se capture.
              </p>
            ) : null}
          </>
        )}
      </CardContent>
    </Card>
  );
}

/**
 * Lo primero que se lee: si este costo llegó al inventario o se quedó en la pantalla.
 *
 * Son tres estados y ninguno es «casi»: entró y cuánto, no entró y ya no va a entrar, o
 * todavía no se registró la producción y entrará si para entonces está completo.
 */
function EstadoDelInventario({ costos, cerrada, yaProducida }: { costos: CostosDeOrden; cerrada: boolean; yaProducida: boolean }) {
  if (costos.componentesEnInventario) {
    return (
      <div className="flex items-start gap-2 rounded-lg border border-emerald-200 bg-emerald-50 p-3 text-sm dark:border-emerald-900 dark:bg-emerald-950/40">
        <CheckCircle2 className="mt-0.5 size-4 shrink-0 text-emerald-600" />
        <p>
          <span className="font-medium">Este costo está en el inventario.</span>{" "}
          {cerrada
            ? "Al cerrar la orden entró al producto terminado el costo REAL: tarifa congelada por la base real de cada componente. El costo quedó congelado; un dato tardío se registra como ajuste, no lo cambia."
            : "Al registrar la producción se sumaron al costo del producto terminado la mano de obra, los gastos y los indirectos a tarifa estándar (con las unidades producidas, o con la base estimada en los de base manual). Aplicar el costo real después no cambia ese valor: la diferencia se ve acá."}
        </p>
      </div>
    );
  }

  return (
    <div className="flex items-start gap-2 rounded-lg border border-amber-200 bg-amber-50 p-3 text-sm dark:border-amber-900 dark:bg-amber-950/40">
      <CircleAlert className="mt-0.5 size-4 shrink-0 text-amber-600" />
      <p>
        {yaProducida ? (
          <>
            <span className="font-medium">Este costo NO está en el inventario.</span> La
            producción se registró sin tener estimado el costo por componentes, así que el
            producto entró valuado sólo a materia prima. Lo de acá es informativo: completarlo ahora ya no cambia ese
            documento.
          </>
        ) : (
          <>
            <span className="font-medium">Todavía no está en el inventario.</span> Entra al
            cerrar la orden con su costo real; los componentes de base manual necesitan su base
            real (se captura con la materia prima despachada) o el cierre se rechaza.
          </>
        )}
      </p>
    </div>
  );
}

/**
 * Lo que el servidor avisa junto con los totales. Un estimado que cuenta un material sin costo como cero se ve igual de
 * prolijo que uno completo: sin este aviso al lado del total, nadie lo distingue.
 */
function AvisosDeCosto({ costos }: { costos: CostosDeOrden }) {
  if (costos.total.estimadoCompleto && 0 === costos.avisos.length) return null;

  return (
    <div className="space-y-1 rounded-lg border border-amber-200 bg-amber-50 p-3 text-sm dark:border-amber-900 dark:bg-amber-950/40">
      {costos.total.estimadoCompleto ? null : (
        <p className="font-medium">El total estimado está incompleto: no lo tomes como definitivo.</p>
      )}
      <ul className="list-disc space-y-0.5 pl-5 text-muted-foreground">
        {costos.avisos.map((a) => (
          <li key={a}>{a}</li>
        ))}
      </ul>
    </div>
  );
}

function Diferencia({ valor }: { valor: number | null }) {
  if (null === valor) return <span className="text-muted-foreground">—</span>;

  // Positivo = se estimó de más, o sea que salió más barato. Verde.
  const color = valor > 0 ? "text-emerald-600" : valor < 0 ? "text-red-600" : "";

  return (
    <span className={color}>
      {valor > 0 ? "+" : ""}
      {dinero.format(valor)}
    </span>
  );
}

function Renglones({
  costos,
  fase,
  orden,
  puedeCapturar,
  trabajando,
  ejecutar,
}: {
  costos: CostosDeOrden;
  fase: FaseCosto;
  orden: Orden;
  puedeCapturar: boolean;
  trabajando: boolean;
  ejecutar: (ruta: string, cuerpo: unknown, exito: string) => Promise<boolean>;
}) {
  const [abierto, setAbierto] = useState(false);

  return (
    <div>
      <Button
        variant="ghost"
        size="sm"
        className="px-0 text-muted-foreground"
        onClick={() => setAbierto((v) => !v)}
      >
        {abierto ? "Ocultar" : "Ver"} el detalle por componente ({costos.renglones.length})
      </Button>

      {abierto ? (
        <Table>
          <TableHeader>
            <TableRow>
              <TableHead>Componente</TableHead>
              <TableHead className="text-right">Tarifa</TableHead>
              <TableHead className="text-right">Base</TableHead>
              <TableHead className="text-right">Estimado</TableHead>
              <TableHead className="text-right">Real</TableHead>
              <TableHead />
            </TableRow>
          </TableHeader>
          <TableBody>
            {costos.renglones.map((r) => (
              <TableRow key={r.id}>
                <TableCell>
                  <div className="font-medium">{r.codigo}</div>
                  <div className="text-xs text-muted-foreground">{r.descripcion}</div>
                  {/* Una orden con varios productos tiene un renglón por cada uno: sin esto parecen duplicados. */}
                  <div className="text-xs text-muted-foreground">Producto: {nombreDe(orden, r.pluProducto)}</div>
                </TableCell>
                <TableCell className="text-right tabular-nums">{numero.format(r.tarifa)}</TableCell>
                <TableCell className="text-right tabular-nums">
                  {r.basePendiente && null === r.baseReal ? "—" : numero.format(r.baseReal ?? r.baseEstimada)}
                  <div className="text-xs text-muted-foreground">
                    {"MANUAL" === r.base ? "se captura" : "unidades producidas"}
                  </div>
                </TableCell>
                <TableCell className="text-right tabular-nums">{q(r.montoEstimado)}</TableCell>
                <TableCell className="text-right tabular-nums">{q(r.montoReal)}</TableCell>
                <TableCell className="text-right">
                  {puedeCapturar && (puedeBaseEstimada(fase, r) || puedeBaseReal(fase, r)) ? (
                    <DialogoBase renglon={r} fase={fase} nombreProducto={nombreDe(orden, r.pluProducto)} trabajando={trabajando} ejecutar={ejecutar} />
                  ) : null}
                </TableCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
      ) : null}
    </div>
  );
}

/**
 * La base de un componente que nadie puede calcular solo: los litros procesados, las horas
 * de la cuadrilla. Alguien de la planta los anota.
 */
function DialogoBase({
  renglon,
  fase,
  nombreProducto,
  trabajando,
  ejecutar,
}: {
  renglon: RenglonCosto;
  fase: FaseCosto;
  nombreProducto: string;
  trabajando: boolean;
  ejecutar: (ruta: string, cuerpo: unknown, exito: string) => Promise<boolean>;
}) {
  // El servidor sólo acepta la base ESTIMADA antes de producir (después, sólo si quedó pendiente) y la REAL sólo
  // después: se muestra y se manda únicamente la que corresponde al momento de la orden.
  const pideEstimada = puedeBaseEstimada(fase, renglon);
  const pideReal = puedeBaseReal(fase, renglon);
  const [abierto, setAbierto] = useState(false);
  const [estimada, setEstimada] = useState(renglon.basePendiente ? "" : String(renglon.baseEstimada));
  const [real, setReal] = useState(renglon.baseReal === null ? "" : String(renglon.baseReal));

  return (
    <Dialog open={abierto} onOpenChange={setAbierto}>
      <DialogTrigger asChild>
        <Button size="sm" variant="ghost" disabled={trabajando}>
          <Pencil className="size-4" />
          Base
        </Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Base de {renglon.codigo} · {nombreProducto}</DialogTitle>
          <DialogDescription>
            {renglon.descripcion}. La tarifa es {numero.format(renglon.tarifa)} por unidad de
            base; el monto sale de multiplicarlas.
          </DialogDescription>
        </DialogHeader>
        <div className="space-y-3">
          {pideEstimada ? (
            <div className="space-y-1">
              <Label htmlFor={`base-est-${renglon.id}`}>Base estimada</Label>
              <Input
                id={`base-est-${renglon.id}`}
                type="number"
                min={0}
                step="any"
                value={estimada}
                onChange={(e) => setEstimada(e.target.value)}
              />
              <p className="text-xs text-muted-foreground">
                Lo que se espera gastar. Se captura antes de producir, y es la que entra al costo del
                producto terminado en el inventario.
              </p>
            </div>
          ) : null}
          {pideReal ? (
            <div className="space-y-1">
              <Label htmlFor={`base-real-${renglon.id}`}>Base real</Label>
              <Input
                id={`base-real-${renglon.id}`}
                type="number"
                min={0}
                step="any"
                value={real}
                onChange={(e) => setReal(e.target.value)}
              />
              <p className="text-xs text-muted-foreground">
                Lo que de verdad se gastó. Se captura antes de cerrar la orden, y el cierre la exige;
                con la orden ya cerrada no se cambia: se registra como ajuste.
              </p>
            </div>
          ) : null}
        </div>
        <DialogFooter>
          <Button variant="outline" onClick={() => setAbierto(false)}>
            Cancelar
          </Button>
          <Button
            disabled={trabajando || guardarBaseDeshabilitado(pideEstimada, pideReal, estimada, real)}
            onClick={async () => {
              const bien = await ejecutar(
                "/bases",
                {
                  renglonId: renglon.id,
                  baseEstimada: pideEstimada && "" !== estimada.trim() ? Number(estimada) : null,
                  baseReal: pideReal && "" !== real.trim() ? Number(real) : null,
                },
                "Base capturada.",
              );
              if (bien) setAbierto(false);
            }}
          >
            Guardar
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}
