"use client";

import * as React from "react";
import Link from "next/link";
import { useQuery } from "@tanstack/react-query";
import { apiFetch } from "@/lib/api";
import type { OllamaModelInfo } from "@/lib/ollama";
import { Message } from "@/components/chat/message";
import { ChatInput } from "@/components/chat/chat-input";
import { ChatMessagesContext } from "@/components/chat/chat-messages-context";
import type { MessageRecord } from "@/components/chat/chat-view";
import type { ToolCallView } from "@/components/chat/tool-call-block";
import { useShortcut } from "@/hooks/use-keyboard";
import { Button } from "@/components/ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { Slider } from "@/components/ui/slider";
import { Switch } from "@/components/ui/switch";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import {
  CONTEXT_PRESETS,
  KEEP_ALIVE_PRESETS,
  DEFAULT_SYSTEM_PROMPT,
} from "@/lib/constants";
import {
  PanelLeft,
  Settings2,
  Settings as SettingsIcon,
  Trash2,
  Brain,
  Wrench,
  EyeOff,
  RotateCcw,
} from "lucide-react";
import { useUIStore } from "@/lib/stores/ui-store";
import { ThemeToggle } from "@/components/theme-toggle";
import { toast } from "sonner";
import { cn } from "@/lib/utils";

interface MeResponse {
  user: {
    defaultModel: string | null;
    defaultContext: number;
    defaultSystemPrompt: string;
    defaultKeepAlive: number;
    showThinking: boolean;
    toolsEnabled: boolean;
  };
}

interface TempConfig {
  model: string;
  contextWindow: number;
  systemPrompt: string;
  temperature: number;
  showThinking: boolean;
  keepAlive: number;
  toolsEnabled: boolean;
}

interface StreamingState {
  content: string;
  thinking: string;
  startedAt: number;
  liveTokens: number;
  toolCalls: ToolCallView[];
  promptTokens: number | null;
  tokens: number | null;
  totalDurationMs: number | null;
  evalDurationMs: number | null;
}

