// Settings screen — Task 12, plus the AR milestone closeout's Invoicing
// section (db/migrations/0008_ar_closeout.sql's set_next_invoice_no()) and
// AP Task 5's Checks section (format/offsets + per-account next check
// numbers — db/migrations/0009_ap.sql's set_next_check_no()). Backups (GET
// /api/backup/status, POST /api/backup), a read-only Company section. No
// debit/credit language, errors in English (CLAUDE.md §9).

import { useEffect, useState } from "react";
import { useCompany } from "../lib/company";
import { useApiGet } from "../lib/useApiGet";
import { apiPost, apiPut, ApiError } from "../lib/api";
import { fmtHoursAgo } from "../lib/date";
import { PRODUCT_NAME } from "../lib/brand";
import { ErrorBanner } from "../components/ErrorBanner";
import { SalesTaxCard } from "./SalesTaxCard";
import type { NavTarget } from "../lib/useHashView";

interface VersionResponse {
  version: string;
}

// Task 1 of docs/superpowers/plans/2026-08-01-launch.md — mirrors
// HealthPanel.tsx's identical interface for GET /api/update-status
// (server/src/updateRoutes.ts). Not company data (see that file's own
// comment), fetched here purely so this "Updates" card can render current
// version / last-checked / an available-update card.
interface UpdateStatusResponse {
  current: string;
  latest: string | null;
  notes: string | null;
  downloadUrl: string | null;
  checkedAt: string | null;
  status: "disabled" | "unreachable" | "current" | "update-available";
}

interface BackupStatus {
  dir: string;
  lastBackup: { file: string; ageHours: number } | null;
  count: number;
}

interface InvoicingSettings {
  nextInvoiceNo: string;
  highestUsed: string | null;
}

interface CheckSettingsAccount {
  accountId: string;
  name: string;
  nextCheckNo: string;
  highestUsed: string | null;
}

interface CheckSettings {
  format: string;
  offsetX: string;
  offsetY: string;
  accounts: CheckSettingsAccount[];
}

/** One money account's own "next check number" mini-form — same
 *  input+Save shape as the Invoicing section's single field below, just
 *  repeated per account (a company can print checks from more than one
 *  bank account, unlike invoice numbering which is one counter company-wide). */
function CheckAccountRow({
  account,
  onSave,
}: {
  account: CheckSettingsAccount;
  onSave: (accountId: string, nextCheckNo: string) => Promise<void>;
}) {
  const [value, setValue] = useState(account.nextCheckNo);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [saved, setSaved] = useState(false);

  async function handleSave() {
    setSaving(true);
    setError(null);
    setSaved(false);
    try {
      await onSave(account.accountId, value);
      setSaved(true);
    } catch (err) {
      setError(err instanceof ApiError ? err.message : "Couldn't save that check number.");
    } finally {
      setSaving(false);
    }
  }

  return (
    <div className="row">
      <span className="n">
        {account.name}
        <span className="sub" style={{ display: "block" }}>
          Highest used: {account.highestUsed ?? "None yet"}
        </span>
      </span>
      <span className="a" style={{ display: "flex", alignItems: "center", gap: 8 }}>
        <input
          type="text"
          inputMode="numeric"
          value={value}
          onChange={(e) => {
            setValue(e.target.value);
            setSaved(false);
          }}
          style={{ width: 110, textAlign: "right" }}
        />
        <button type="button" className="btn" disabled={saving} onClick={() => void handleSave()}>
          {saving ? "Saving…" : "Save"}
        </button>
      </span>
      {error && (
        <div style={{ width: "100%", marginTop: 6 }}>
          <ErrorBanner message={error} onRetry={() => void handleSave()} />
        </div>
      )}
      {saved && !error && <p className="sub" style={{ width: "100%", marginTop: 6 }}>Saved.</p>}
    </div>
  );
}

interface BackupRunResult {
  file: string;
  bytes: number;
  durationMs: number;
}

function fmtBytes(bytes: number): string {
  if (bytes < 1024) return `${bytes} B`;
  const kb = bytes / 1024;
  if (kb < 1024) return `${kb.toFixed(1)} KB`;
  const mb = kb / 1024;
  if (mb < 1024) return `${mb.toFixed(1)} MB`;
  return `${(mb / 1024).toFixed(2)} GB`;
}

function baseName(filePath: string): string {
  const parts = filePath.split(/[\\/]/);
  return parts[parts.length - 1] ?? filePath;
}

