import { useCoreServices } from '@learnitive/context';
import {
  LEARNITIVE_GIT_AUTHOR_EMAIL_STORAGE_KEY,
  LEARNITIVE_GIT_AUTHOR_NAME_STORAGE_KEY,
  isDefaultPublicGitCorsProxy,
  readBrowserGitSettings,
  writeBrowserGitCorsProxySetting,
  type ProjectGitCommit,
  type ProjectGitDiff,
  type ProjectGitRemote,
  type ProjectGitStatus,
} from '@learnitive/service-core';
import {
  Button,
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  Input,
  Label,
  toast,
} from '@learnitive/ui-components';
import { GitBranch, RefreshCw } from 'lucide-react';
import React from 'react';

type GitTab = 'changes' | 'history' | 'branches' | 'remote';

const EMPTY_STATUS: ProjectGitStatus = { staged: [], unstaged: [] };

function useLocalSetting(key: string, fallback = '') {
  const [value, setValue] = React.useState(() => {
    if (typeof window === 'undefined') return fallback;
    return window.localStorage.getItem(key) ?? fallback;
  });
  React.useEffect(() => {
    window.localStorage.setItem(key, value);
  }, [key, value]);
  return [value, setValue] as const;
}

function useGitCorsProxySetting() {
  const [value, setValue] = React.useState(() => readBrowserGitSettings().corsProxy);
  React.useEffect(() => {
    writeBrowserGitCorsProxySetting(value);
  }, [value]);
  return [value, setValue] as const;
}

