import { useEffect, useState } from "react";
import { apiClient } from "./api/client";
import { AssistantPanel } from "./components/AssistantPanel";
import { ConnectionPanel } from "./components/ConnectionPanel";
import { HistoryPanel } from "./components/HistoryPanel";
import { MetricsPanel } from "./components/MetricsPanel";
import { OutputPanel } from "./components/OutputPanel";
import { PromptPanel } from "./components/PromptPanel";
import { QueryPanel } from "./components/QueryPanel";
import { RuntimePanel } from "./components/RuntimePanel";
import { DEFAULT_CONNECTION, DEFAULT_PROMPTS, DEFAULT_QUERY } from "./state/defaults";
import type {
  AssistantConversationItem,
  ConnectionState,
  HistoryItem,
  NormalizeResultState,
  PromptState,
  QueryState,
  RuntimeRun,
  TabKey,
  UiMode
} from "./state/types";

const SESSION_CONFIG_KEY = "ndc_normalizer_session_config_v1";
const ASSISTANT_STAGES = ["Разбираю запрос", "Ищу данные", "Собираю ответ"];

function withTs(message: string): string {
  return `[${new Date().toLocaleTimeString("ru-RU")}] ${message}`;
}

function diffPrompts(current: PromptState, previous: PromptState | null): string {
  if (!previous) return "Previous preset is not selected.";
  const fields: Array<keyof PromptState> = ["systemPrompt", "developerPrompt", "domainPrompt", "schemaNotes", "fewShotExamples"];
  const changed = fields
    .filter((field) => current[field] !== previous[field])
    .map((field) => `${field}: ${Math.abs(current[field].length - previous[field].length)} chars delta`);
  if (changed.length === 0) return "No changes against previous preset.";
  return `Changed fields: ${changed.length}. ${changed.join(" | ")}`;
}

