import type { CompanyOption, Credential, InvoiceShelfAccount } from './useInvoiceShelfData';
import { useState } from 'react';
import { Building2, Check, CheckCircle2, Loader2, Trash2, TriangleAlert } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
  parseAccountError,
  useAccountCompanies,
  useInvoiceShelfAccountActions,
  useInvoiceShelfAccounts,
  useInvoiceShelfHealth,
} from './useInvoiceShelfData';

// Connecting InvoiceShelf instances to Officer, from the app.
//
// This screen is BOTH the setup wizard and the permanent settings page: InvoicesView renders it in place of
// whatever section the URL asks for while nothing is connected, and /invoices/settings renders it for good.
//
// The company picker is the part that has no equivalent in the photos version, and it is not a nicety: the
// `company` header scopes almost every InvoiceShelf route, and a wrong or missing value does NOT error — it
// silently returns another company's books. So the account pins one, chosen once, visibly. Two accounts on
// the same URL with the same token and different companies is an ordinary thing to have, which is why the
// LABEL is what has to be unique.
//
// The credential is never displayed, because Officer cannot display it: the token is encrypted at rest and
// the sidecar's GET has no field that could carry it back, and the password is not stored at all.

const HINT = 'text-[11px] leading-relaxed text-muted-foreground';

/** InvoiceShelf's own default — its production compose publishes 8090, and the sidecar dials from this machine. */
const DEFAULT_URL = 'http://localhost:8090';

const URL_HINT =
  "The instance's base URL, without /api. Officer reaches it from the server, not from this browser — so " +
  'localhost here means the machine Officer runs on, and a local instance needs no TLS.';

const SIGN_IN_HINT =
  'Your InvoiceShelf login. It is used once, by the sidecar, to mint an API token — the password is never ' +
  'stored and never reaches the browser again. Each sign-in creates a new token in InvoiceShelf; revoke old ' +
  'ones there.';

const TOKEN_HINT =
  'A Sanctum token you already have. Paste it whole, including the leading "1|". It can read and write the ' +
  'entire books, so Officer stores it encrypted and never shows it again.';

type FieldProps = {
  label: string;
  hint?: string;
  value: string;
  onChange: (value: string) => void;
  placeholder: string;
  type?: string;
  autoFocus?: boolean;
};

const Field = ({ label, hint, value, onChange, placeholder, type, autoFocus }: FieldProps) => (
  <label className="flex flex-col gap-1.5">
    <span className="text-xs font-medium">{label}</span>
    <Input
      value={value}
      onChange={(ev) => onChange(ev.target.value)}
      placeholder={placeholder}
      type={type}
      autoFocus={autoFocus}
      autoComplete="off"
      spellCheck={false}
    />
    {hint && <span className={HINT}>{hint}</span>}
  </label>
);

// ── credentials ──────────────────────────────────────────────────────────────────────────────────
//
// Two ways to prove the same thing, so they share one piece of state rather than two forms. Sign-in is the
// default because InvoiceShelf 2.4.2 ships no screen that issues tokens — before this, connecting Officer
// meant running a curl command against /auth/login by hand, which is exactly what the sidecar now does.

type CredentialMode = 'login' | 'token';

function useCredential() {
  const [mode, setMode] = useState<CredentialMode>('login');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [token, setToken] = useState('');

  const filled = mode === 'token' ? !!token.trim() : !!email.trim() && !!password;
  const payload: Credential = mode === 'token' ? { token: token.trim() } : { email: email.trim(), password };

  const reset = () => {
    setEmail('');
    setPassword('');
    setToken('');
  };

  return { mode, setMode, email, setEmail, password, setPassword, token, setToken, filled, payload, reset };
}

type CredentialState = ReturnType<typeof useCredential>;

const TAB = 'rounded-md px-2 py-1 text-[11px] transition-colors';

const CredentialFields = ({ cred, autoFocus }: { cred: CredentialState; autoFocus?: boolean }) => (
  <div className="flex flex-col gap-3">
    <div className="flex items-center gap-1">
      <span className="text-xs font-medium">Credential</span>
      <div className="ml-auto flex items-center gap-1 rounded-lg bg-muted p-0.5">
        <button
          type="button"
          onClick={() => cred.setMode('login')}
          className={`${TAB} ${cred.mode === 'login' ? 'bg-background shadow-sm' : 'text-muted-foreground'}`}
        >
          Sign in
        </button>
        <button
          type="button"
          onClick={() => cred.setMode('token')}
          className={`${TAB} ${cred.mode === 'token' ? 'bg-background shadow-sm' : 'text-muted-foreground'}`}
        >
          API token
        </button>
      </div>
    </div>

    {cred.mode === 'login' ? (
      <>
        <Field
          label="Email"
          value={cred.email}
          onChange={cred.setEmail}
          placeholder="you@example.com"
          type="email"
          autoFocus={autoFocus}
        />
        <Field
          label="Password"
          value={cred.password}
          onChange={cred.setPassword}
          placeholder="••••••••"
          type="password"
          hint={SIGN_IN_HINT}
        />
      </>
    ) : (
      <Field
        label="API token"
        value={cred.token}
        onChange={cred.setToken}
        placeholder="1|xxxxxxxx"
        type="password"
        hint={TOKEN_HINT}
        autoFocus={autoFocus}
      />
    )}
  </div>
);