export function GitPanel({ wsName }: { wsName: string }) {
  const { projectGit } = useCoreServices();
  const [open, setOpen] = React.useState(false);
  const [tab, setTab] = React.useState<GitTab>('changes');
  const [repository, setRepository] = React.useState(false);
  const [status, setStatus] = React.useState<ProjectGitStatus>(EMPTY_STATUS);
  const [history, setHistory] = React.useState<readonly ProjectGitCommit[]>([]);
  const [diff, setDiff] = React.useState<ProjectGitDiff>();
  const [branches, setBranches] = React.useState<readonly string[]>([]);
  const [remotes, setRemotes] = React.useState<readonly ProjectGitRemote[]>([]);
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState<string>();
  const [commitMessage, setCommitMessage] = React.useState('');
  const [branchName, setBranchName] = React.useState('');
  const [remoteName, setRemoteName] = React.useState('origin');
  const [remoteUrl, setRemoteUrl] = React.useState('');
  const [token, setToken] = React.useState('');
  const [authorName, setAuthorName] = useLocalSetting(LEARNITIVE_GIT_AUTHOR_NAME_STORAGE_KEY);
  const [authorEmail, setAuthorEmail] = useLocalSetting(
    LEARNITIVE_GIT_AUTHOR_EMAIL_STORAGE_KEY,
  );
  const [corsProxy, setCorsProxy] = useGitCorsProxySetting();

  const [publicProxyAuthConfirmed, setPublicProxyAuthConfirmed] = React.useState(false);
  React.useEffect(() => {
    setPublicProxyAuthConfirmed(false);
  }, [token, corsProxy]);

  const assertAuthenticatedProxyAllowed = () => {
    if (
      token.trim() &&
      isDefaultPublicGitCorsProxy(corsProxy) &&
      !publicProxyAuthConfirmed
    ) {
      throw new Error(
        'Authentication would be sent through the public CORS proxy. Confirm the proxy warning first or use a trusted/self-hosted proxy.',
      );
    }
  };

  const refresh = React.useCallback(async () => {
    setError(undefined);
    const isRepository = await projectGit.isRepository(wsName);
    setRepository(isRepository);
    if (!isRepository) {
      setStatus(EMPTY_STATUS);
      setHistory([]);
      setBranches([]);
      setRemotes([]);
      return;
    }
    const [nextStatus, nextHistory, nextBranches, nextRemotes] =
      await Promise.all([
        projectGit.status(wsName),
        projectGit.log(wsName),
        projectGit.listBranches(wsName),
        projectGit.listRemotes(wsName),
      ]);
    setStatus(nextStatus);
    setHistory(nextHistory);
    setBranches(nextBranches);
    setRemotes(nextRemotes);
  }, [projectGit, wsName]);

  React.useEffect(() => {
    if (!open) return;
    setBusy(true);
    void refresh()
      .catch((reason) =>
        setError(String(reason instanceof Error ? reason.message : reason)),
      )
      .finally(() => setBusy(false));
  }, [open, refresh]);

  const run = async (operation: () => Promise<void>, success?: string) => {
    setBusy(true);
    setDiff(undefined);
    setError(undefined);
    try {
      await operation();
      await refresh();
      if (success) toast.success(success);
    } catch (reason) {
      const message = reason instanceof Error ? reason.message : String(reason);
      setError(message);
      toast.error(message);
    } finally {
      setBusy(false);
    }
  };

  const previewDiff = async (
    path: ProjectGitStatus['unstaged'][number]['path'],
  ) => {
    setBusy(true);
    setError(undefined);
    try {
      setDiff(await projectGit.diff(wsName, path));
    } catch (reason) {
      const message = reason instanceof Error ? reason.message : String(reason);
      setError(message);
      toast.error(message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <>
      <Button
        className="desktop-titlebar-no-drag h-7 gap-1.5 px-2"
        size="sm"
        title={t.app.git.title}
        variant="ghost"
        onClick={() => setOpen(true)}
      >
        <GitBranch className="size-4" />
        <span className="hidden sm:inline">{t.app.git.button}</span>
      </Button>
      <Dialog open={open} onOpenChange={setOpen}>
        <DialogContent className="flex max-h-[82vh] flex-col sm:max-w-2xl">
          <DialogHeader>
            <DialogTitle>{t.app.git.title}</DialogTitle>
            <DialogDescription>
              {repository
                ? t.app.git.repositoryDescription({
                    branch: status.branch ?? 'HEAD',
                  })
                : t.app.git.notRepositoryDescription}
            </DialogDescription>
          </DialogHeader>

          {error && (
            <div
              className="rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm text-destructive"
              role="alert"
            >
              {error}
            </div>
          )}

          {!repository ? (
            <div className="py-6 text-center">
              <Button
                disabled={busy}
                onClick={() =>
                  void run(
                    () => projectGit.init(wsName),
                    t.app.git.initialized,
                  )
                }
              >
                {t.app.git.initialize}
              </Button>
            </div>
          ) : (
            <>
              <div className="flex flex-wrap gap-1 border-b pb-2">
                {(['changes', 'history', 'branches', 'remote'] as const).map(
                  (nextTab) => (
                    <Button
                      key={nextTab}
                      size="sm"
                      variant={tab === nextTab ? 'secondary' : 'ghost'}
                      onClick={() => setTab(nextTab)}
                    >
                      {t.app.git.tabs[nextTab]}
                    </Button>
                  ),
                )}
                <Button
                  className="ml-auto"
                  disabled={busy}
                  size="icon"
                  variant="ghost"
                  aria-label={t.app.git.refresh}
                  onClick={() => void run(refresh)}
                >
                  <RefreshCw className={`size-4 ${busy ? 'animate-spin' : ''}`} />
                </Button>
              </div>

              <div className="min-h-0 overflow-auto py-2">
                {tab === 'changes' && (
                  <ChangesTab
                    status={status}
                    busy={busy}
                    commitMessage={commitMessage}
                    authorName={authorName}
                    authorEmail={authorEmail}
                    setCommitMessage={setCommitMessage}
                    setAuthorName={setAuthorName}
                    setAuthorEmail={setAuthorEmail}
                    stage={(paths) =>
                      run(() => projectGit.stage(wsName, paths))
                    }
                    unstage={(paths) =>
                      run(() => projectGit.unstage(wsName, paths))
                    }
                    diff={diff}
                    previewDiff={previewDiff}
                    closeDiff={() => setDiff(undefined)}
                    commit={() =>
                      run(async () => {
                        const message = commitMessage.trim();
                        const name = authorName.trim();
                        const email = authorEmail.trim();
                        if (!message || !name || !email) {
                          throw new Error(t.app.git.commitFieldsRequired);
                        }
                        await projectGit.commit(wsName, {
                          message,
                          authorName: name,
                          authorEmail: email,
                        });
                        setCommitMessage('');
                      }, t.app.git.committed)
                    }
                  />
                )}
                {tab === 'history' && <HistoryTab history={history} />}
                {tab === 'branches' && (
                  <BranchesTab
                    branches={branches}
                    current={status.branch}
                    newBranch={branchName}
                    setNewBranch={setBranchName}
                    busy={busy}
                    create={() =>
                      run(async () => {
                        const name = branchName.trim();
                        if (!name) throw new Error(t.app.git.branchNameRequired);
                        await projectGit.createBranch(wsName, name);
                        setBranchName('');
                      })
                    }
                    checkout={(branch) =>
                      run(
                        () => projectGit.checkout(wsName, branch),
                        t.app.git.branchSwitched({ branch }),
                      )
                    }
                  />
                )}
                {tab === 'remote' && (
                  <RemoteTab
                    remotes={remotes}
                    remoteName={remoteName}
                    remoteUrl={remoteUrl}
                    token={token}
                    corsProxy={corsProxy}
                    busy={busy}
                    setRemoteName={setRemoteName}
                    setRemoteUrl={setRemoteUrl}
                    setToken={setToken}
                    setCorsProxy={setCorsProxy}
                    publicProxyAuthConfirmed={publicProxyAuthConfirmed}
                    setPublicProxyAuthConfirmed={setPublicProxyAuthConfirmed}
                    addRemote={() =>
                      run(async () => {
                        const name = remoteName.trim();
                        const url = remoteUrl.trim();
                        if (!name || !url) {
                          throw new Error(t.app.git.remoteFieldsRequired);
                        }
                        await projectGit.addRemote(wsName, name, url);
                        setRemoteUrl('');
                      })
                    }
                    fetch={() =>
                      run(() => {
                        assertAuthenticatedProxyAllowed();
                        return projectGit.fetch(wsName, {
                          remote: remoteName || 'origin',
                          token: token || undefined,
                          corsProxy: corsProxy || undefined,
                        });
                      })
                    }
                    pull={() =>
                      run(
                        () => {
                          assertAuthenticatedProxyAllowed();
                          return projectGit.pull(wsName, {
                            remote: remoteName || 'origin',
                            token: token || undefined,
                            corsProxy: corsProxy || undefined,
                            author:
                              authorName && authorEmail
                                ? { name: authorName, email: authorEmail }
                                : undefined,
                          });
                        },
                        t.app.git.pulled,
                      )
                    }
                    push={() =>
                      run(
                        () => {
                          assertAuthenticatedProxyAllowed();
                          return projectGit.push(wsName, {
                            remote: remoteName || 'origin',
                            token: token || undefined,
                            corsProxy: corsProxy || undefined,
                          });
                        },
                        t.app.git.pushed,
                      )
                    }
                  />
                )}
              </div>
            </>
          )}
        </DialogContent>
      </Dialog>
    </>
  );
}

function ChangesTab({
  status,
  busy,
  commitMessage,
  authorName,
  authorEmail,
  setCommitMessage,
  setAuthorName,
  setAuthorEmail,
  diff,
  previewDiff,
  closeDiff,
  stage,
  unstage,
  commit,
}: {
  status: ProjectGitStatus;
  busy: boolean;
  commitMessage: string;
  authorName: string;
  authorEmail: string;
  setCommitMessage: (value: string) => void;
  setAuthorName: (value: string) => void;
  setAuthorEmail: (value: string) => void;
  diff?: ProjectGitDiff;
  previewDiff: (
    path: ProjectGitStatus['unstaged'][number]['path'],
  ) => Promise<void>;
  closeDiff: () => void;
  stage: (
    paths: ProjectGitStatus['unstaged'][number]['path'][],
  ) => Promise<void>;
  unstage: (
    paths: ProjectGitStatus['staged'][number]['path'][],
  ) => Promise<void>;
  commit: () => Promise<void>;
}) {
  return (
    <div className="space-y-5">
      <ChangeList
        title={t.app.git.staged}
        empty={t.app.git.noStagedChanges}
        changes={status.staged}
        actionLabel={t.app.git.unstage}
        action={(path) => unstage([path])}
        preview={previewDiff}
      />
      <ChangeList
        title={t.app.git.changes}
        empty={t.app.git.noChanges}
        changes={status.unstaged}
        actionLabel={t.app.git.stage}
        action={(path) => stage([path])}
        preview={previewDiff}
      />
      {diff && <GitDiffPreview diff={diff} onClose={closeDiff} />}
      {status.unstaged.length > 0 && (
        <Button
          disabled={busy}
          size="sm"
          variant="outline"
          onClick={() =>
            void stage(status.unstaged.map((change) => change.path))
          }
        >
          {t.app.git.stageAll}
        </Button>
      )}
      <div className="space-y-2 border-t pt-4">
        <div className="grid gap-2 sm:grid-cols-2">
          <Input
            aria-label={t.app.git.authorName}
            placeholder={t.app.git.authorName}
            value={authorName}
            onChange={(event) => setAuthorName(event.currentTarget.value)}
          />
          <Input
            aria-label={t.app.git.authorEmail}
            placeholder={t.app.git.authorEmail}
            value={authorEmail}
            onChange={(event) => setAuthorEmail(event.currentTarget.value)}
          />
        </div>
        <Input
          aria-label={t.app.git.commitMessage}
          placeholder={t.app.git.commitMessage}
          value={commitMessage}
          onChange={(event) => setCommitMessage(event.currentTarget.value)}
        />
        <Button
          disabled={busy || status.staged.length === 0}
          onClick={() => void commit()}
        >
          {t.app.git.commit}
        </Button>
      </div>
    </div>
  );
}

function ChangeList({
  title,
  empty,
  changes,
  actionLabel,
  action,
  preview,
}: {
  title: string;
  empty: string;
  changes: ProjectGitStatus['staged'] | ProjectGitStatus['unstaged'];
  actionLabel: string;
  action: (path: ProjectGitStatus['staged'][number]['path']) => Promise<void>;
  preview: (path: ProjectGitStatus['staged'][number]['path']) => Promise<void>;
}) {
  return (
    <section>
      <h3 className="mb-2 text-xs font-semibold uppercase text-muted-foreground">
        {title}
      </h3>
      {changes.length === 0 ? (
        <p className="text-sm text-muted-foreground">{empty}</p>
      ) : (
        <div className="divide-y rounded-md border">
          {changes.map((change) => (
            <div
              key={`${change.staged}-${change.path}`}
              className="flex items-center gap-2 px-3 py-2 text-sm"
            >
              <span className="w-5 font-mono text-xs uppercase text-muted-foreground">
                {change.status.slice(0, 1)}
              </span>
              <span className="min-w-0 flex-1 truncate font-mono">
                {change.path}
              </span>
              <Button
                size="sm"
                variant="ghost"
                onClick={() => void preview(change.path)}
              >
                {t.app.git.viewDiff}
              </Button>
              <Button
                size="sm"
                variant="ghost"
                onClick={() => void action(change.path)}
              >
                {actionLabel}
              </Button>
            </div>
          ))}
        </div>
      )}
    </section>
  );
}

function GitDiffPreview({
  diff,
  onClose,
}: {
  diff: ProjectGitDiff;
  onClose: () => void;
}) {
  return (
    <section className="rounded-md border">
      <div className="flex items-center gap-2 border-b px-3 py-2">
        <code className="min-w-0 flex-1 truncate text-xs">{diff.path}</code>
        <Button size="sm" variant="ghost" onClick={onClose}>
          {t.app.git.closeDiff}
        </Button>
      </div>
      {diff.binary ? (
        <p className="px-3 py-3 text-sm text-muted-foreground">
          {t.app.git.binaryDiff} · {diff.oldSize} B → {diff.newSize} B
        </p>
      ) : (
        <pre className="max-h-72 overflow-auto whitespace-pre-wrap break-all px-3 py-2 font-mono text-xs">
          {(diff.lines ?? [])
            .map((line) => {
              const prefix =
                line.kind === 'add' ? '+' : line.kind === 'remove' ? '-' : ' ';
              return `${prefix} ${line.text}`;
            })
            .join('\n')}
        </pre>
      )}
    </section>
  );
}

function HistoryTab({ history }: { history: readonly ProjectGitCommit[] }) {
  if (history.length === 0) {
    return (
      <p className="text-sm text-muted-foreground">{t.app.git.noCommits}</p>
    );
  }
  return (
    <div className="divide-y rounded-md border">
      {history.map((commit) => (
        <div key={commit.oid} className="space-y-1 px-3 py-2">
          <div className="flex gap-2 text-sm">
            <code className="text-muted-foreground">
              {commit.oid.slice(0, 7)}
            </code>
            <span className="font-medium">{commit.message.trim()}</span>
          </div>
          <div className="text-xs text-muted-foreground">
            {commit.authorName} ·{' '}
            {new Date(commit.timestamp * 1000).toLocaleString()}
          </div>
        </div>
      ))}
    </div>
  );
}

function BranchesTab({
  branches,
  current,
  newBranch,
  setNewBranch,
  busy,
  create,
  checkout,
}: {
  branches: readonly string[];
  current?: string;
  newBranch: string;
  setNewBranch: (value: string) => void;
  busy: boolean;
  create: () => Promise<void>;
  checkout: (branch: string) => Promise<void>;
}) {
  return (
    <div className="space-y-4">
      <div className="divide-y rounded-md border">
        {branches.map((branch) => (
          <div
            key={branch}
            className="flex items-center gap-2 px-3 py-2 text-sm"
          >
            <GitBranch className="size-4" />
            <span className="flex-1 font-mono">{branch}</span>
            {branch === current ? (
              <span className="text-xs text-muted-foreground">
                {t.app.git.current}
              </span>
            ) : (
              <Button
                size="sm"
                variant="ghost"
                onClick={() => void checkout(branch)}
              >
                {t.app.git.checkout}
              </Button>
            )}
          </div>
        ))}
      </div>
      <div className="flex gap-2">
        <Input
          aria-label={t.app.git.newBranch}
          placeholder={t.app.git.newBranch}
          value={newBranch}
          onChange={(event) => setNewBranch(event.currentTarget.value)}
        />
        <Button disabled={busy} onClick={() => void create()}>
          {t.app.git.createBranch}
        </Button>
      </div>
    </div>
  );
}

function RemoteTab({
  remotes,
  remoteName,
  remoteUrl,
  token,
  corsProxy,
  busy,
  setRemoteName,
  setRemoteUrl,
  setToken,
  setCorsProxy,
  publicProxyAuthConfirmed,
  setPublicProxyAuthConfirmed,
  addRemote,
  fetch,
  pull,
  push,
}: {
  remotes: readonly ProjectGitRemote[];
  remoteName: string;
  remoteUrl: string;
  token: string;
  corsProxy: string;
  busy: boolean;
  setRemoteName: (value: string) => void;
  setRemoteUrl: (value: string) => void;
  setToken: (value: string) => void;
  setCorsProxy: (value: string) => void;
  publicProxyAuthConfirmed: boolean;
  setPublicProxyAuthConfirmed: (value: boolean) => void;
  addRemote: () => Promise<void>;
  fetch: () => Promise<void>;
  pull: () => Promise<void>;
  push: () => Promise<void>;
}) {
  return (
    <div className="space-y-4">
      <div className="space-y-1">
        {remotes.map((remote) => (
          <div key={remote.remote} className="rounded-md border px-3 py-2 text-sm">
            <div className="font-medium">{remote.remote}</div>
            <div className="truncate font-mono text-xs text-muted-foreground">{remote.url}</div>
          </div>
        ))}
      </div>
      <div className="grid gap-2 sm:grid-cols-[8rem_1fr_auto]">
        <Input value={remoteName} aria-label={t.app.git.remoteName} onChange={(event) => setRemoteName(event.currentTarget.value)} />
        <Input value={remoteUrl} aria-label={t.app.git.remoteUrl} placeholder="https://…" onChange={(event) => setRemoteUrl(event.currentTarget.value)} />
        <Button disabled={busy} variant="outline" onClick={() => void addRemote()}>{t.app.git.addRemote}</Button>
      </div>
      <div className="space-y-2 border-t pt-4">
        <Label htmlFor="interactive-git-token">{t.app.git.token}</Label>
        <Input id="interactive-git-token" type="password" autoComplete="off" value={token} onChange={(event) => setToken(event.currentTarget.value)} />
        <p className="text-xs text-muted-foreground">{t.app.git.tokenMemoryOnly}</p>
        <Label htmlFor="interactive-git-proxy">{t.app.git.corsProxy}</Label>
        <Input id="interactive-git-proxy" value={corsProxy} onChange={(event) => setCorsProxy(event.currentTarget.value)} />
        <p className="text-xs text-muted-foreground">A CORS proxy is an external network intermediary for browser Git traffic.</p>
        {token.trim() && isDefaultPublicGitCorsProxy(corsProxy) && (
          <label className="flex items-start gap-2 rounded-md border border-amber-500/40 bg-amber-500/5 p-2 text-xs">
            <input
              className="mt-0.5"
              type="checkbox"
              checked={publicProxyAuthConfirmed}
              onChange={(event) => setPublicProxyAuthConfirmed(event.currentTarget.checked)}
            />
            <span>I understand this authentication token will be sent through the public CORS proxy for this operation.</span>
          </label>
        )}
        <div className="flex gap-2 pt-1">
          <Button disabled={busy} variant="outline" onClick={() => void fetch()}>{t.app.git.fetch}</Button>
          <Button disabled={busy} variant="outline" onClick={() => void pull()}>{t.app.git.pull}</Button>
          <Button disabled={busy} onClick={() => void push()}>{t.app.git.push}</Button>
        </div>
      </div>
    </div>
  );
}