export default function App() {
  const [connection, setConnection] = useState<ConnectionState>(DEFAULT_CONNECTION);
  const [prompts, setPrompts] = useState<PromptState>(DEFAULT_PROMPTS);
  const [query, setQuery] = useState<QueryState>(DEFAULT_QUERY);
  const [result, setResult] = useState<NormalizeResultState | null>(null);
  const [historyItems, setHistoryItems] = useState<HistoryItem[]>([]);
  const [appLogs, setAppLogs] = useState<string[]>([]);
  const [activeTab, setActiveTab] = useState<TabKey>("normalized");
  const [busy, setBusy] = useState(false);
  const [connectionStatus, setConnectionStatus] = useState("");
  const [presetList, setPresetList] = useState<
    Array<{
      id: string;
      name: string;
      prompt_version: string;
      systemPrompt: string;
      developerPrompt: string;
      domainPrompt: string;
      schemaNotes?: string;
      fewShotExamples?: string;
    }>
  >([]);
  const [selectedPresetId, setSelectedPresetId] = useState("");
  const [presetName, setPresetName] = useState("NDC custom preset");
  const [previousPreset, setPreviousPreset] = useState<PromptState | null>(null);
  const [diffSummary, setDiffSummary] = useState("");
  const [useMock, setUseMock] = useState(false);
  const [runs, setRuns] = useState<RuntimeRun[]>([]);
  const [selectedRunId, setSelectedRunId] = useState("");
  const [runTrace, setRunTrace] = useState<unknown[]>([]);
  const [evalBusy, setEvalBusy] = useState(false);
  const [evalReport, setEvalReport] = useState<unknown>(null);
  const [lastError, setLastError] = useState("");

  const [uiMode, setUiMode] = useState<UiMode>("decomposition");
  const [assistantSessionId, setAssistantSessionId] = useState("");
  const [assistantConversation, setAssistantConversation] = useState<AssistantConversationItem[]>([]);
  const [assistantInput, setAssistantInput] = useState("");
  const [assistantBusy, setAssistantBusy] = useState(false);
  const [assistantStatus, setAssistantStatus] = useState("");
  const [assistantError, setAssistantError] = useState("");

  const log = (message: string) => {
    setAppLogs((prev) => [withTs(message), ...prev].slice(0, 300));
  };

  function startAssistantStatusTicker(): () => void {
    let index = 0;
    setAssistantStatus(ASSISTANT_STAGES[0]);
    const timer = window.setInterval(() => {
      index = Math.min(index + 1, ASSISTANT_STAGES.length - 1);
      setAssistantStatus(ASSISTANT_STAGES[index]);
    }, 650);
    return () => window.clearInterval(timer);
  }

  useEffect(() => {
    const cached = localStorage.getItem(SESSION_CONFIG_KEY);
    if (cached) {
      try {
        const parsed = JSON.parse(cached) as Partial<ConnectionState>;
        setConnection((prev) => ({
          ...prev,
          model: parsed.model ?? prev.model,
          baseUrl: parsed.baseUrl ?? prev.baseUrl,
          temperature: parsed.temperature ?? prev.temperature,
          maxOutputTokens: parsed.maxOutputTokens ?? prev.maxOutputTokens
        }));
      } catch {
        // ignore broken local cache
      }
    }

    void refreshHistory();
    void refreshPresets();
    void refreshRuns();
  }, []);

  async function refreshHistory() {
    try {
      const payload = await apiClient.loadHistory();
      setHistoryItems(payload.items ?? []);
    } catch (error) {
      log(`History load error: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  async function refreshPresets() {
    try {
      const payload = await apiClient.loadPresets();
      setPresetList(payload.presets ?? []);
    } catch (error) {
      log(`Presets load error: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  async function refreshRuns() {
    try {
      const payload = await apiClient.listRuns();
      setRuns(payload.items ?? []);
    } catch (error) {
      log(`Runs load error: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  function saveLocalConfig() {
    localStorage.setItem(
      SESSION_CONFIG_KEY,
      JSON.stringify({
        model: connection.model,
        baseUrl: connection.baseUrl,
        temperature: connection.temperature,
        maxOutputTokens: connection.maxOutputTokens
      })
    );
    log("Local config saved (without API key).");
  }

  async function testConnection() {
    setBusy(true);
    setLastError("");
    try {
      const payload = await apiClient.testConnection(connection);
      setConnectionStatus(`OK - ${payload.model}`);
      log(`OpenAI connection ok: ${payload.model}`);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      setConnectionStatus("Connection error");
      setLastError(`Test connection: ${message}`);
      log(`Test connection error: ${message}`);
    } finally {
      setBusy(false);
    }
  }

  async function normalize(saveAsCase: boolean) {
    setBusy(true);
    setLastError("");
    try {
      const payload = await apiClient.normalize({
        connection,
        prompts,
        promptVersion: "normalizer_v2_0_2",
        query: {
          userQuestion: query.userQuestion,
          periodHint: query.periodHint,
          businessContext: query.businessContext,
          expectedRoute: query.expectedRoute
        },
        saveAsTestCase: saveAsCase,
        useMock
      });
      setResult(payload);
      setActiveTab("normalized");
      log(`Normalize done: trace=${payload.trace_id}, validation=${payload.validation.passed ? "passed" : "failed"}`);
      void refreshHistory();
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      setLastError(`Normalize: ${message}`);
      log(`Normalize error: ${message}`);
    } finally {
      setBusy(false);
    }
  }

  function loadSelectedPreset() {
    const selected = presetList.find((item) => item.id === selectedPresetId);
    if (!selected) {
      log("Preset is not selected.");
      return;
    }
    setPreviousPreset(prompts);
    setPrompts({
      systemPrompt: selected.systemPrompt,
      developerPrompt: selected.developerPrompt,
      domainPrompt: selected.domainPrompt,
      schemaNotes: selected.schemaNotes ?? "",
      fewShotExamples: selected.fewShotExamples ?? ""
    });
    log(`Preset loaded: ${selected.name}`);
  }

  async function savePreset() {
    try {
      await apiClient.savePreset({
        name: presetName || "NDC preset",
        prompt_version: "normalizer_v2_0_2",
        systemPrompt: prompts.systemPrompt,
        developerPrompt: prompts.developerPrompt,
        domainPrompt: prompts.domainPrompt,
        schemaNotes: prompts.schemaNotes,
        fewShotExamples: prompts.fewShotExamples
      });
      log("Preset saved.");
      await refreshPresets();
    } catch (error) {
      log(`Preset save error: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  function resetDefaults() {
    setPrompts(DEFAULT_PROMPTS);
    log("Prompt panel reset to defaults.");
  }

  function diffWithPrevious() {
    const summary = diffPrompts(prompts, previousPreset);
    setDiffSummary(summary);
    log(summary);
  }

  function applyBatchFormat() {
    const formatted = query.batchQuestionsRaw
      .split(";")
      .map((item) => item.trim())
      .filter(Boolean)
      .join("\n\n");
    if (!formatted) {
      return;
    }
    setQuery((prev) => ({
      ...prev,
      batchQuestionsRaw: formatted
    }));
    log("Batch field formatted: `;` converted to blank-line separators.");
  }

  async function openTrace(traceId: string) {
    try {
      const payload = await apiClient.loadTrace(traceId);
      const trace = payload.trace as Record<string, unknown>;
      const normalized = (trace.parsed_normalized_json ?? null) as NormalizeResultState["normalized"];
      setResult({
        trace_id: String(trace.trace_id ?? traceId),
        ok: Boolean((trace.validation_result as { passed?: boolean } | undefined)?.passed),
        normalized,
        route_hint_summary:
          (trace.route_hint_summary as NormalizeResultState["route_hint_summary"] | undefined) ??
          (normalized
            ? {
                route_hint: (normalized as { route_hint?: string }).route_hint ?? null,
                confidence: (normalized as { confidence?: { route_hint?: string } }).confidence?.route_hint ?? null
              }
            : null),
        raw_model_output: trace.raw_model_response ?? {},
        validation: (trace.validation_result as NormalizeResultState["validation"]) ?? { passed: false, errors: ["validation not found"] },
        usage: (trace.usage as NormalizeResultState["usage"]) ?? { input_tokens: 0, output_tokens: 0, total_tokens: 0 },
        latency_ms: Number(trace.latency_ms ?? 0),
        prompt_version: String(trace.prompt_version ?? "unknown"),
        schema_version: String(trace.schema_version ?? "unknown")
      });
      setActiveTab("raw");
      setLastError("");
      log(`Trace opened: ${traceId}`);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      setLastError(`Trace: ${message}`);
      log(`Trace open error ${traceId}: ${message}`);
    }
  }

  async function startRun() {
    try {
      const payload = await apiClient.startRun();
      setSelectedRunId(payload.run.runId);
      log(`Run started: ${payload.run.runId}`);
      log("Tip: start run does not execute normalize by itself. Use 'Run eval v2.0.2' button.");
      await refreshRuns();
    } catch (error) {
      log(`Run start error: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  async function finishRun() {
    if (!selectedRunId) return;
    try {
      await apiClient.finishRun(selectedRunId);
      log(`Run finished: ${selectedRunId}`);
      await refreshRuns();
    } catch (error) {
      log(`Run finish error: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  async function runEval() {
    setEvalBusy(true);
    setLastError("");
    try {
      log("Starting eval in v2 contour.");
      const rawQuestions = query.batchQuestionsRaw.trim() || query.userQuestion.trim();
      if (!rawQuestions) {
        throw new Error("Fill batch field or Raw user question first.");
      }
      const payload = await apiClient.runEval({
        connection,
        prompts,
        promptVersion: "normalizer_v2_0_2",
        mode: "single-pass-strict",
        rawQuestions,
        useMock
      });
      setEvalReport(payload.report);
      log("Eval v2.0.2 run finished.");
      const report = payload.report as { metrics?: Record<string, unknown>; run_id?: string };
      if (report.run_id) {
        log(`Eval run id: ${report.run_id}`);
      }
      if (report.metrics) {
        const metrics = report.metrics;
        log(
          `Eval metrics v2.0.2: schema=${metrics.schema_validation_pass_rate ?? "n/a"}%, route_accuracy=${metrics.route_resolution_accuracy ?? "n/a"}%, no_route_precision=${metrics.no_route_precision ?? "n/a"}%, state_consistency=${metrics.execution_state_consistency_rate ?? "n/a"}%`
        );
      }
      await refreshHistory();
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      if (message.includes("Legacy eval runner supports normalized_query_v1 only")) {
        setEvalReport({
          status: "plan_only",
          prompt_version: "normalizer_v2",
          reason: "backend eval runner is still legacy-v1 only",
          plan_file: "reports/v2_pilot_eval_plan.md",
          next_steps: [
            "run cheap mock sanity for schema/fragment/scope",
            "run small real batch (10-15 messages, temperature=0)",
            "run challenge-30 replay with v2 metrics"
          ]
        });
        log("Backend is legacy-only for eval right now. Showing v2 pilot plan.");
      } else {
        setLastError(`Eval: ${message}`);
        log(`Eval run error: ${message}`);
      }
    } finally {
      setEvalBusy(false);
    }
  }

  async function copyEvalReport() {
    try {
      const text = JSON.stringify(evalReport ?? {}, null, 2);
      await navigator.clipboard.writeText(text);
      log("Eval report copied to clipboard.");
    } catch (error) {
      log(`Eval report copy error: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  function resetAssistantSession() {
    setAssistantSessionId("");
    setAssistantConversation([]);
    setAssistantInput("");
    setAssistantStatus("");
    setAssistantError("");
    log("Assistant session reset.");
  }

  async function sendAssistantMessage() {
    const userMessage = assistantInput.trim();
    if (!userMessage) {
      return;
    }

    setAssistantBusy(true);
    setAssistantError("");
    setAssistantInput("");
      setAssistantConversation((prev) => [
      ...prev,
      {
        message_id: `local-${Date.now()}`,
        session_id: assistantSessionId || "pending",
        role: "user",
        text: userMessage,
        reply_type: null,
        created_at: new Date().toISOString(),
        trace_id: null,
        debug: null
      }
    ]);

    const stopTicker = startAssistantStatusTicker();
    try {
      const response = await apiClient.sendAssistantMessage({
        connection,
        prompts,
        userMessage,
        sessionId: assistantSessionId || undefined,
        promptVersion: "normalizer_v2_0_2",
        context: {
          periodHint: query.periodHint,
          businessContext: query.businessContext
        },
        useMock
      });
      setAssistantSessionId(response.session_id);
      setAssistantConversation(response.conversation);
      setAssistantStatus("Ответ готов");
      log(`Assistant reply received: trace=${response.debug.trace_id}`);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      setAssistantError(message);
      setAssistantStatus("Ошибка ассистента");
      log(`Assistant error: ${message}`);
    } finally {
      stopTicker();
      setAssistantBusy(false);
    }
  }

  useEffect(() => {
    if (!selectedRunId) {
      setRunTrace([]);
      return;
    }
    void apiClient
      .runTrace(selectedRunId)
      .then((payload) => setRunTrace(payload.items))
      .catch((error) => log(`Run trace error: ${error instanceof Error ? error.message : String(error)}`));
  }, [selectedRunId]);

  return (
    <main className="app-root">
      <div className="hero">
        <h1>NDC AI First Layer</h1>
        <p>Два режима в одном интерфейсе: диагностика декомпозиции и диалоговый ассистент на общем backend-контуре.</p>
      </div>

      <div className="mode-switch-row">
        <button type="button" className={uiMode === "assistant" ? "tab active" : "tab"} onClick={() => setUiMode("assistant")}>
          Ассистент
        </button>
        <button type="button" className={uiMode === "decomposition" ? "tab active" : "tab"} onClick={() => setUiMode("decomposition")}>
          Декомпозиция
        </button>
      </div>

      {uiMode === "assistant" ? (
        <div className="layout-grid">
          <ConnectionPanel
            value={connection}
            onChange={setConnection}
            onSaveLocalConfig={saveLocalConfig}
            onTestConnection={testConnection}
            lastStatus={connectionStatus}
            busy={busy || assistantBusy}
          />

          <PromptPanel
            value={prompts}
            onChange={setPrompts}
            presets={presetList}
            selectedPresetId={selectedPresetId}
            onSelectPreset={setSelectedPresetId}
            onLoadPreset={loadSelectedPreset}
            onSavePreset={savePreset}
            onResetDefaults={resetDefaults}
            onDiffPrevious={diffWithPrevious}
            presetName={presetName}
            onPresetNameChange={setPresetName}
            diffSummary={diffSummary}
          />

          <AssistantPanel
            sessionId={assistantSessionId}
            conversation={assistantConversation}
            inputValue={assistantInput}
            onInputChange={setAssistantInput}
            periodHint={query.periodHint}
            onPeriodHintChange={(value) => setQuery((prev) => ({ ...prev, periodHint: value }))}
            businessContext={query.businessContext}
            onBusinessContextChange={(value) => setQuery((prev) => ({ ...prev, businessContext: value }))}
            useMock={useMock}
            onUseMockChange={setUseMock}
            onSend={sendAssistantMessage}
            onClear={resetAssistantSession}
            busy={assistantBusy}
            statusText={assistantStatus}
            errorMessage={assistantError}
          />
        </div>
      ) : (
        <div className="layout-grid">
          <ConnectionPanel
            value={connection}
            onChange={setConnection}
            onSaveLocalConfig={saveLocalConfig}
            onTestConnection={testConnection}
            lastStatus={connectionStatus}
            busy={busy}
          />

          <PromptPanel
            value={prompts}
            onChange={setPrompts}
            presets={presetList}
            selectedPresetId={selectedPresetId}
            onSelectPreset={setSelectedPresetId}
            onLoadPreset={loadSelectedPreset}
            onSavePreset={savePreset}
            onResetDefaults={resetDefaults}
            onDiffPrevious={diffWithPrevious}
            presetName={presetName}
            onPresetNameChange={setPresetName}
            diffSummary={diffSummary}
          />

          <QueryPanel
            value={query}
            onChange={setQuery}
            onApplyBatchFormat={applyBatchFormat}
            onNormalize={normalize}
            busy={busy}
            useMock={useMock}
            onUseMockChange={setUseMock}
            errorMessage={lastError}
          />

          <OutputPanel tab={activeTab} onTabChange={setActiveTab} result={result} appLogs={appLogs} />

          <MetricsPanel result={result} />

          <HistoryPanel items={historyItems} onRefresh={refreshHistory} onOpenTrace={openTrace} />

          <RuntimePanel
            runs={runs}
            selectedRunId={selectedRunId}
            onSelectRun={setSelectedRunId}
            onStartRun={startRun}
            onFinishRun={finishRun}
            onRefreshRuns={refreshRuns}
            onRunEval={runEval}
            onCopyEvalReport={copyEvalReport}
            evalBusy={evalBusy}
            traceItems={runTrace}
            evalReport={evalReport}
          />
        </div>
      )}
    </main>
  );
}