export function SettingsScreen({ navigate }: { navigate: (target: NavTarget) => void }) {
  const { selectedCompany } = useCompany();
  // /api/backup/status isn't company-scoped (a pg_dump covers the whole
  // database) — the selected company id just satisfies useApiGet's "don't
  // fetch until we have something to scope to" gate; the header is ignored
  // server-side (server/src/backupRoutes.ts is registered outside /api/c/*).
  const { data, loading, error, retry } = useApiGet<BackupStatus>(
    "/api/backup/status", selectedCompany?.id ?? null,
  );

  const [running, setRunning] = useState(false);
  const [runError, setRunError] = useState<string | null>(null);
  const [runResult, setRunResult] = useState<BackupRunResult | null>(null);

  // -------------------------------------------------------------------------
  // Invoicing — next invoice number (GET/PUT /api/c/settings/invoicing)
  // -------------------------------------------------------------------------
  const {
    data: invoicing, loading: invoicingLoading, error: invoicingError, retry: retryInvoicing,
  } = useApiGet<InvoicingSettings>("/api/c/settings/invoicing", selectedCompany?.id ?? null);

  const [nextInvoiceNoInput, setNextInvoiceNoInput] = useState("");
  // Tracks which company the input has been seeded for, so a fetch that
  // completes after the user has already started editing (e.g. the refetch
  // this screen triggers right after a successful save) never clobbers what
  // they typed — only a genuine company switch re-seeds the field. Mirrors
  // useApiGet's own prevKeyRef "only reset on a real resource change" pattern.
  const [seededForCompany, setSeededForCompany] = useState<string | null>(null);
  const [savingInvoiceNo, setSavingInvoiceNo] = useState(false);
  const [invoiceNoSaveError, setInvoiceNoSaveError] = useState<string | null>(null);
  const [invoiceNoSaved, setInvoiceNoSaved] = useState(false);

  useEffect(() => {
    if (invoicing && seededForCompany !== (selectedCompany?.id ?? null)) {
      setNextInvoiceNoInput(invoicing.nextInvoiceNo);
      setSeededForCompany(selectedCompany?.id ?? null);
      setInvoiceNoSaveError(null);
      setInvoiceNoSaved(false);
    }
  }, [invoicing, selectedCompany?.id, seededForCompany]);

  async function handleSaveNextInvoiceNo() {
    setInvoiceNoSaveError(null);
    setInvoiceNoSaved(false);
    setSavingInvoiceNo(true);
    try {
      const res = await apiPut<InvoicingSettings>(
        "/api/c/settings/invoicing",
        { nextInvoiceNo: nextInvoiceNoInput },
        selectedCompany?.id,
      );
      setNextInvoiceNoInput(res.nextInvoiceNo);
      setInvoiceNoSaved(true);
      retryInvoicing();
    } catch (err) {
      setInvoiceNoSaveError(
        err instanceof ApiError ? err.message : "Couldn't save the next invoice number.",
      );
    } finally {
      setSavingInvoiceNo(false);
    }
  }

  // -------------------------------------------------------------------------
  // Checks — format/offsets + per-account next check numbers
  // (GET/PUT /api/c/settings/checks)
  // -------------------------------------------------------------------------
  const {
    data: checkSettings, loading: checkSettingsLoading, error: checkSettingsError, retry: retryCheckSettings,
  } = useApiGet<CheckSettings>("/api/c/settings/checks", selectedCompany?.id ?? null);

  const [formatInput, setFormatInput] = useState<string>("three_per_page");
  const [offsetXInput, setOffsetXInput] = useState("0.00");
  const [offsetYInput, setOffsetYInput] = useState("0.00");
  const [seededChecksForCompany, setSeededChecksForCompany] = useState<string | null>(null);
  const [savingCheckDefaults, setSavingCheckDefaults] = useState(false);
  const [checkDefaultsSaveError, setCheckDefaultsSaveError] = useState<string | null>(null);
  const [checkDefaultsSaved, setCheckDefaultsSaved] = useState(false);

  useEffect(() => {
    if (checkSettings && seededChecksForCompany !== (selectedCompany?.id ?? null)) {
      setFormatInput(checkSettings.format);
      setOffsetXInput(checkSettings.offsetX);
      setOffsetYInput(checkSettings.offsetY);
      setSeededChecksForCompany(selectedCompany?.id ?? null);
      setCheckDefaultsSaveError(null);
      setCheckDefaultsSaved(false);
    }
  }, [checkSettings, selectedCompany?.id, seededChecksForCompany]);

  async function handleSaveCheckDefaults() {
    setCheckDefaultsSaveError(null);
    setCheckDefaultsSaved(false);
    setSavingCheckDefaults(true);
    try {
      await apiPut<CheckSettings>(
        "/api/c/settings/checks",
        { format: formatInput, offsetX: offsetXInput, offsetY: offsetYInput },
        selectedCompany?.id,
      );
      setCheckDefaultsSaved(true);
      retryCheckSettings();
    } catch (err) {
      setCheckDefaultsSaveError(
        err instanceof ApiError ? err.message : "Couldn't save check settings.",
      );
    } finally {
      setSavingCheckDefaults(false);
    }
  }

  async function handleSaveNextCheckNo(accountId: string, nextCheckNo: string) {
    await apiPut<CheckSettings>(
      "/api/c/settings/checks",
      { accounts: [{ accountId, nextCheckNo }] },
      selectedCompany?.id,
    );
    retryCheckSettings();
  }

  // -------------------------------------------------------------------------
  // Version + updates — GET /api/version, GET/POST /api/update-status(/check)
  // Neither is company-scoped (see the interface comments above); selectedCompany?.id
  // just satisfies useApiGet's "don't fetch until we have something to scope
  // to" gate, same as the Backups section above.
  // -------------------------------------------------------------------------
  const { data: versionData } = useApiGet<VersionResponse>(
    "/api/version", selectedCompany?.id ?? null,
  );
  const {
    data: updateStatus, loading: updateStatusLoading, error: updateStatusError, retry: retryUpdateStatus,
  } = useApiGet<UpdateStatusResponse>("/api/update-status", selectedCompany?.id ?? null);

  const [checkingNow, setCheckingNow] = useState(false);
  const [checkNowError, setCheckNowError] = useState<string | null>(null);

  async function handleCheckNow() {
    setCheckingNow(true);
    setCheckNowError(null);
    try {
      await apiPost<UpdateStatusResponse>("/api/update-status/check");
      retryUpdateStatus();
    } catch (err) {
      setCheckNowError(err instanceof ApiError ? err.message : "Couldn't check for updates.");
    } finally {
      setCheckingNow(false);
    }
  }

  async function handleBackupNow() {
    setRunning(true);
    setRunError(null);
    setRunResult(null);
    try {
      const res = await apiPost<BackupRunResult>("/api/backup");
      setRunResult(res);
      retry();
    } catch (err) {
      setRunError(err instanceof ApiError ? err.message : "Couldn't run the backup.");
    } finally {
      setRunning(false);
    }
  }

  return (
    <div className="wrap">
      <header className="top">
        <h1>Settings</h1>
      </header>

      <div className="settings-cards">
        <div className="card">
          <h2>Backups</h2>
          <p className="sub">
            A full backup runs automatically once a day; the last 30 are kept. You can also run one right
            now.
          </p>

          {loading && !data && <p className="msg-inline">Checking&hellip;</p>}
          {error && !data && <ErrorBanner message={error} onRetry={retry} />}

          {data && (
            <div className="rows">
              <div className="row">
                <span className="n">Backup folder</span>
                <span className="a">{data.dir}</span>
              </div>
              <div className="row">
                <span className="n">Last backup</span>
                <span className="a">
                  {data.lastBackup
                    ? `${baseName(data.lastBackup.file)} · ${fmtHoursAgo(data.lastBackup.ageHours)} ago`
                    : "Never"}
                </span>
              </div>
              <div className="row">
                <span className="n">Backups kept</span>
                <span className="a">{data.count}</span>
              </div>
            </div>
          )}

          {runError && (
            <div style={{ marginTop: 12 }}>
              <ErrorBanner message={runError} onRetry={handleBackupNow} />
            </div>
          )}
          {runResult && (
            <div className="success-banner" style={{ marginTop: 12 }}>
              <span>
                Backup complete — {baseName(runResult.file)} ({fmtBytes(runResult.bytes)}).
              </span>
              <button type="button" onClick={() => setRunResult(null)}>
                Dismiss
              </button>
            </div>
          )}

          <div className="wizard-actions">
            <button type="button" className="btn" disabled={running} onClick={() => void handleBackupNow()}>
              {running ? "Backing up…" : "Back up now"}
            </button>
          </div>

          <p className="sub" style={{ marginTop: 14, marginBottom: 0 }}>
            Backups live in the folder shown above (override with the <code>BACKUP_DIR</code> environment
            variable). To restore one onto a new machine, see <code>RESTORE.md</code> in the project
            folder — restore into a fresh, empty database, never over the live one.
          </p>
        </div>

        <div className="card">
          <h2>Invoicing</h2>
          <p className="sub">
            The next invoice number this company will assign. Raising it skips ahead — handy right after
            bringing in history from another system — but it can never be set to a number already used.
          </p>

          {invoicingLoading && !invoicing && <p className="msg-inline">Checking&hellip;</p>}
          {invoicingError && !invoicing && <ErrorBanner message={invoicingError} onRetry={retryInvoicing} />}

          {invoicing && (
            <div className="rows">
              <div className="row">
                <span className="n">Highest invoice number used</span>
                <span className="a">{invoicing.highestUsed ?? "None yet"}</span>
              </div>
              <div className="row">
                <span className="n">Next invoice number</span>
                <span className="a">
                  <input
                    type="text"
                    inputMode="numeric"
                    value={nextInvoiceNoInput}
                    onChange={(e) => {
                      setNextInvoiceNoInput(e.target.value);
                      setInvoiceNoSaved(false);
                    }}
                    style={{ width: 120, textAlign: "right" }}
                  />
                </span>
              </div>
            </div>
          )}

          {invoiceNoSaveError && (
            <div style={{ marginTop: 12 }}>
              <ErrorBanner message={invoiceNoSaveError} onRetry={() => void handleSaveNextInvoiceNo()} />
            </div>
          )}
          {invoiceNoSaved && !invoiceNoSaveError && (
            <div className="success-banner" style={{ marginTop: 12 }}>
              <span>Saved — the next invoice issued will be #{nextInvoiceNoInput}.</span>
            </div>
          )}

          <div className="wizard-actions">
            <button
              type="button"
              className="btn"
              disabled={savingInvoiceNo || !invoicing}
              onClick={() => void handleSaveNextInvoiceNo()}
            >
              {savingInvoiceNo ? "Saving…" : "Save"}
            </button>
          </div>
        </div>

        <div className="card">
          <h2>Checks</h2>
          <p className="sub">
            The stock you print checks on, the alignment nudge for that printer, and each money account's own
            next check number. Raising a next number skips ahead, but it can never be set to a number already
            used on that account.
          </p>

          {checkSettingsLoading && !checkSettings && <p className="msg-inline">Checking&hellip;</p>}
          {checkSettingsError && !checkSettings && (
            <ErrorBanner message={checkSettingsError} onRetry={retryCheckSettings} />
          )}

          {checkSettings && (
            <>
              <div className="field">
                <label>Check stock</label>
                <div className="method-choice">
                  <label className="method-option">
                    <input
                      type="radio"
                      name="settings-check-format"
                      checked={formatInput === "three_per_page"}
                      onChange={() => {
                        setFormatInput("three_per_page");
                        setCheckDefaultsSaved(false);
                      }}
                    />
                    <span>3 checks per page</span>
                  </label>
                  <label className="method-option">
                    <input
                      type="radio"
                      name="settings-check-format"
                      checked={formatInput === "voucher"}
                      onChange={() => {
                        setFormatInput("voucher");
                        setCheckDefaultsSaved(false);
                      }}
                    />
                    <span>Voucher (check + 2 stubs)</span>
                  </label>
                </div>
              </div>

              <div className="rows">
                <div className="row">
                  <span className="n">Horizontal offset (mm)</span>
                  <span className="a">
                    <input
                      type="text"
                      inputMode="decimal"
                      value={offsetXInput}
                      onChange={(e) => {
                        setOffsetXInput(e.target.value);
                        setCheckDefaultsSaved(false);
                      }}
                      style={{ width: 90, textAlign: "right" }}
                    />
                  </span>
                </div>
                <div className="row">
                  <span className="n">Vertical offset (mm)</span>
                  <span className="a">
                    <input
                      type="text"
                      inputMode="decimal"
                      value={offsetYInput}
                      onChange={(e) => {
                        setOffsetYInput(e.target.value);
                        setCheckDefaultsSaved(false);
                      }}
                      style={{ width: 90, textAlign: "right" }}
                    />
                  </span>
                </div>
              </div>

              {checkDefaultsSaveError && (
                <div style={{ marginTop: 12 }}>
                  <ErrorBanner message={checkDefaultsSaveError} onRetry={() => void handleSaveCheckDefaults()} />
                </div>
              )}
              {checkDefaultsSaved && !checkDefaultsSaveError && (
                <div className="success-banner" style={{ marginTop: 12 }}>
                  <span>Saved.</span>
                </div>
              )}

              <div className="wizard-actions">
                <button type="button" className="btn" disabled={savingCheckDefaults} onClick={() => void handleSaveCheckDefaults()}>
                  {savingCheckDefaults ? "Saving…" : "Save"}
                </button>
              </div>

              {checkSettings.accounts.length > 0 && (
                <div className="rows" style={{ marginTop: 18, paddingTop: 14, borderTop: "1px solid var(--grid)" }}>
                  {checkSettings.accounts.map((a) => (
                    <CheckAccountRow key={a.accountId} account={a} onSave={handleSaveNextCheckNo} />
                  ))}
                </div>
              )}
            </>
          )}
        </div>

        <SalesTaxCard />

        <div className="card">
          <h2>Updates</h2>
          <p className="sub">
            {PRODUCT_NAME} checks for a newer version once a day. Nothing downloads or installs itself — you
            decide, and it never happens without you clicking a button.
          </p>

          {updateStatusLoading && !updateStatus && <p className="msg-inline">Checking&hellip;</p>}
          {updateStatusError && !updateStatus && <ErrorBanner message={updateStatusError} onRetry={retryUpdateStatus} />}

          {updateStatus && (
            <div className="rows">
              <div className="row">
                <span className="n">Current version</span>
                <span className="a">{versionData?.version ?? updateStatus.current}</span>
              </div>
              <div className="row">
                <span className="n">Last checked</span>
                <span className="a">
                  {updateStatus.checkedAt
                    ? `${fmtHoursAgo((Date.now() - new Date(updateStatus.checkedAt).getTime()) / 3_600_000)} ago`
                    : updateStatus.status === "disabled"
                      ? "Not configured"
                      : "Never"}
                </span>
              </div>
            </div>
          )}

          {checkNowError && (
            <div style={{ marginTop: 12 }}>
              <ErrorBanner message={checkNowError} onRetry={() => void handleCheckNow()} />
            </div>
          )}

          <div className="wizard-actions">
            <button type="button" className="btn" disabled={checkingNow} onClick={() => void handleCheckNow()}>
              {checkingNow ? "Checking…" : "Check now"}
            </button>
          </div>

          {updateStatus?.status === "update-available" && updateStatus.latest && (
            <div className="success-banner" style={{ marginTop: 14, flexDirection: "column", alignItems: "flex-start", gap: 8 }}>
              <span>
                <strong>
                  {PRODUCT_NAME} {updateStatus.latest} is available
                </strong>
                {" "}(you have {updateStatus.current}).
              </span>
              {updateStatus.notes && (
                <p className="sub" style={{ margin: 0, whiteSpace: "pre-wrap" }}>
                  {updateStatus.notes}
                </p>
              )}
              {updateStatus.downloadUrl && (
                <a
                  className="btn"
                  href={updateStatus.downloadUrl}
                  target="_blank"
                  rel="noopener noreferrer"
                >
                  Download the update
                </a>
              )}
              <p className="sub" style={{ margin: 0 }}>
                Updating takes about five minutes and keeps all your data — here's how: download the zip,
                unzip it next to this folder, copy your <code>.env</code> file and <code>imports/</code> folder
                across, then run <code>npm install</code> and <code>npm run app</code> from the new folder.
                Migrations apply automatically. Full step-by-step guidance is in <code>UPDATING.md</code> in
                the project folder.
              </p>
            </div>
          )}

          <p className="sub" style={{ marginTop: 14, marginBottom: 0 }}>
            No update feed configured yet, unreachable, or already current? This card just shows nothing extra
            — {PRODUCT_NAME} works exactly the same either way.
          </p>
        </div>

        <div className="card">
          <h2>Company</h2>
          <div className="rows">
            <div className="row">
              <span className="n">Name</span>
              <span className="a">{selectedCompany?.name ?? "—"}</span>
            </div>
            <div className="row">
              <span className="n">Company id</span>
              <span className="a">{selectedCompany?.id ?? "—"}</span>
            </div>
            <div className="row">
              <span className="n">App version</span>
              <span className="a">{versionData?.version ?? "—"}</span>
            </div>
          </div>
          <p className="sub" style={{ marginTop: 14, marginBottom: 0 }}>
            {/* Same stated intent as the switcher's "+ Add company…", so the
                same route: #/setup?new=1 lands on the name step instead of
                offering to resume the company this Settings page is about. */}
            <button
              type="button"
              className="acct-link"
              onClick={() => navigate({ view: "setup", intent: "new-company" })}
            >
              + Add another company
            </button>
          </p>
        </div>
      </div>
    </div>
  );
}
