"use client";

import { Fragment, useCallback, useEffect, useRef, useState } from "react";
import { AppShell } from "@/components/AppShell";
import { useCan } from "@/lib/abilities";
import {
  OPS_KIND_LABELS,
  OPS_STATUS_LABELS,
  formatBytes,
  formatDateTime,
} from "@/lib/labels";
import {
  backupDownloadUrl,
  deleteBackup,
  deleteIngest,
  getOpsJob,
  getReplicationStatus,
  listBackups,
  listIngest,
  listOpsJobs,
  startOpsJob,
  uploadIngest,
} from "@/lib/api";
import type { UploadProgress } from "@/lib/api";
import type {
  BackupFile,
  IngestFile,
  OpsJob,
  OpsJobKind,
  ReplicationStatus,
} from "@/lib/types";

const INGEST_MAX_BYTES = 2 * 1024 * 1024 * 1024;

export default function OperacionesPage() {
  return (
    <AppShell>
      <Operaciones />
    </AppShell>
  );
}

type ConfirmState =
  | { kind: "REIMPORT" }
  | { kind: "SYNC" }
  | { kind: "RESTORE"; file: string }
  | null;

function Operaciones() {
  const allowed = useCan("db:manage");

  const [ingest, setIngest] = useState<IngestFile[] | null>(null);
  const [backups, setBackups] = useState<BackupFile[] | null>(null);
  const [jobs, setJobs] = useState<OpsJob[] | null>(null);
  const [activeJob, setActiveJob] = useState<OpsJob | null>(null);

  const [error, setError] = useState<string | null>(null);
  const [notice, setNotice] = useState<string | null>(null);
  const [confirm, setConfirm] = useState<ConfirmState>(null);
  const [confirmText, setConfirmText] = useState("");
  const [uploading, setUploading] = useState<string | null>(null);
  const [progress, setProgress] = useState<UploadProgress | null>(null);
  const [starting, setStarting] = useState(false);

  const fileInputs = useRef<Record<string, HTMLInputElement | null>>({});

  const refreshLists = useCallback(() => {
    listIngest().then(setIngest).catch(() => setIngest([]));
    listBackups().then(setBackups).catch(() => setBackups([]));
    listOpsJobs()
      .then((rows) => {
        setJobs(rows);
        const running = rows.find((j) => j.status === "RUNNING");
        if (running) setActiveJob(running);
      })
      .catch(() => setJobs([]));
  }, []);

  useEffect(() => {
    if (allowed) refreshLists();
  }, [allowed, refreshLists]);

  // Poll the active job while it runs; refresh everything when it finishes.
  useEffect(() => {
    if (!activeJob || activeJob.status !== "RUNNING") return;
    const id = activeJob.id;
    const timer = setInterval(() => {
      getOpsJob(id)
        .then((job) => {
          setActiveJob(job);
          if (job.status !== "RUNNING") {
            clearInterval(timer);
            refreshLists();
            setNotice(
              job.status === "SUCCESS"
                ? `${OPS_KIND_LABELS[job.kind]} completada.`
                : `${OPS_KIND_LABELS[job.kind]} terminó con error. Revise el registro.`,
            );
          }
        })
        .catch(() => {
          /* transient — keep polling */
        });
    }, 1500);
    return () => clearInterval(timer);
  }, [activeJob, refreshLists]);

  if (!allowed) {
    return (
      <div className="page-head">
        <h1 className="page-title">Operaciones</h1>
        <div className="state-box state-error">
          No tiene permisos para administrar la base de datos.
        </div>
      </div>
    );
  }

  const jobRunning = activeJob?.status === "RUNNING";

  async function handleUpload(name: string, file: File | undefined) {
    if (!file) return;
    setError(null);
    setNotice(null);
    setUploading(name);
    setProgress(null);
    try {
      await uploadIngest(name, file, setProgress);
      setNotice(`${name} cargado.`);
      refreshLists();
    } catch (e) {
      setError((e as Error)?.message ?? "No se pudo cargar el archivo.");
    } finally {
      setUploading(null);
      setProgress(null);
      const input = fileInputs.current[name];
      if (input) input.value = "";
    }
  }

  async function handleDeleteIngest(name: string) {
    setError(null);
    try {
      await deleteIngest(name);
      refreshLists();
    } catch (e) {
      setError((e as Error)?.message ?? "No se pudo eliminar.");
    }
  }

  async function handleDeleteBackup(name: string) {
    setError(null);
    try {
      await deleteBackup(name);
      refreshLists();
    } catch (e) {
      setError((e as Error)?.message ?? "No se pudo eliminar el respaldo.");
    }
  }

  async function start(kind: OpsJobKind, file?: string) {
    setError(null);
    setNotice(null);
    setStarting(true);
    try {
      const job = await startOpsJob(kind, file);
      setActiveJob(job);
      setJobs((prev) => (prev ? [job, ...prev] : [job]));
    } catch (e) {
      setError((e as Error)?.message ?? "No se pudo iniciar la operación.");
    } finally {
      setStarting(false);
    }
  }

  function askConfirm(state: ConfirmState) {
    setConfirm(state);
    setConfirmText("");
    setError(null);
    setNotice(null);
  }

  async function runConfirmed() {
    if (!confirm) return;
    const c = confirm;
    setConfirm(null);
    if (c.kind === "REIMPORT") await start("REIMPORT");
    else if (c.kind === "SYNC") await start("SYNC");
    else await start("RESTORE", c.file);
  }

  const ingestReady = (ingest ?? []).every((f) => f.present);

  return (
    <>
      <div className="page-head">
        <h1 className="page-title">Operaciones de base de datos</h1>
      </div>

      {error && <div className="state-box state-error">{error}</div>}
      {notice && <div className="state-box">{notice}</div>}

      {/* Active / running job with live log */}
      {activeJob && (
        <div className="card" style={{ padding: 20, marginBottom: 20 }}>
          <div className="row-actions" style={{ justifyContent: "space-between" }}>
            <h2 className="section-title" style={{ margin: 0 }}>
              {OPS_KIND_LABELS[activeJob.kind]}{" "}
              <span
                className={`badge ${
                  activeJob.status === "SUCCESS"
                    ? "badge-positive"
                    : activeJob.status === "FAILED"
                      ? "badge-negative"
                      : "badge-neutral"
                }`}
              >
                {jobRunning && <span className="spinner" aria-hidden style={{ marginRight: 6 }} />}
                {OPS_STATUS_LABELS[activeJob.status]}
              </span>
            </h2>
            {!jobRunning && (
              <button className="btn btn-ghost" type="button" onClick={() => setActiveJob(null)}>
                Ocultar
              </button>
            )}
          </div>
          <pre className="ops-log">{activeJob.log || "Iniciando…"}</pre>
        </div>
      )}

      <ReplicationCard />

      {/* Ingest folder */}
      <div className="card" style={{ padding: 20, marginBottom: 20 }}>
        <h2 className="section-title">Carpeta de ingesta</h2>
        <p className="inline-form-note">
          Los cuatro archivos originales de Access. La reimportación y la
          sincronización leen de aquí. Tamaño máximo por archivo: {formatBytes(INGEST_MAX_BYTES)}.
        </p>
        <div className="tx-scroll">
          <table className="tx-table">
            <thead>
              <tr>
                <th>Archivo</th>
                <th>Estado</th>
                <th className="num">Tamaño</th>
                <th>Modificado</th>
                <th className="num">Acciones</th>
              </tr>
            </thead>
            <tbody>
              {(ingest ?? []).map((f) => (
                <Fragment key={f.name}>
                  <tr>
                    <td className="mono">{f.name}</td>
                    <td>
                      <span className={`badge ${f.present ? "badge-positive" : "badge-negative"}`}>
                        {f.present ? "Presente" : "Falta"}
                      </span>
                    </td>
                    <td className="num">{formatBytes(f.size)}</td>
                    <td>{formatDateTime(f.modifiedAt)}</td>
                    <td>
                      <div className="row-actions">
                        <input
                          ref={(el) => {
                            fileInputs.current[f.name] = el;
                          }}
                          type="file"
                          style={{ display: "none" }}
                          onChange={(e) => handleUpload(f.name, e.target.files?.[0])}
                        />
                        <button
                          className="btn btn-outline"
                          type="button"
                          disabled={uploading === f.name}
                          onClick={() => fileInputs.current[f.name]?.click()}
                        >
                          {uploading === f.name ? "Cargando…" : f.present ? "Reemplazar" : "Cargar"}
                        </button>
                        {f.present && (
                          <button
                            className="btn btn-ghost"
                            type="button"
                            disabled={uploading === f.name}
                            onClick={() => handleDeleteIngest(f.name)}
                          >
                            Eliminar
                          </button>
                        )}
                      </div>
                    </td>
                  </tr>
                  {uploading === f.name && (
                    <tr>
                      <td colSpan={5}>
                        <UploadProgressBar progress={progress} />
                      </td>
                    </tr>
                  )}
                </Fragment>
              ))}
            </tbody>
          </table>
        </div>
      </div>

      {/* Operations */}
      <div className="card" style={{ padding: 20, marginBottom: 20 }}>
        <h2 className="section-title">Operaciones</h2>
        <div className="form-grid">
          <OpTile
            title="Respaldo"
            desc="Genera un volcado comprimido de la base de datos actual."
            action="Crear respaldo"
            tone="primary"
            disabled={jobRunning || starting}
            onClick={() => start("BACKUP")}
          />
          <OpTile
            title="Reimportar (purga)"
            desc="Respalda, borra TODO y reconstruye desde los archivos de ingesta. Se pierden los datos capturados manualmente."
            action="Reimportar"
            tone="danger"
            disabled={jobRunning || starting || !ingestReady}
            onClick={() => askConfirm({ kind: "REIMPORT" })}
          />
          <OpTile
            title="Sincronizar"
            desc="Respalda, luego importa lo nuevo del legado. Borra del sistema los registros del legado que ya no aparecen en los archivos de ingesta. Se conservan los datos capturados a mano."
            action="Sincronizar"
            tone="primary"
            disabled={jobRunning || starting || !ingestReady}
            onClick={() => askConfirm({ kind: "SYNC" })}
          />
        </div>
        {!ingestReady && (
          <p className="inline-form-note" style={{ marginTop: 12 }}>
            La reimportación requiere que los cuatro archivos estén presentes.
          </p>
        )}
      </div>

      {/* Backups */}
      <div className="card" style={{ padding: 20, marginBottom: 20 }}>
        <h2 className="section-title">Respaldos</h2>
        <p className="inline-form-note">
          Restaurar sobreescribe la base de datos completa con el respaldo elegido.
        </p>
        <div className="tx-scroll">
          <table className="tx-table">
            <thead>
              <tr>
                <th>Archivo</th>
                <th className="num">Tamaño</th>
                <th>Creado</th>
                <th className="num">Acciones</th>
              </tr>
            </thead>
            <tbody>
              {backups === null ? (
                <tr>
                  <td colSpan={4}>
                    <span className="spinner" aria-label="Cargando" />
                  </td>
                </tr>
              ) : backups.length === 0 ? (
                <tr>
                  <td colSpan={4} className="muted">
                    Sin respaldos.
                  </td>
                </tr>
              ) : (
                backups.map((b) => (
                  <tr key={b.name}>
                    <td className="mono">{b.name}</td>
                    <td className="num">{formatBytes(b.size)}</td>
                    <td>{formatDateTime(b.createdAt)}</td>
                    <td>
                      <div className="row-actions">
                        <a className="btn btn-outline" href={backupDownloadUrl(b.name)}>
                          Descargar
                        </a>
                        <button
                          className="btn btn-outline"
                          type="button"
                          disabled={jobRunning || starting}
                          onClick={() => askConfirm({ kind: "RESTORE", file: b.name })}
                        >
                          Restaurar
                        </button>
                        <button
                          className="btn btn-ghost"
                          type="button"
                          onClick={() => handleDeleteBackup(b.name)}
                        >
                          Eliminar
                        </button>
                      </div>
                    </td>
                  </tr>
                ))
              )}
            </tbody>
          </table>
        </div>
      </div>

      {/* Recent jobs */}
      <div className="card" style={{ padding: 20 }}>
        <h2 className="section-title">Historial</h2>
        <div className="tx-scroll">
          <table className="tx-table">
            <thead>
              <tr>
                <th>Operación</th>
                <th>Estado</th>
                <th>Inicio</th>
                <th>Fin</th>
                <th className="num"></th>
              </tr>
            </thead>
            <tbody>
              {(jobs ?? []).map((j) => (
                <tr key={j.id}>
                  <td>{OPS_KIND_LABELS[j.kind]}</td>
                  <td>
                    <span
                      className={`badge ${
                        j.status === "SUCCESS"
                          ? "badge-positive"
                          : j.status === "FAILED"
                            ? "badge-negative"
                            : "badge-neutral"
                      }`}
                    >
                      {OPS_STATUS_LABELS[j.status]}
                    </span>
                  </td>
                  <td>{formatDateTime(j.startedAt)}</td>
                  <td>{formatDateTime(j.finishedAt)}</td>
                  <td className="num">
                    <button
                      className="btn btn-ghost"
                      type="button"
                      onClick={() => setActiveJob(j)}
                    >
                      Ver registro
                    </button>
                  </td>
                </tr>
              ))}
              {jobs && jobs.length === 0 && (
                <tr>
                  <td colSpan={5} className="muted">
                    Sin operaciones registradas.
                  </td>
                </tr>
              )}
            </tbody>
          </table>
        </div>
      </div>

      {/* Destructive-op confirm */}
      {confirm && (
        <div className="modal-backdrop" role="dialog" aria-modal="true">
          <div className="card" style={{ padding: 24, maxWidth: 480 }}>
            <h2 className="section-title" style={{ marginTop: 0 }}>
              {confirm.kind === "REIMPORT"
                ? "Confirmar reimportación"
                : confirm.kind === "SYNC"
                  ? "Confirmar sincronización"
                  : "Confirmar restauración"}
            </h2>
            <p className="inline-form-note">
              {confirm.kind === "REIMPORT"
                ? "Esto BORRA todos los datos actuales (incluidos los capturados a mano) y reconstruye desde los archivos de ingesta. Se creará un respaldo previo automático."
                : confirm.kind === "SYNC"
                  ? "Se creará un respaldo previo automático. Luego se importarán al sistema los registros nuevos del legado y se eliminarán los del legado que ya no aparezcan en los archivos de ingesta. Los datos capturados a mano NO se borran."
                  : `Esto sobreescribe la base de datos completa con “${confirm.file}”. Se recomienda crear un respaldo antes.`}
            </p>
            <label className="field">
              <span className="field-label">Escriba CONFIRMAR para continuar</span>
              <input
                className="input"
                value={confirmText}
                onChange={(e) => setConfirmText(e.target.value)}
                autoFocus
              />
            </label>
            <div className="form-actions">
              <button
                className={confirm.kind === "SYNC" ? "btn btn-primary" : "btn btn-danger"}
                type="button"
                disabled={confirmText !== "CONFIRMAR" || starting}
                onClick={runConfirmed}
              >
                {confirm.kind === "REIMPORT"
                  ? "Reimportar"
                  : confirm.kind === "SYNC"
                    ? "Sincronizar"
                    : "Restaurar"}
              </button>
              <button className="btn btn-outline" type="button" onClick={() => setConfirm(null)}>
                Cancelar
              </button>
            </div>
          </div>
        </div>
      )}
    </>
  );
}