function emptyMessage(role: MessageRecord["role"], content: string): MessageRecord {
  return {
    id: `mem_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
    chatId: "temp",
    role,
    content,
    thinking: null,
    toolCalls: null,
    toolName: null,
    toolCallId: null,
    toolMeta: null,
    promptTokens: null,
    tokens: null,
    totalDurationMs: null,
    evalDurationMs: null,
    createdAt: new Date().toISOString(),
  };
}

export function TempChatView() {
  const toggleSidebar = useUIStore((s) => s.toggleSidebar);

  const meQuery = useQuery({
    queryKey: ["me"],
    queryFn: () => apiFetch<MeResponse>("/api/me"),
  });
  const modelsQuery = useQuery({
    queryKey: ["models"],
    queryFn: () => apiFetch<{ models: OllamaModelInfo[] }>("/api/models"),
    retry: false,
  });

  const [config, setConfig] = React.useState<TempConfig | null>(null);
  const [messages, setMessages] = React.useState<MessageRecord[]>([]);
  const [streaming, setStreaming] = React.useState<StreamingState | null>(null);
  const abortRef = React.useRef<AbortController | null>(null);
  const scrollRef = React.useRef<HTMLDivElement>(null);

  // Init config from user defaults once
  React.useEffect(() => {
    if (config || !meQuery.data || !modelsQuery.data) return;
    const me = meQuery.data.user;
    const models = modelsQuery.data.models;
    const model =
      me.defaultModel && models.some((m) => m.name === me.defaultModel)
        ? me.defaultModel
        : models[0]?.name ?? "";
    setConfig({
      model,
      contextWindow: me.defaultContext,
      systemPrompt: me.defaultSystemPrompt || DEFAULT_SYSTEM_PROMPT,
      temperature: 0.7,
      showThinking: me.showThinking,
      keepAlive: me.defaultKeepAlive,
      toolsEnabled: me.toolsEnabled,
    });
  }, [meQuery.data, modelsQuery.data, config]);

  const showQuery = useQuery({
    queryKey: ["model-show", config?.model],
    queryFn: () =>
      apiFetch<{
        capabilities: string[];
        supportsThinking: boolean;
        supportsTools: boolean;
      }>(`/api/models/show?name=${encodeURIComponent(config?.model ?? "")}`),
    enabled: !!config?.model,
    staleTime: Infinity,
    retry: false,
  });
  const canThink = !!showQuery.data?.supportsThinking;
  const canTools = !!showQuery.data?.supportsTools;

  function scrollToBottom() {
    scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: "smooth" });
  }

  React.useEffect(() => {
    scrollToBottom();
  }, [messages.length, streaming?.content, streaming?.toolCalls.length]);

  const stopGeneration = React.useCallback(() => {
    abortRef.current?.abort();
    abortRef.current = null;
  }, []);

  useShortcut("Escape", () => {
    if (streaming) stopGeneration();
  });

  function clearChat() {
    if (streaming) return;
    setMessages([]);
    toast.success("Conversation effacée");
  }

  const sendMessage = React.useCallback(
    async (text: string) => {
      const trimmed = text.trim();
      if (!trimmed || !config || !config.model) return;

      // Snapshot prior messages BEFORE adding the new user message (server appends it).
      const historyForServer = messages
        .filter((m) => m.role !== "system")
        .map((m) => ({
          role: m.role as "user" | "assistant" | "tool",
          content: m.content,
          toolCalls: m.toolCalls
            ? (() => {
                try {
                  return JSON.parse(m.toolCalls);
                } catch {
                  return null;
                }
              })()
            : null,
          toolName: m.toolName,
          toolCallId: m.toolCallId,
        }));

      const userMsg = emptyMessage("user", trimmed);
      setMessages((prev) => [...prev, userMsg]);

      const startedAt = Date.now();
      setStreaming({
        content: "",
        thinking: "",
        startedAt,
        liveTokens: 0,
        toolCalls: [],
        promptTokens: null,
        tokens: null,
        totalDurationMs: null,
        evalDurationMs: null,
      });

      const ctl = new AbortController();
      abortRef.current = ctl;

      // Snapshot of tool-call results received in this stream, keyed by id.
      const collectedToolResults: Array<{
        callId: string;
        name: string;
        content: string;
        meta: Record<string, unknown> | null;
        durationMs: number | undefined;
      }> = [];
      // Snapshot of tool calls issued by the assistant in this stream.
      const collectedAssistantToolCalls: Array<{
        id: string;
        function: { name: string; arguments: unknown };
      }> = [];
      let assistantSegments: Array<{ content: string; thinking: string; toolCalls: typeof collectedAssistantToolCalls }> = [];
      let currentSegment = { content: "", thinking: "", toolCalls: [] as typeof collectedAssistantToolCalls };

      try {
        const res = await fetch("/api/chat/ephemeral", {
          method: "POST",
          headers: { "content-type": "application/json" },
          body: JSON.stringify({
            userMessage: trimmed,
            model: config.model,
            contextWindow: config.contextWindow,
            systemPrompt: config.systemPrompt,
            temperature: config.temperature,
            showThinking: config.showThinking,
            keepAlive: config.keepAlive,
            toolsEnabled: config.toolsEnabled,
            history: historyForServer,
          }),
          signal: ctl.signal,
        });
        if (!res.ok || !res.body) throw new Error(`Stream failed: ${res.status}`);

        const reader = res.body.getReader();
        const decoder = new TextDecoder();
        let buffer = "";
        let aggContent = "";
        let aggThinking = "";
        let liveTokens = 0;

        outer: while (true) {
          const { value, done } = await reader.read();
          if (done) break;
          buffer += decoder.decode(value, { stream: true });
          let nl: number;
          while ((nl = buffer.indexOf("\n\n")) >= 0) {
            const block = buffer.slice(0, nl);
            buffer = buffer.slice(nl + 2);
            const line = block.trim();
            if (!line.startsWith("data:")) continue;
            const payload = line.slice(5).trim();
            try {
              const evt = JSON.parse(payload) as { type: string; data?: unknown };
              if (evt.type === "thinking" && typeof evt.data === "string") {
                aggThinking += evt.data;
                currentSegment.thinking += evt.data;
                setStreaming((s) => (s ? { ...s, thinking: aggThinking } : s));
              } else if (evt.type === "content" && typeof evt.data === "string") {
                aggContent += evt.data;
                liveTokens += 1;
                currentSegment.content += evt.data;
                setStreaming((s) => (s ? { ...s, content: aggContent, liveTokens } : s));
              } else if (evt.type === "tool_call_start") {
                const d = (evt.data ?? {}) as {
                  id: string;
                  name: string;
                  arguments: unknown;
                };
                currentSegment.toolCalls.push({
                  id: d.id,
                  function: { name: d.name, arguments: d.arguments },
                });
                collectedAssistantToolCalls.push({
                  id: d.id,
                  function: { name: d.name, arguments: d.arguments },
                });
                setStreaming((s) =>
                  s
                    ? {
                        ...s,
                        toolCalls: [
                          ...s.toolCalls,
                          {
                            id: d.id,
                            name: d.name,
                            arguments: (d.arguments ?? {}) as Record<string, unknown> | string,
                            status: "running",
                          },
                        ],
                      }
                    : s,
                );
              } else if (evt.type === "tool_call_result") {
                const d = (evt.data ?? {}) as {
                  id: string;
                  name: string;
                  result: string;
                  error: string | null;
                  meta: Record<string, unknown> | null;
                  durationMs?: number;
                };
                collectedToolResults.push({
                  callId: d.id,
                  name: d.name,
                  content: d.result,
                  meta: d.meta,
                  durationMs: d.durationMs,
                });
                setStreaming((s) =>
                  s
                    ? {
                        ...s,
                        toolCalls: s.toolCalls.map((tc) =>
                          tc.id === d.id
                            ? {
                                ...tc,
                                status: d.error ? "error" : "done",
                                result: d.result,
                                error: d.error,
                                meta: d.meta,
                                durationMs: d.durationMs,
                              }
                            : tc,
                        ),
                      }
                    : s,
                );
                // close current assistant segment, start new for next iteration
                assistantSegments.push(currentSegment);
                currentSegment = { content: "", thinking: "", toolCalls: [] };
                aggContent = "";
                liveTokens = 0;
                setStreaming((s) => (s ? { ...s, content: "", liveTokens: 0 } : s));
              } else if (evt.type === "done") {
                const d = (evt.data ?? {}) as {
                  promptTokens?: number;
                  evalTokens?: number;
                  evalDurationMs?: number;
                  totalDurationMs?: number;
                };
                setStreaming((s) =>
                  s
                    ? {
                        ...s,
                        promptTokens: d.promptTokens ?? null,
                        tokens: d.evalTokens ?? null,
                        evalDurationMs: d.evalDurationMs ?? null,
                        totalDurationMs: d.totalDurationMs ?? null,
                      }
                    : s,
                );
                // Build final messages list from collected segments.
                assistantSegments.push(currentSegment);
                const newMessages: MessageRecord[] = [];
                let segIdx = 0;
                for (const seg of assistantSegments) {
                  const isLast = segIdx === assistantSegments.length - 1;
                  const am = emptyMessage("assistant", seg.content);
                  am.thinking = seg.thinking || null;
                  if (seg.toolCalls.length > 0)
                    am.toolCalls = JSON.stringify(seg.toolCalls);
                  if (isLast) {
                    am.promptTokens = d.promptTokens ?? null;
                    am.tokens = d.evalTokens ?? null;
                    am.evalDurationMs = d.evalDurationMs ?? null;
                    am.totalDurationMs = d.totalDurationMs ?? null;
                  }
                  newMessages.push(am);
                  // tool results for this segment's tool calls
                  for (const tc of seg.toolCalls) {
                    const r = collectedToolResults.find((rr) => rr.callId === tc.id);
                    if (!r) continue;
                    const tm = emptyMessage("tool", r.content);
                    tm.toolName = r.name;
                    tm.toolCallId = r.callId;
                    tm.toolMeta = r.meta ? JSON.stringify(r.meta) : null;
                    tm.totalDurationMs = r.durationMs ?? null;
                    newMessages.push(tm);
                  }
                  segIdx += 1;
                }
                setMessages((prev) => [...prev, ...newMessages]);
                break outer;
              } else if (evt.type === "warning" && typeof evt.data === "string") {
                toast.warning(evt.data);
              } else if (evt.type === "error" && typeof evt.data === "string") {
                toast.error(evt.data);
                break outer;
              }
            } catch {
              /* ignore malformed */
            }
          }
        }
      } catch (e) {
        if ((e as Error).name !== "AbortError") {
          toast.error(e instanceof Error ? e.message : "Stream failed");
        }
      } finally {
        abortRef.current = null;
        setStreaming(null);
      }
    },
    [config, messages],
  );

  if (!config) {
    return (
      <div className="flex h-full items-center justify-center">
        <div className="spinner h-6 w-6" />
      </div>
    );
  }

  const noModels = (modelsQuery.data?.models?.length ?? 0) === 0;

  return (
    <div className="flex h-full flex-col">
      <header className="flex h-14 shrink-0 items-center justify-between border-b border-border px-4">
        <div className="flex min-w-0 items-center gap-2">
          <Button variant="ghost" size="icon-sm" onClick={toggleSidebar} aria-label="Toggle sidebar">
            <PanelLeft />
          </Button>
          <EyeOff className="h-4 w-4 text-muted-foreground" />
          <span className="truncate font-serif text-base">Conversation temporaire</span>
          <span className="ml-2 hidden truncate rounded-full bg-muted px-2 py-0.5 text-[11px] text-muted-foreground sm:inline">
            {config.model || "(pas de modèle)"}
          </span>
          {canThink && (
            <span className="hidden items-center gap-1 rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-medium text-primary sm:inline-flex">
              <Brain className="h-2.5 w-2.5" />
              thinks
            </span>
          )}
          {canTools && (
            <span className="hidden items-center gap-1 rounded-full bg-emerald-500/10 px-2 py-0.5 text-[10px] font-medium text-emerald-600 dark:text-emerald-400 sm:inline-flex">
              <Wrench className="h-2.5 w-2.5" />
              tools
            </span>
          )}
        </div>
        <div className="flex items-center gap-1">
          <TempConfigPopover
            config={config}
            setConfig={setConfig}
            models={modelsQuery.data?.models ?? []}
            canThink={canThink}
          />
          <Button
            variant="ghost"
            size="icon-sm"
            onClick={clearChat}
            disabled={!!streaming || messages.length === 0}
            aria-label="Vider la conversation"
          >
            <Trash2 />
          </Button>
          <ThemeToggle />
          <Button asChild variant="ghost" size="icon-sm" aria-label="Settings">
            <Link href="/settings">
              <SettingsIcon />
            </Link>
          </Button>
        </div>
      </header>

      <div className="border-b border-amber-500/30 bg-amber-500/5 px-6 py-2 text-xs text-amber-700 dark:text-amber-400">
        <span className="inline-flex items-center gap-1.5">
          <EyeOff className="h-3 w-3" />
          Cette conversation n&apos;est pas enregistrée. Elle disparaîtra au rechargement
          de la page ou en quittant l&apos;onglet.
        </span>
      </div>

      <ChatMessagesContext.Provider value={messages}>
        <div ref={scrollRef} className="scroll-area flex-1 overflow-y-auto">
          <div className="mx-auto w-full max-w-3xl px-6 py-8">
            {messages.length === 0 && !streaming && (
              <div className="py-16 text-center text-sm text-muted-foreground">
                {noModels
                  ? "Aucun modèle installé. Va dans Settings → Models."
                  : "Pose une question. Rien ne sera sauvegardé."}
              </div>
            )}
            {messages.map((m) => (
              <Message key={m.id} message={m} showThinking={config.showThinking} />
            ))}
            {streaming && (
              <Message
                streaming
                showThinking={config.showThinking}
                streamingStartedAt={streaming.startedAt}
                streamingLiveTokens={streaming.liveTokens}
                streamingToolCalls={streaming.toolCalls}
                message={{
                  id: "streaming",
                  chatId: "temp",
                  role: "assistant",
                  content: streaming.content,
                  thinking: streaming.thinking || null,
                  toolCalls: null,
                  toolName: null,
                  toolCallId: null,
                  toolMeta: null,
                  promptTokens: streaming.promptTokens,
                  tokens: streaming.tokens,
                  totalDurationMs: streaming.totalDurationMs,
                  evalDurationMs: streaming.evalDurationMs,
                  createdAt: new Date(streaming.startedAt).toISOString(),
                }}
              />
            )}
          </div>
        </div>
      </ChatMessagesContext.Provider>

      <ChatInput
        onSend={sendMessage}
        onStop={stopGeneration}
        isStreaming={!!streaming}
      />
    </div>
  );
}

function TempConfigPopover({
  config,
  setConfig,
  models,
  canThink,
}: {
  config: TempConfig;
  setConfig: React.Dispatch<React.SetStateAction<TempConfig | null>>;
  models: OllamaModelInfo[];
  canThink: boolean;
}) {
  const [contextMode, setContextMode] = React.useState<"preset" | "custom">(
    CONTEXT_PRESETS.some((p) => p.value === config.contextWindow) ? "preset" : "custom",
  );
  const [keepAliveMode, setKeepAliveMode] = React.useState<"preset" | "custom">(
    KEEP_ALIVE_PRESETS.some((p) => p.value === config.keepAlive) ? "preset" : "custom",
  );

  const update = (patch: Partial<TempConfig>) =>
    setConfig((prev) => (prev ? { ...prev, ...patch } : prev));

  return (
    <Popover>
      <PopoverTrigger asChild>
        <Button variant="ghost" size="icon-sm" aria-label="Chat settings">
          <Settings2 />
        </Button>
      </PopoverTrigger>
      <PopoverContent align="end" className="w-[22rem]">
        <div className="space-y-4">
          <div className="space-y-1.5">
            <Label className="text-xs">Model</Label>
            <Select value={config.model} onValueChange={(v) => update({ model: v })}>
              <SelectTrigger className="h-8 text-xs">
                <SelectValue placeholder={models.length ? "Select" : "No models"} />
              </SelectTrigger>
              <SelectContent>
                {models.map((m) => (
                  <SelectItem key={m.name} value={m.name}>
                    {m.name}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>

          <div className="space-y-1.5">
            <Label className="text-xs">Context window</Label>
            <Select
              value={contextMode === "preset" ? String(config.contextWindow) : "custom"}
              onValueChange={(v) => {
                if (v === "custom") setContextMode("custom");
                else {
                  setContextMode("preset");
                  update({ contextWindow: Number(v) });
                }
              }}
            >
              <SelectTrigger className="h-8 text-xs">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {CONTEXT_PRESETS.map((p) => (
                  <SelectItem key={p.value} value={String(p.value)}>
                    {p.label} ({p.value.toLocaleString()})
                  </SelectItem>
                ))}
                <SelectItem value="custom">Custom…</SelectItem>
              </SelectContent>
            </Select>
            {contextMode === "custom" && (
              <Input
                type="number"
                min={512}
                step={512}
                value={config.contextWindow}
                onChange={(e) => update({ contextWindow: Number(e.target.value) })}
                className="mt-2 h-8 text-xs"
              />
            )}
          </div>

          <div className="space-y-1.5">
            <Label className="text-xs">Keep alive</Label>
            <Select
              value={keepAliveMode === "preset" ? String(config.keepAlive) : "custom"}
              onValueChange={(v) => {
                if (v === "custom") setKeepAliveMode("custom");
                else {
                  setKeepAliveMode("preset");
                  update({ keepAlive: Number(v) });
                }
              }}
            >
              <SelectTrigger className="h-8 text-xs">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {KEEP_ALIVE_PRESETS.map((p) => (
                  <SelectItem key={p.value} value={String(p.value)}>
                    {p.label}
                  </SelectItem>
                ))}
                <SelectItem value="custom">Custom…</SelectItem>
              </SelectContent>
            </Select>
            {keepAliveMode === "custom" && (
              <Input
                type="number"
                min={-1}
                value={config.keepAlive}
                onChange={(e) => update({ keepAlive: Number(e.target.value) })}
                className="mt-2 h-8 text-xs"
              />
            )}
          </div>

          <div className="space-y-1.5">
            <div className="flex items-center justify-between">
              <Label className="text-xs">System prompt</Label>
              <Button
                variant="ghost"
                size="sm"
                className="h-6 px-2 text-[11px]"
                onClick={() => update({ systemPrompt: DEFAULT_SYSTEM_PROMPT })}
              >
                <RotateCcw className="!h-3 !w-3" />
                Reset
              </Button>
            </div>
            <Textarea
              value={config.systemPrompt}
              onChange={(e) => update({ systemPrompt: e.target.value })}
              className="min-h-[100px] text-xs"
            />
          </div>

          <div className="space-y-1.5">
            <div className="flex items-center justify-between">
              <Label className="text-xs">Temperature</Label>
              <span className="font-mono text-xs text-muted-foreground">
                {config.temperature.toFixed(2)}
              </span>
            </div>
            <Slider
              min={0}
              max={2}
              step={0.05}
              value={[config.temperature]}
              onValueChange={(v) => update({ temperature: v[0] ?? 0 })}
            />
          </div>

          <div className={cn("flex items-center justify-between rounded-lg border px-3 py-2", "border-border")}>
            <div>
              <Label className="text-xs">Show thinking</Label>
              <p className="text-[11px] text-muted-foreground">
                {canThink ? "Display reasoning blocks" : "Model doesn't support thinking"}
              </p>
            </div>
            <Switch
              checked={canThink && config.showThinking}
              onCheckedChange={(v) => update({ showThinking: v })}
              disabled={!canThink}
            />
          </div>

          <div className="flex items-center justify-between rounded-lg border border-border px-3 py-2">
            <div>
              <Label className="text-xs">Tools</Label>
              <p className="text-[11px] text-muted-foreground">Activer recherche/PDF/etc.</p>
            </div>
            <Switch
              checked={config.toolsEnabled}
              onCheckedChange={(v) => update({ toolsEnabled: v })}
            />
          </div>
        </div>
      </PopoverContent>
    </Popover>
  );
}