type CompanyPickerProps = {
  companies: CompanyOption[];
  value: number | null;
  onChange: (id: number) => void;
  disabled?: boolean;
};

/** A plain select — the list is short, and a combobox would be machinery for four options. */
const CompanyPicker = ({ companies, value, onChange, disabled }: CompanyPickerProps) => (
  <select
    value={value ?? ''}
    disabled={disabled}
    onChange={(ev) => onChange(Number(ev.target.value))}
    className="h-8 rounded-md border bg-background px-2 text-xs"
  >
    <option value="" disabled>
      Choose a company…
    </option>
    {companies.map((company) => (
      <option key={company.id} value={company.id}>
        {company.name ?? `Company ${company.id}`}
      </option>
    ))}
  </select>
);

type AccountRowProps = { account: InvoiceShelfAccount };

/**
 * One stored account. The active one carries the live health line, because health only ever describes the
 * account actually being used — showing a status next to the others would be inventing one.
 */
const AccountRow = ({ account }: AccountRowProps) => {
  const { data: health, refetch: recheck, isFetching: checking } = useInvoiceShelfHealth();
  const { edit, activate, remove } = useInvoiceShelfAccountActions();

  const cred = useCredential();
  const [error, setError] = useState<string | null>(null);
  const [replacing, setReplacing] = useState(false);
  const [changingCompany, setChangingCompany] = useState(false);
  // Removing an account throws away a token Officer can never show again, so the bin asks once.
  const [confirming, setConfirming] = useState(false);

  const companies = useAccountCompanies(changingCompany ? account.id : null);

  const run = async (action: Promise<unknown>) => {
    setError(null);
    try {
      await action;
      cred.reset();
      setReplacing(false);
      setChangingCompany(false);
    } catch (err) {
      setError(parseAccountError(err).message);
    }
  };

  return (
    <div className={`flex flex-col gap-2 rounded-lg border p-4 text-xs ${account.isActive ? 'border-primary/40' : ''}`}>
      <div className="flex items-center gap-2">
        {account.isActive ? <Check className="h-4 w-4 shrink-0 text-primary" /> : <span className="h-4 w-4 shrink-0" />}
        <span className="truncate font-medium">{account.label}</span>
        {account.isActive && (
          <span className="rounded bg-primary/10 px-1.5 py-0.5 text-[10px] text-primary">in use</span>
        )}
        <div className="ml-auto flex items-center gap-1">
          {!account.isActive && (
            <Button
              variant="ghost"
              size="sm"
              className="h-7"
              disabled={activate.isPending}
              onClick={() => void run(activate.mutateAsync(account.id))}
            >
              Use
            </Button>
          )}
          {account.isActive && (
            <Button variant="ghost" size="sm" className="h-7" onClick={() => void recheck()}>
              {checking ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : 'Test'}
            </Button>
          )}
          <Button
            variant="ghost"
            size="sm"
            className={`h-7 ${confirming ? 'text-destructive' : 'text-muted-foreground hover:text-destructive'}`}
            disabled={remove.isPending}
            onClick={() => {
              if (!confirming) return setConfirming(true);
              setConfirming(false);
              void run(remove.mutateAsync(account.id));
            }}
          >
            {confirming ? 'Remove?' : <Trash2 className="h-3.5 w-3.5" />}
          </Button>
        </div>
      </div>

      <p className="truncate text-muted-foreground">{account.url}</p>

      <div className="flex items-center gap-2 text-muted-foreground">
        <Building2 className="h-3.5 w-3.5 shrink-0" />
        {changingCompany ? (
          <>
            {companies.isLoading ? (
              <Loader2 className="h-3.5 w-3.5 animate-spin" />
            ) : (
              <CompanyPicker
                companies={companies.data?.companies ?? []}
                value={account.companyId}
                disabled={edit.isPending}
                onChange={(companyId) => void run(edit.mutateAsync({ id: account.id, companyId }))}
              />
            )}
            <Button variant="ghost" size="sm" className="h-7" onClick={() => setChangingCompany(false)}>
              Cancel
            </Button>
          </>
        ) : (
          <>
            <span className="truncate">{account.companyName ?? `Company ${account.companyId ?? '?'}`}</span>
            <Button variant="ghost" size="sm" className="h-7" onClick={() => setChangingCompany(true)}>
              Change
            </Button>
          </>
        )}
      </div>

      {account.isActive && (
        <div className="flex items-center gap-1.5 text-muted-foreground">
          {health?.ok ? (
            <CheckCircle2 className="h-3.5 w-3.5 text-emerald-500" />
          ) : (
            <TriangleAlert className="h-3.5 w-3.5 text-amber-500" />
          )}
          <span>{health?.ok ? `InvoiceShelf ${health.version ?? '?'}` : (health?.error ?? 'not checked yet')}</span>
        </div>
      )}

      {replacing ? (
        <div className="flex flex-col gap-3 rounded-lg border p-3">
          <CredentialFields cred={cred} autoFocus />
          <div className="flex items-center gap-2">
            <Button
              size="sm"
              className="h-8"
              disabled={!cred.filled || edit.isPending}
              onClick={() => void run(edit.mutateAsync({ id: account.id, ...cred.payload }))}
            >
              {edit.isPending && <Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />}
              Save
            </Button>
            <Button
              variant="ghost"
              size="sm"
              className="h-8"
              onClick={() => {
                cred.reset();
                setReplacing(false);
              }}
            >
              Cancel
            </Button>
          </div>
        </div>
      ) : (
        <button
          type="button"
          onClick={() => setReplacing(true)}
          className="self-start text-[11px] text-muted-foreground underline-offset-2 hover:text-foreground hover:underline"
        >
          Replace credential
        </button>
      )}

      {error && <p className="text-destructive">{error}</p>}
    </div>
  );
};