/** "1:05" / "0:09" — remaining time, coarse on purpose. */
function formatEta(seconds: number): string {
  const s = Math.max(0, Math.round(seconds));
  if (s >= 3600) {
    const h = Math.floor(s / 3600);
    const m = Math.round((s % 3600) / 60);
    return `${h} h ${m} min`;
  }
  return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
}

/**
 * Live upload readout. The bar tracks bytes handed to the network; once those
 * are all sent the server still has to write the file, so the tail of the
 * upload reads "Procesando…" instead of sitting at 100%.
 */
function UploadProgressBar({ progress }: { progress: UploadProgress | null }) {
  const pct = progress?.fraction != null ? Math.round(progress.fraction * 100) : null;
  return (
    <div className="upload-progress">
      <div
        className={`progress-track${pct === null ? " progress-indeterminate" : ""}`}
        role="progressbar"
        aria-valuemin={0}
        aria-valuemax={100}
        aria-valuenow={pct ?? undefined}
      >
        <div className="progress-fill" style={{ width: `${pct ?? 100}%` }} />
      </div>
      <div className="upload-progress-stats mono">
        {progress === null ? (
          "Preparando…"
        ) : progress.finishing ? (
          `Procesando en el servidor… (${formatBytes(progress.total)} enviados)`
        ) : (
          <>
            {pct !== null && <strong>{pct}%</strong>}
            {progress.total > 0 && (
              <span>
                {formatBytes(progress.loaded)} / {formatBytes(progress.total)}
              </span>
            )}
            {progress.bytesPerSecond > 0 && (
              <span>{formatBytes(progress.bytesPerSecond)}/s</span>
            )}
            {progress.secondsRemaining !== null && progress.bytesPerSecond > 0 && (
              <span>faltan {formatEta(progress.secondsRemaining)}</span>
            )}
          </>
        )}
      </div>
    </div>
  );
}

