"use client";

import { useEffect, useRef, useState } from "react";
import { CheckCircle2, CircleAlert, Plus, Upload } from "lucide-react";
import {
  api,
  cargarTarifasCsv,
  type Linea,
  type ResultadoCargaMasiva,
  type TarifaCosto,
} from "@/lib/api";
import { crearSecuencia } from "@/lib/secuencia";
import { usePuede } from "@/components/sesion-proveedor";
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 { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Badge } from "@/components/ui/badge";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { Textarea } from "@/components/ui/textarea";
import { toast } from "sonner";

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

export default function PaginaTarifas() {
  const puedeAdministrar = usePuede("administrar");
  const [tarifas, setTarifas] = useState<TarifaCosto[]>([]);
  const [lineas, setLineas] = useState<Linea[]>([]);
  const [cargando, setCargando] = useState(true);
  // Sin saber qué líneas hay, crear una tarifa «para una línea» acabaría creando una global.
  const [lineasListas, setLineasListas] = useState(false);
  // Si la lectura falla, el catálogo es DESCONOCIDO: mostrarlo vacío diría «0 tarifas» y «No hay tarifas», que es otra cosa.
  const [errorCarga, setErrorCarga] = useState<string | null>(null);

  async function cargar() {
    try {
      setTarifas(await api<TarifaCosto[]>("/api/costos-estandar"));
      setErrorCarga(null);
    } catch (error) {
      const mensaje = error instanceof Error ? error.message : "No se pudieron cargar las tarifas.";
      // Se descartan las filas viejas: tras un cambio que pudo haberse guardado ya no son de fiar (ni sus botones).
      setTarifas([]);
      setErrorCarga(mensaje);
      toast.error(mensaje);
    } finally {
      setCargando(false);
    }
    // Los nombres de las líneas sólo adornan el alcance: si no llegan, se muestra el número.
    try {
      setLineas(await api<Linea[]>("/api/lineas"));
      setLineasListas(true);
    } catch {
      setLineasListas(false);
    }
  }

  const nombreLinea = (id: number | null) =>
    null === id ? "Todas las líneas" : (lineas.find((l) => l.id === id)?.nombre ?? `Línea ${id}`);

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

  return (
    <div className="space-y-6">
      <div>
        <h1 className="text-2xl font-semibold tracking-tight">Tarifas de costo</h1>
        <p className="text-sm text-muted-foreground">
          Lo que cuesta la mano de obra, los gastos de fabricación y los indirectos. Cada orden las
          usa al estimar su costo; una tarifa nueva no cambia las órdenes ya estimadas.
        </p>
      </div>

      {puedeAdministrar ? <NuevaTarifa lineas={lineas} lineasListas={lineasListas} alGuardar={cargar} /> : null}
      {puedeAdministrar ? <CargaCsv alGuardar={cargar} /> : null}

      <Card>
        <CardHeader>
          <CardTitle className="text-base">Tarifas vigentes</CardTitle>
          <CardDescription>
            {cargando ? "Cargando…" : null !== errorCarga ? "No se pudo leer el catálogo." : `${tarifas.length} tarifas activas.`}
          </CardDescription>
        </CardHeader>
        <CardContent>
          <Table>
            <TableHeader>
              <TableRow>
                <TableHead>Código</TableHead>
                <TableHead>Tipo</TableHead>
                <TableHead className="text-right">Tarifa</TableHead>
                <TableHead>Base</TableHead>
                <TableHead>Alcance</TableHead>
                <TableHead>Motivo</TableHead>
                <TableHead>Vigente desde</TableHead>
                <TableHead>Vigente hasta</TableHead>
                <TableHead />
              </TableRow>
            </TableHeader>
            <TableBody>
              {tarifas.map((t) => (
                <TableRow key={t.id}>
                  <TableCell>
                    <div className="font-medium">{t.codigo}</div>
                    <div className="text-xs text-muted-foreground">{t.descripcion}</div>
                  </TableCell>
                  <TableCell>{t.tipoEtiqueta}</TableCell>
                  <TableCell className="text-right tabular-nums">{numero.format(t.tarifa)}</TableCell>
                  <TableCell>
                    {"MANUAL" === t.base ? "Se captura (litros, horas…)" : "Unidades producidas"}
                  </TableCell>
                  <TableCell>
                    {/* Varias filas pueden compartir código: el alcance es lo que las distingue. */}
                    <div>{nombreLinea(t.lineaId)}</div>
                    <div className="text-xs text-muted-foreground">
                      {null === t.pluProducto ? "Todos los productos" : `Producto ${t.pluProducto}`}
                    </div>
                  </TableCell>
                  <TableCell className="max-w-48 text-xs text-muted-foreground">{t.motivo ?? "—"}</TableCell>
                  <TableCell className="tabular-nums">{t.vigenteDesde}</TableCell>
                  <TableCell className="tabular-nums">{t.vigenteHasta ?? "sin fin"}</TableCell>
                  <TableCell className="text-right">
                    {puedeAdministrar ? <CerrarVigencia tarifa={t} alCerrar={cargar} /> : null}
                  </TableCell>
                </TableRow>
              ))}
              {!cargando && null !== errorCarga ? (
                <TableRow>
                  <TableCell colSpan={9} className="text-center text-amber-600">
                    No se pudieron leer las tarifas ({errorCarga}). No significa que no haya: recargue la página.
                  </TableCell>
                </TableRow>
              ) : null}
              {!cargando && null === errorCarga && 0 === tarifas.length ? (
                <TableRow>
                  <TableCell colSpan={9} className="text-center text-muted-foreground">
                    No hay tarifas. Cargue un archivo CSV arriba.
                  </TableCell>
                </TableRow>
              ) : null}
            </TableBody>
          </Table>
        </CardContent>
      </Card>
    </div>
  );
}