export const ConnectionSection = () => {
  const { data, isLoading } = useInvoiceShelfAccounts();
  const { add } = useInvoiceShelfAccountActions();

  const cred = useCredential();
  const [label, setLabel] = useState('');
  const [url, setUrl] = useState(DEFAULT_URL);
  const [error, setError] = useState<string | null>(null);
  // Populated only when the instance answers with more than one company — see parseAccountError.
  const [choices, setChoices] = useState<CompanyOption[]>([]);
  const [companyId, setCompanyId] = useState<number | null>(null);

  const accounts = data?.accounts ?? [];
  const hasAccounts = accounts.length > 0;

  const submit = async (withCompany: number | null) => {
    setError(null);
    if (!url.trim()) return setError('The InvoiceShelf URL is required');
    if (!cred.filled) {
      return setError(cred.mode === 'token' ? 'An API token is required' : 'An email and password are required');
    }

    try {
      await add.mutateAsync({ label: label.trim(), url: url.trim(), companyId: withCompany, ...cred.payload });
      setLabel('');
      setUrl(DEFAULT_URL);
      cred.reset();
      setChoices([]);
      setCompanyId(null);
    } catch (err) {
      const parsed = parseAccountError(err);
      // A 409 is not a failure — the credential can act for several companies and one has to be picked. Keep
      // the form filled in and show the list rather than making them type it all again.
      if (parsed.needsChoice && parsed.companies.length) {
        setChoices(parsed.companies);
        setCompanyId(parsed.companies[0]?.id ?? null);
        setError(null);
        return;
      }
      setError(parsed.message);
    }
  };

  return (
    <div className="h-full overflow-y-auto">
      <div className="mx-auto flex max-w-xl flex-col gap-4 p-6">
        <div className="flex items-center gap-3">
          <div className="flex h-9 w-9 items-center justify-center rounded-xl bg-sky-500/15 text-sky-500">
            <Building2 className="h-5 w-5" />
          </div>
          <div>
            <h2 className="text-sm font-semibold">{hasAccounts ? 'InvoiceShelf accounts' : 'Connect InvoiceShelf'}</h2>
            <p className="text-xs text-muted-foreground">
              {hasAccounts
                ? 'Officer stores each instance and its token encrypted, for this account only. One is in use at a time.'
                : 'Invoices needs an InvoiceShelf instance and a login before it can show anything.'}
            </p>
          </div>
        </div>

        {!isLoading && accounts.map((account) => <AccountRow key={account.id} account={account} />)}

        <form
          onSubmit={(ev) => {
            ev.preventDefault();
            void submit(companyId);
          }}
          className="flex flex-col gap-3 rounded-lg border p-4"
        >
          <p className="text-xs font-medium">{hasAccounts ? 'Add another account' : 'Add an account'}</p>
          <Field
            label="Label"
            value={label}
            onChange={setLabel}
            placeholder="Optional — defaults to the company name"
            hint="What the switcher calls this account. Two accounts cannot share a label; two can share a URL, a login and everything but the company."
            autoFocus={hasAccounts}
          />
          <Field
            label="InvoiceShelf URL"
            value={url}
            onChange={setUrl}
            placeholder={DEFAULT_URL}
            hint={URL_HINT}
            autoFocus={!hasAccounts}
          />
          <CredentialFields cred={cred} />

          {choices.length > 0 && (
            <label className="flex flex-col gap-1.5">
              <span className="text-xs font-medium">Company</span>
              <CompanyPicker companies={choices} value={companyId} onChange={setCompanyId} disabled={add.isPending} />
              <span className={HINT}>
                This login can act for more than one company. Whichever you pick is pinned to this account — add a
                second account for the other one.
              </span>
            </label>
          )}

          {error && <p className="text-xs text-destructive">{error}</p>}

          <div className="flex items-center gap-2">
            <Button type="submit" size="sm" disabled={add.isPending || (choices.length > 0 && companyId == null)}>
              {add.isPending && <Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />}
              {add.isPending ? 'Verifying…' : hasAccounts ? 'Add account' : 'Connect'}
            </Button>
            {add.isPending && <span className={HINT}>Checking the instance and the credential…</span>}
          </div>
        </form>
      </div>
    </div>
  );
};