function OpTile({
  title,
  desc,
  action,
  tone,
  disabled,
  onClick,
}: {
  title: string;
  desc: string;
  action: string;
  tone: "primary" | "danger" | "muted";
  disabled: boolean;
  onClick: () => void;
}) {
  const btnClass =
    tone === "danger" ? "btn btn-danger" : tone === "muted" ? "btn btn-outline" : "btn btn-primary";
  return (
    <div className="card" style={{ padding: 16 }}>
      <h3 className="section-title" style={{ fontSize: 15, margin: "0 0 4px" }}>
        {title}
      </h3>
      <p className="inline-form-note" style={{ minHeight: 48 }}>
        {desc}
      </p>
      <button className={btnClass} type="button" disabled={disabled} onClick={onClick}>
        {action}
      </button>
    </div>
  );
}

/**
 * Health of the read replica behind my.jorgecuadros.com.
 *
 * Worth a panel because the failure mode is silent: a replica whose SQL thread
 * has stopped keeps answering queries, just with data frozen at the moment it
 * stopped. Nothing on the customer site looks wrong — the balances are simply
 * out of date — so without this the only signal is a customer complaining.
 */
function ReplicationCard() {
  const [status, setStatus] = useState<ReplicationStatus | null>(null);
  const [failed, setFailed] = useState(false);

  const load = useCallback(() => {
    getReplicationStatus()
      .then((s) => {
        setStatus(s);
        setFailed(false);
      })
      .catch(() => setFailed(true));
  }, []);

  useEffect(() => {
    load();
    const t = setInterval(load, 30_000);
    return () => clearInterval(t);
  }, [load]);

  // Not configured is the normal state in dev and before cutover, so it is a
  // quiet note rather than an alarm — showing red here would train people to
  // ignore the card.
  if (failed || (status && !status.configured)) {
    return (
      <div className="card" style={{ padding: 20, marginBottom: 20 }}>
        <h2 className="section-title">Réplica del sitio de clientes</h2>
        <p className="inline-form-note">
          {failed
            ? "No se pudo consultar el estado de la réplica."
            : "No configurada en este entorno."}
        </p>
      </div>
    );
  }

  if (!status) {
    return (
      <div className="card" style={{ padding: 20, marginBottom: 20 }}>
        <h2 className="section-title">Réplica del sitio de clientes</h2>
        <p className="inline-form-note">Consultando…</p>
      </div>
    );
  }

  return (
    <div className="card" style={{ padding: 20, marginBottom: 20 }}>
      <div className="row-actions" style={{ justifyContent: "space-between" }}>
        <h2 className="section-title" style={{ margin: 0 }}>
          Réplica del sitio de clientes{" "}
          <span className={`badge ${status.healthy ? "badge-positive" : "badge-negative"}`}>
            {status.healthy ? "Replicando" : "Detenida"}
          </span>
        </h2>
        <button className="btn btn-ghost" type="button" onClick={load}>
          Actualizar
        </button>
      </div>

      {status.problem && (
        <div className="state-box state-error" style={{ marginTop: 12 }}>
          {status.problem}
        </div>
      )}

      <div className="kv-grid" style={{ paddingLeft: 0, paddingRight: 0 }}>
        <KV label="Servidor" value={status.host} />
        <KV label="Origen" value={status.sourceHost} />
        <KV label="Hilo de E/S" value={status.ioRunning} />
        <KV label="Hilo SQL" value={status.sqlRunning} />
        {/* Never render a null lag as "0 s": MySQL reports NULL whenever a
            thread is down, so the honest word is "unknown", not "up to date". */}
        <KV
          label="Retraso"
          value={status.secondsBehind === null ? "sin dato" : `${status.secondsBehind} s`}
        />
        <KV label="Consultado" value={formatDateTime(status.checkedAt)} />
      </div>
    </div>
  );
}

/** Matches the KV in the clientes/polizas/servicios detail pages. */
function KV({ label, value }: { label: string; value: string | null | undefined }) {
  return (
    <div>
      <div className="kv-label">{label}</div>
      <div className="kv-value">{value || "—"}</div>
    </div>
  );
}
