import { useCoreServices } from '@learnitive/context';
import { projectFilePath } from '@learnitive/project-storage';
import { Button, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, toast } from '@learnitive/ui-components';
import { WsPath } from '@learnitive/ws-path';
import { useAtomValue } from 'jotai';
import { AlertTriangle, Bug, LoaderCircle, Settings2, ShieldCheck, Sparkles, Wrench } from 'lucide-react';
import React from 'react';
import { requestProjectEditorReveal } from '../project-compile-ui';
import { type CodeReviewFinding, type CodeReviewMode, type CodeReviewResult, runCodeReview } from '../code-ai';
import { openAiSettingsDialog } from './ai-settings-events';
import { AI_LARGE_DIALOG_CONTENT_CLASS } from './ai-modal-layout';

const MODE_COPY: Record<CodeReviewMode, { label: string; description: string }> = {
  quick: { label: 'Quick', description: 'Obvious bugs, API misuse, type errors and high-confidence improvements.' },
  full: { label: 'Full', description: 'Correctness, architecture, maintainability, performance, types, security and test gaps.' },
  security: { label: 'Security', description: 'Input handling, injection, secrets, auth, paths, commands and insecure framework patterns.' },
};

function severityClass(severity: CodeReviewFinding['severity']): string {
  return severity === 'critical' || severity === 'high'
    ? 'border-destructive/35 bg-destructive/5'
    : severity === 'medium'
      ? 'border-amber-400/35 bg-amber-50/50 dark:bg-amber-950/15'
      : 'border-border bg-background';
}