/**
 * Subir un CSV en dos pasos: primero se REVISA (no guarda nada), y sólo si no hay errores se
 * ofrece GUARDAR. Es todo o nada: una fila mala deja todo el archivo sin guardar.
 */
function CargaCsv({ alGuardar }: { alGuardar: () => Promise<void> }) {
  const [archivo, setArchivo] = useState<File | null>(null);
  const [bytes, setBytes] = useState<ArrayBuffer | null>(null);
  const [resultado, setResultado] = useState<ResultadoCargaMasiva | null>(null);
  const [trabajando, setTrabajando] = useState(false);
  // Cada lectura o revisión toma un número; sólo la última puede escribir en pantalla.
  const secuencia = useRef(crearSecuencia());

  async function elegir(f: File | null) {
    const mia = secuencia.current.nueva();
    setArchivo(f);
    setResultado(null);
    setBytes(null);
    setTrabajando(false); // lo que estaba en vuelo ya no cuenta
    if (!f) return;
    // Los BYTES, no `f.text()`: ese decodifica todo como UTF-8 y rompe las tildes de un CSV de Excel en Windows-1252.
    const leidos = await f.arrayBuffer();
    if (secuencia.current.vigente(mia)) setBytes(leidos);
  }

  async function enviar(aplicar: boolean) {
    if (null === bytes) return;
    const mia = secuencia.current.nueva();
    setTrabajando(true);
    try {
      const r = await cargarTarifasCsv(bytes, aplicar);
      if (!secuencia.current.vigente(mia)) return; // se eligió otro archivo mientras tanto
      setResultado(r);
      if (r.aplicado) {
        toast.success(`Se guardaron ${r.creados.length} tarifas.`);
        await alGuardar();
      }
    } catch (error) {
      if (secuencia.current.vigente(mia)) {
        toast.error(error instanceof Error ? error.message : "No se pudo procesar el archivo.");
      }
    } finally {
      if (secuencia.current.vigente(mia)) setTrabajando(false);
    }
  }

  const sinErrores = null !== resultado && 0 === resultado.errores.length;

  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-base">Cargar tarifas desde un archivo CSV</CardTitle>
        <CardDescription>
          Columnas obligatorias: codigo, descripcion, tipo (MANO_OBRA, GASTO_FABRICACION o
          INDIRECTO) y tarifa. Opcionales: base (UNIDAD_PRODUCIDA o MANUAL), vigente_desde,
          vigente_hasta, plu_producto, linea y motivo (si falta, queda «Carga masiva»). Primero se revisa; no se guarda nada hasta que usted
          lo confirme, y si una fila trae un error no se guarda ninguna.
        </CardDescription>
      </CardHeader>
      <CardContent className="space-y-4">
        <div className="space-y-1">
          <Label htmlFor="archivo-tarifas">Archivo CSV</Label>
          <Input
            id="archivo-tarifas"
            type="file"
            accept=".csv,text/csv"
            // Mientras una carga está en vuelo no se cambia de archivo: la petición no se puede cancelar y puede guardar en el servidor.
            disabled={trabajando}
            onChange={(e) => void elegir(e.target.files?.[0] ?? null)}
          />
        </div>

        <Button disabled={trabajando || null === bytes} onClick={() => void enviar(false)}>
          <Upload className="size-4" />
          Revisar archivo
        </Button>

        {resultado && !resultado.aplicado && sinErrores ? (
          <Alert>
            <CheckCircle2 className="size-4" />
            <AlertTitle>El archivo está bien: {resultado.filasValidas} tarifas válidas</AlertTitle>
            <AlertDescription className="space-y-2">
              <p>Todavía no se guardó nada.</p>
              <Button
                size="sm"
                disabled={trabajando || null === archivo || null === bytes}
                onClick={() => void enviar(true)}
              >
                Guardar {resultado.filasValidas} tarifas
              </Button>
            </AlertDescription>
          </Alert>
        ) : null}

        {resultado && resultado.aplicado ? (
          <Alert>
            <CheckCircle2 className="size-4" />
            <AlertTitle>Se guardaron {resultado.creados.length} tarifas</AlertTitle>
            <AlertDescription>Ya aparecen en la lista de abajo.</AlertDescription>
          </Alert>
        ) : null}

        {resultado && resultado.errores.length > 0 ? (
          <Alert variant="destructive">
            <CircleAlert className="size-4" />
            <AlertTitle>
              No se guardó nada: {resultado.errores.length}{" "}
              {1 === resultado.errores.length ? "error" : "errores"} ({resultado.filasValidas}{" "}
              {1 === resultado.filasValidas ? "fila válida" : "filas válidas"})
            </AlertTitle>
            <AlertDescription>
              <p className="mb-2">
                El número de fila cuenta la primera fila (los nombres de las columnas) como la 1,
                igual que en Excel. Corrija el archivo y vuelva a revisarlo.
              </p>
              <ul className="space-y-1">
                {resultado.errores.map((e, i) => (
                  <li key={i}>
                    <Badge variant="outline" className="mr-2">
                      {0 === e.fila ? "Archivo" : `Fila ${e.fila}`}
                    </Badge>
                    {e.mensaje}
                  </li>
                ))}
              </ul>
            </AlertDescription>
          </Alert>
        ) : null}
      </CardContent>
    </Card>
  );
}