export function CodeAiReviewDialog({
  open,
  onOpenChange,
  wsName,
  currentFile,
  onOpenReview,
  diagnostics,
}: {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  wsName: string;
  currentFile: string;
  onOpenReview?: () => void;
  diagnostics?: string;
}) {
  const { projectAi, projectReview, projectStorage, navigation, fileSystem } = useCoreServices();
  const aiSettings = useAtomValue(projectAi.$settings);
  const [mode, setMode] = React.useState<CodeReviewMode>('full');
  const [running, setRunning] = React.useState(false);
  const [result, setResult] = React.useState<CodeReviewResult>();
  const [fixingId, setFixingId] = React.useState<string>();
  const controllerRef = React.useRef<AbortController>();

  React.useEffect(() => () => controllerRef.current?.abort(), []);
  React.useEffect(() => {
    if (!open) controllerRef.current?.abort();
  }, [open]);

  const start = async () => {
    controllerRef.current?.abort();
    const controller = new AbortController();
    controllerRef.current = controller;
    setRunning(true);
    try {
      const next = await runCodeReview({ projectAi, projectStorage, wsName, currentFile, mode, diagnostics, signal: controller.signal });
      setResult(next);
    } catch (error) {
      if (!controller.signal.aborted) toast.error(error instanceof Error ? error.message : 'Code review failed.');
    } finally {
      if (controllerRef.current === controller) controllerRef.current = undefined;
      setRunning(false);
    }
  };

  const openFinding = (finding: CodeReviewFinding) => {
    const wsPath = WsPath.fromParts(wsName, projectFilePath(finding.filePath)).wsPath;
    navigation.goWsPath(wsPath);
    requestProjectEditorReveal(wsPath, finding.line ?? 1);
  };

  const proposeFix = async (finding: CodeReviewFinding) => {
    if (!finding.originalText || !finding.replacementText) return;
    setFixingId(finding.id);
    try {
      const wsPath = WsPath.fromParts(wsName, projectFilePath(finding.filePath)).wsPath;
      const source = await fileSystem.readFileAsText(wsPath);
      if (source === undefined) throw new Error(`Unable to load ${finding.filePath}.`);
      const first = source.indexOf(finding.originalText);
      if (first < 0) throw new Error('This finding is outdated because the reviewed code has changed.');
      if (source.indexOf(finding.originalText, first + 1) >= 0) throw new Error('The proposed snippet appears more than once. Open the finding and apply it manually.');
      const thread = await projectReview.proposeSourceChange(wsName, {
        filePath: finding.filePath,
        from: first,
        to: first + finding.originalText.length,
        originalText: finding.originalText,
        proposedText: finding.replacementText,
        author: { id: 'learnitive-code-review', displayName: 'Learnitive AI · Code Review', kind: 'ai' },
        metadata: { source: 'ai', category: finding.category, reason: `${finding.title}: ${finding.explanation}`, findingId: finding.id },
      });
      toast.success('Fix added to Review. Accept or reject it there.');
      onOpenReview?.();
      openFinding(finding);
      void thread;
    } catch (error) {
      toast.error(error instanceof Error ? error.message : 'Unable to create a review fix.');
    } finally {
      setFixingId(undefined);
    }
  };

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className={AI_LARGE_DIALOG_CONTENT_CLASS} data-testid="code-ai-review-dialog">
        <DialogHeader>
          <DialogTitle className="flex items-center gap-2"><Sparkles className="size-5" />AI Code Review</DialogTitle>
          <DialogDescription>Review code for correctness, security, performance and maintainability. Proposed edits are sent to Review and never applied silently.</DialogDescription>
        </DialogHeader>

        <div className="grid gap-2 sm:grid-cols-3">
          {(Object.keys(MODE_COPY) as CodeReviewMode[]).map((value) => (
            <button
              key={value}
              type="button"
              className={`rounded-lg border p-3 text-left transition-colors ${mode === value ? 'border-primary bg-primary/5' : 'hover:bg-muted/40'}`}
              onClick={() => setMode(value)}
            >
              <div className="font-medium">{MODE_COPY[value].label}</div>
              <div className="mt-1 text-xs text-muted-foreground">{MODE_COPY[value].description}</div>
            </button>
          ))}
        </div>

        <div className="flex flex-wrap items-center gap-2 border-y py-3 text-sm">
          <span className="text-muted-foreground">AI provider:</span>
          <span className="font-medium">{aiSettings.provider === 'disabled' ? 'Disabled' : aiSettings.provider}</span>
          <Button size="sm" variant="ghost" onClick={() => openAiSettingsDialog()}><Settings2 className="mr-1 size-4" />AI Settings</Button>
          <Button className="ml-auto" disabled={running} onClick={() => void start()}>
            {running ? <LoaderCircle className="mr-1 size-4 animate-spin" /> : <Sparkles className="mr-1 size-4" />}
            {result ? 'Review again' : 'Start code review'}
          </Button>
        </div>

        {result ? (
          <div className="space-y-3">
            <div className="rounded-lg border bg-muted/20 p-3 text-sm">{result.summary}</div>
            {result.findings.length === 0 ? (
              <div className="rounded-lg border p-6 text-center text-sm text-muted-foreground">No actionable findings were returned.</div>
            ) : result.findings.map((finding) => (
              <article key={finding.id} className={`rounded-lg border p-3 ${severityClass(finding.severity)}`} data-testid="code-review-finding">
                <div className="flex flex-wrap items-center gap-2">
                  {finding.category === 'security' ? <ShieldCheck className="size-4" /> : finding.severity === 'critical' || finding.severity === 'high' ? <AlertTriangle className="size-4" /> : <Bug className="size-4" />}
                  <span className="rounded bg-muted px-1.5 py-0.5 text-[10px] font-semibold uppercase">{finding.severity}</span>
                  <span className="rounded bg-muted px-1.5 py-0.5 text-[10px] uppercase text-muted-foreground">{finding.category}</span>
                  <button type="button" className="text-xs font-medium underline underline-offset-2" onClick={() => openFinding(finding)}>
                    {finding.filePath}{finding.line ? `:${finding.line}` : ''}
                  </button>
                </div>
                <h3 className="mt-2 font-semibold">{finding.title}</h3>
                <p className="mt-1 whitespace-pre-wrap text-sm text-muted-foreground">{finding.explanation}</p>
                {finding.originalText && finding.replacementText && (
                  <div className="mt-3 flex flex-wrap gap-2">
                    <Button size="sm" variant="secondary" disabled={fixingId === finding.id} onClick={() => void proposeFix(finding)}>
                      {fixingId === finding.id ? <LoaderCircle className="mr-1 size-4 animate-spin" /> : <Wrench className="mr-1 size-4" />}
                      Propose fix in Review
                    </Button>
                  </div>
                )}
              </article>
            ))}
          </div>
        ) : (
          <div className="rounded-lg border border-dashed p-6 text-center text-sm text-muted-foreground">Choose a review depth and start. Project source is bounded and build outputs, dependencies, hidden runtime data and likely secret files are excluded.</div>
        )}
      </DialogContent>
    </Dialog>
  );
}