/** Una tarifa no se edita ni se borra: se le cierra la vigencia y se crea la nueva desde el día siguiente. */
function CerrarVigencia({ tarifa, alCerrar }: { tarifa: TarifaCosto; alCerrar: () => Promise<void> }) {
  const [abierto, setAbierto] = useState(false);
  const [hasta, setHasta] = useState(tarifa.vigenteHasta ?? "");
  const [trabajando, setTrabajando] = useState(false);
  const yaCerrada = null !== tarifa.vigenteHasta;

  return (
    <Dialog open={abierto} onOpenChange={setAbierto}>
      <DialogTrigger asChild>
        <Button size="sm" variant="ghost">
          {yaCerrada ? "Ajustar vigencia" : "Cerrar vigencia"}
        </Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Cerrar la vigencia de {tarifa.codigo}</DialogTitle>
          <DialogDescription>
            La tarifa deja de aplicarse después de esa fecha{yaCerrada ? " (hoy: " + tarifa.vigenteHasta + ")" : ""}. Las órdenes que ya la usaron no cambian.
          </DialogDescription>
        </DialogHeader>
        <div className="space-y-1">
          <Label htmlFor={`hasta-${tarifa.id}`}>Última fecha en que aplica</Label>
          <Input id={`hasta-${tarifa.id}`} type="date" value={hasta} onChange={(e) => setHasta(e.target.value)} />
        </div>
        <DialogFooter>
          <Button variant="outline" onClick={() => setAbierto(false)}>
            Cancelar
          </Button>
          <Button
            disabled={trabajando || "" === hasta || hasta === tarifa.vigenteHasta}
            onClick={async () => {
              setTrabajando(true);
              try {
                await api(`/api/costos-estandar/${tarifa.id}/cerrar-vigencia`, {
                  metodo: "POST",
                  cuerpo: { hasta },
                });
                toast.success("Vigencia cerrada.");
                setAbierto(false);
                await alCerrar();
              } catch (error) {
                toast.error(error instanceof Error ? error.message : "No se pudo cerrar la vigencia.");
              } finally {
                setTrabajando(false);
              }
            }}
          >
            Cerrar vigencia
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}


/** Una tarifa nueva (o la renovación de una existente). El motivo es obligatorio: queda en la fila y en la bitácora. */
function NuevaTarifa({ lineas, lineasListas, alGuardar }: { lineas: Linea[]; lineasListas: boolean; alGuardar: () => Promise<void> }) {
  const vacio = { codigo: "", descripcion: "", tipo: "MANO_OBRA", tarifa: "", base: "", lineaId: "", pluProducto: "", vigenteDesde: "", motivo: "" };
  const [abierto, setAbierto] = useState(false);
  const [f, setF] = useState(vacio);
  const [trabajando, setTrabajando] = useState(false);
  const campo = (k: keyof typeof vacio) => (e: { target: { value: string } }) => setF((prev) => ({ ...prev, [k]: e.target.value }));
  const completo = "" !== f.codigo.trim() && "" !== f.descripcion.trim() && "" !== f.tarifa.trim() && "" !== f.motivo.trim();

  return (
    <Dialog open={abierto} onOpenChange={setAbierto}>
      <DialogTrigger asChild>
        <Button variant="outline" className="w-fit" disabled={!lineasListas} title={lineasListas ? undefined : "No se pudieron leer las líneas: recargue la página"}>
          <Plus className="size-4" />
          Nueva tarifa
        </Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Nueva tarifa</DialogTitle>
          <DialogDescription>
            Si el código ya existe con el mismo alcance, la vigencia nueva no puede traslaparse con la anterior: primero se
            cierra la vigencia de la vieja. Una tarifa nueva no cambia las órdenes ya estimadas.
          </DialogDescription>
        </DialogHeader>
        <div className="grid gap-3 sm:grid-cols-2">
          <div className="space-y-1">
            <Label htmlFor="nt-codigo">Código</Label>
            <Input id="nt-codigo" value={f.codigo} onChange={campo("codigo")} />
          </div>
          <div className="space-y-1">
            <Label htmlFor="nt-desc">Descripción</Label>
            <Input id="nt-desc" value={f.descripcion} onChange={campo("descripcion")} />
          </div>
          <div className="space-y-1">
            <Label htmlFor="nt-tipo">Tipo</Label>
            <select id="nt-tipo" className="h-9 w-full rounded-md border bg-transparent px-2 text-sm" value={f.tipo} onChange={campo("tipo")}>
              <option value="MANO_OBRA">Mano de obra</option>
              <option value="GASTO_FABRICACION">Gasto de fabricación</option>
              <option value="INDIRECTO">Indirecto</option>
            </select>
          </div>
          <div className="space-y-1">
            <Label htmlFor="nt-tarifa">Tarifa</Label>
            <Input id="nt-tarifa" type="number" min={0} step="any" value={f.tarifa} onChange={campo("tarifa")} />
          </div>
          <div className="space-y-1">
            <Label htmlFor="nt-base">Base</Label>
            <select id="nt-base" className="h-9 w-full rounded-md border bg-transparent px-2 text-sm" value={f.base} onChange={campo("base")}>
              <option value="">La del elemento (o unidades producidas)</option>
              <option value="UNIDAD_PRODUCIDA">Unidades producidas</option>
              <option value="MANUAL">Se captura (litros, horas…)</option>
            </select>
          </div>
          <div className="space-y-1">
            <Label htmlFor="nt-linea">Línea</Label>
            <select id="nt-linea" className="h-9 w-full rounded-md border bg-transparent px-2 text-sm" value={f.lineaId} onChange={campo("lineaId")}>
              <option value="">Todas las líneas</option>
              {lineas.map((l) => (
                <option key={l.id} value={String(l.id)}>
                  {l.nombre}
                </option>
              ))}
            </select>
          </div>
          <div className="space-y-1">
            <Label htmlFor="nt-plu">Producto (opcional)</Label>
            <Input id="nt-plu" value={f.pluProducto} onChange={campo("pluProducto")} placeholder="código del producto" />
          </div>
          <div className="space-y-1">
            <Label htmlFor="nt-desde">Vigente desde (opcional)</Label>
            <Input id="nt-desde" type="date" value={f.vigenteDesde} onChange={campo("vigenteDesde")} />
          </div>
        </div>
        <div className="space-y-1">
          <Label htmlFor="nt-motivo">¿Por qué se crea o cambia esta tarifa?</Label>
          <Textarea id="nt-motivo" maxLength={255} value={f.motivo} onChange={campo("motivo")} placeholder="Por ejemplo: ajuste salarial octubre" />
        </div>
        <DialogFooter>
          <Button variant="outline" onClick={() => setAbierto(false)}>
            Cancelar
          </Button>
          <Button
            disabled={trabajando || !completo}
            onClick={async () => {
              setTrabajando(true);
              try {
                await api("/api/costos-estandar", {
                  metodo: "POST",
                  cuerpo: {
                    codigo: f.codigo.trim(),
                    descripcion: f.descripcion.trim(),
                    tipo: f.tipo,
                    tarifa: Number(f.tarifa),
                    base: f.base,
                    lineaId: f.lineaId,
                    pluProducto: f.pluProducto.trim() || null,
                    vigenteDesde: f.vigenteDesde || undefined,
                    motivo: f.motivo.trim(),
                  },
                });
                toast.success("Tarifa creada.");
                setAbierto(false);
                setF(vacio);
                await alGuardar();
              } catch (error) {
                toast.error(error instanceof Error ? error.message : "No se pudo crear la tarifa.");
              } finally {
                setTrabajando(false);
              }
            }}
          >
            Guardar tarifa
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}
