import { useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router";
import { FileText, Menu, PanelRightOpen, X } from "lucide-react";
import { toast } from "sonner";
import KnowledgeManagement from "../KnowledgeManagement";
import {
  createAnalysisMessage,
  deleteAnalysis,
  getAnalysis,
  getAnalysisMessages,
  getDatabaseClients,
  getDocuments,
  listAnalyses,
  updateAnalysisDataBind,
  RESERVED_FAILED_MESSAGE_ID,
  type Analysis,
  type AnalysisMessage,
  type DataBindItem,
} from "@/services/orchestrationApi";
import { AgenticApiError, getObservability, streamChat, streamHelp, type AgentStreamEvent } from "@/services/agenticApi";
import { AnalysisHeader } from "./AnalysisHeader";
import { AppNavigation, type AppMenuKey } from "./AppNavigation";
import { ChatInput } from "./ChatInput";
import { HomeDashboard } from "./HomeDashboard";
import { MessageList } from "./MessageList";
import { NewAnalysisDialog } from "./NewAnalysisDialog";
import { ReportSidebar } from "./ReportSidebar";
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "../ui/resizable";
import { SuggestedQuestionsBar } from "./SuggestedQuestionsBar";
import { clearSession, getCurrentSession } from "./session";
import type { StreamState, UiMessage } from "./types";
import { cx, makeLocalId } from "./utils";

function toUiMessage(message: AnalysisMessage): UiMessage {
  const failed = message.status === "failed";
  return {
    id: message.id,
    role: message.role,
    content: message.content,
    created_at: message.created_at,
    status: failed ? "error" : "complete",
    statusText: failed ? message.note : undefined,
    messageId: message.message_id ?? undefined,
    persistStatus: message.status,
    persistNote: message.note,
  };
}


function sortByUpdatedAtDesc(items: Analysis[]) {
  return [...items].sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime());
}

function normalizeAssistantContent(content: string) {
  return content
    .replace(/([A-Za-z0-9])(?=\*\*)/g, "$1 ")
    .replace(/(\*\*[^*\n]+\*\*)(?=[A-Za-z0-9])/g, "$1 ")
    .replace(/\*\*([^*\n]*?)\*\*/g, (_, inner) => `**${inner.trim()}**`)
    .trim();
}

export function AnalysisShell() {
  const navigate = useNavigate();
  const session = useMemo(() => getCurrentSession(), []);
  const [activeMenu, setActiveMenu] = useState<AppMenuKey>("home");
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  const [mobileReportOpen, setMobileReportOpen] = useState(false);
  const [desktopNavCollapsed, setDesktopNavCollapsed] = useState(false);
  const [analyses, setAnalyses] = useState<Analysis[]>([]);
  const [activeAnalysis, setActiveAnalysis] = useState<Analysis | null>(null);
  const [messages, setMessages] = useState<UiMessage[]>([]);
  const [loadingAnalyses, setLoadingAnalyses] = useState(true);
  const [loadingMessages, setLoadingMessages] = useState(false);
  const [streamState, setStreamState] = useState<StreamState>("idle");
  const [scrollTo, setScrollTo] = useState<"bottom" | "last-ai" | null>(null);
  const [newAnalysisOpen, setNewAnalysisOpen] = useState(false);
  const [reportCollapsed, setReportCollapsed] = useState(false);
  const [reportFullscreen, setReportFullscreen] = useState(false);
  const [reportWidthPercent, setReportWidthPercent] = useState(23);
  const [staleSources, setStaleSources] = useState<DataBindItem[]>([]);

  const selectAnalysis = async (analysis: Analysis) => {
    setActiveAnalysis(analysis);
    setLoadingMessages(true);
    try {
      const [detail, history] = await Promise.all([getAnalysis(analysis.id), getAnalysisMessages(analysis.id)]);
      setActiveAnalysis(detail);
      setAnalyses((prev) => sortByUpdatedAtDesc(prev.map((item) => (item.id === detail.id ? detail : item))));
      setMessages(history.map(toUiMessage));
    } catch (err) {
      toast.error(err instanceof Error ? err.message : "Failed to open analysis");
      setMessages([]);
    } finally {
      setLoadingMessages(false);
    }
  };

  const loadAnalyses = async () => {
    setLoadingAnalyses(true);
    try {
      const result = await listAnalyses({ status: "active", page: 1, limit: 50 });
      setAnalyses(sortByUpdatedAtDesc(result.analyses));
      if (!activeAnalysis && result.analyses.length > 0) {
        await selectAnalysis(result.analyses[0]);
      }
    } catch (err) {
      toast.error(err instanceof Error ? err.message : "Failed to load analyses");
    } finally {
      setLoadingAnalyses(false);
    }
  };

  useEffect(() => {
    loadAnalyses();
  }, []);

  useEffect(() => {
    const checkStaleSources = async () => {
      if (!session?.user_id || !activeAnalysis?.data_bind.length) {
        setStaleSources([]);
        return;
      }

      try {
        const [documents, databaseClients] = await Promise.all([getDocuments(session.user_id), getDatabaseClients(session.user_id)]);
        const documentIds = new Set(documents.map((document) => document.id));
        const databaseIds = new Set(databaseClients.map((client) => client.id));
        setStaleSources(
          activeAnalysis.data_bind.filter((source) => {
            if (source.group_type === "document") return !documentIds.has(source.id);
            if (source.group_type === "database") return !databaseIds.has(source.id);
            return false;
          })
        );
      } catch {
        setStaleSources([]);
      }
    };

    checkStaleSources();
  }, [activeAnalysis?.id, activeAnalysis?.data_bind_version, activeMenu, session?.user_id]);

  const fetchTraceability = async (analysisId: string, messageId: string | undefined, uiMessageId: string) => {
    if (!messageId) return;
    setMessages((prev) => prev.map((msg) => (msg.id === uiMessageId ? { ...msg, traceabilityLoading: true, traceabilityError: null } : msg)));

    try {
      const traceability = await getObservability(analysisId, messageId);
      setMessages((prev) => prev.map((msg) => (msg.id === uiMessageId ? { ...msg, traceability, traceabilityLoading: false } : msg)));
    } catch (err) {
      const traceabilityError =
        err instanceof AgenticApiError && err.status === 404
          ? "Traceability not available for this answer."
          : err instanceof Error
          ? err.message
          : "Failed to load traceability";
      setMessages((prev) => prev.map((msg) => (msg.id === uiMessageId ? { ...msg, traceabilityLoading: false, traceabilityError } : msg)));
    }
  };
  useEffect(() => {
    if (!activeAnalysis?.id || loadingMessages) return;

    messages
      .filter(
        (message) =>
          message.role === "ai" &&
          message.status === "complete" &&
          Boolean(message.messageId) &&
          message.messageId !== RESERVED_FAILED_MESSAGE_ID &&
          !message.traceability &&
          !message.traceabilityLoading &&
          !message.traceabilityError
      )
      .forEach((message) => {
        fetchTraceability(activeAnalysis.id, message.messageId, message.id);
      });
  }, [activeAnalysis?.id, loadingMessages, messages]);

  const runAgentStream = async (kind: "chat" | "help", prompt?: string) => {
    if (!session?.user_id || !activeAnalysis || streamState !== "idle") return;

    const aiId = makeLocalId(kind === "chat" ? "ai" : "help");
    let finalContent = "";
    let doneMessageId: string | undefined;
    let streamError: string | null = null;

    setMessages((prev) => [
      ...prev,
      {
        id: aiId,
        role: "ai",
        content: "",
        created_at: new Date().toISOString(),
        status: "streaming",
        statusText: kind === "chat" ? "Reading analysis context" : "Preparing help",
      },
    ]);

    setScrollTo("bottom");
    setStreamState(kind === "chat" ? "streaming-chat" : "streaming-help");
    try {
      const onEvent = (event: AgentStreamEvent) => {
        if (event.type === "chunk") {
          finalContent += event.data;
          setMessages((prev) => prev.map((msg) => (msg.id === aiId ? { ...msg, content: finalContent, statusText: undefined } : msg)));
        }
        if (event.type === "status") {
          setMessages((prev) => prev.map((msg) => (msg.id === aiId ? { ...msg, statusText: event.data } : msg)));
        }
        if (event.type === "sources") {
          setMessages((prev) => prev.map((msg) => (msg.id === aiId ? { ...msg, sources: event.data } : msg)));
        }
        if (event.type === "done") {
          doneMessageId = typeof event.data.message_id === "string" ? event.data.message_id : undefined;
        }
        if (event.type === "error") {
          streamError = event.data;
          setMessages((prev) => prev.map((msg) => (msg.id === aiId ? { ...msg, status: "error", statusText: event.data } : msg)));
        }
      };

      if (kind === "chat" && prompt) {
        await streamChat({ user_id: session.user_id, analysis_id: activeAnalysis.id, message: prompt }, onEvent);
      } else {
        await streamHelp({ user_id: session.user_id, analysis_id: activeAnalysis.id }, onEvent);
      }

      if (streamError) return;

      const completedContent = normalizeAssistantContent(finalContent);
      let traceTargetId = aiId;
      let resolvedMessageId = doneMessageId;
      setScrollTo("last-ai");
      setMessages((prev) =>
        prev.map((msg) =>
          msg.id === aiId
            ? { ...msg, content: completedContent || msg.content, status: "complete", messageId: doneMessageId, traceabilityLoading: Boolean(doneMessageId) }
            : msg
        )
      );

      if (completedContent) {
        const saved = await createAnalysisMessage(activeAnalysis.id, { role: "ai", content: completedContent, message_id: doneMessageId });
        traceTargetId = saved.id;
        const savedUi = toUiMessage(saved);
        const persistFailed = saved.status === "failed";
        resolvedMessageId = persistFailed ? undefined : savedUi.messageId ?? doneMessageId;
        setMessages((prev) =>
          prev.map((msg) =>
            msg.id === aiId
              ? { ...savedUi, sources: msg.sources, traceabilityLoading: Boolean(resolvedMessageId) }
              : msg
          )
        );
        if (persistFailed) {
          toast.error(saved.note || "Failed to save AI response");
          return;
        }
      }

      await fetchTraceability(activeAnalysis.id, resolvedMessageId, traceTargetId);
    } catch (err) {
      const message = err instanceof Error ? err.message : "Agent stream failed";
      setMessages((prev) => prev.map((msg) => (msg.id === aiId ? { ...msg, status: "error", statusText: message, traceabilityLoading: false } : msg)));
    } finally {
      setStreamState("idle");
    }
  };

  const handleSend = async (content: string) => {
    if (!activeAnalysis || !session?.user_id || streamState !== "idle") return;
    const localId = makeLocalId("user");
    setMessages((prev) => [
      ...prev,
      { id: localId, role: "user", content, created_at: new Date().toISOString(), status: "sending" },
    ]);

    try {
      const saved = await createAnalysisMessage(activeAnalysis.id, { role: "user", content });
      setMessages((prev) => prev.map((msg) => (msg.id === localId ? toUiMessage(saved) : msg)));
    } catch (err) {
      setMessages((prev) => prev.map((msg) => (msg.id === localId ? { ...msg, status: "error", statusText: "Failed to save message" } : msg)));
      toast.error(err instanceof Error ? err.message : "Failed to save message");
      return;
    }

    await runAgentStream("chat", content);
  };

  const handleHelp = async () => {
    await runAgentStream("help");
  };

  const handleCreated = (analysis: Analysis) => {
    setAnalyses((prev) => sortByUpdatedAtDesc([analysis, ...prev.filter((item) => item.id !== analysis.id)]));
    setActiveMenu("analysis-agent");
    selectAnalysis(analysis);
  };

  const handleDelete = async (analysis: Analysis) => {
    if (!window.confirm(`Delete analysis "${analysis.analysis_title}"?`)) return;
    try {
      await deleteAnalysis(analysis.id);
      const next = analyses.filter((item) => item.id !== analysis.id);
      setAnalyses(next);
      if (activeAnalysis?.id === analysis.id) {
        setActiveAnalysis(null);
        setMessages([]);
        setStaleSources([]);
        if (next[0]) selectAnalysis(next[0]);
      }
    } catch (err) {
      toast.error(err instanceof Error ? err.message : "Failed to delete analysis");
    }
  };

  const handleUpdateDataBind = async (items: DataBindItem[]) => {
    if (!activeAnalysis) return;
    const updated = await updateAnalysisDataBind(activeAnalysis.id, activeAnalysis.data_bind_version, items);
    setActiveAnalysis(updated);
    setAnalyses((prev) => sortByUpdatedAtDesc(prev.map((item) => (item.id === updated.id ? updated : item))));
    setStaleSources([]);
  };

  const logout = () => {
    clearSession();
    navigate("/login", { replace: true });
  };

  const changeMenu = (menu: AppMenuKey) => {
    setActiveMenu(menu);
    setMobileNavOpen(false);
    setMobileReportOpen(false);
  };

  const activeMenuLabel =
    activeMenu === "analysis-agent" ? "Analysis Agent" : activeMenu === "knowledge" ? "Knowledge" : "Home";

  const chatPane = (
    <main className="flex h-full min-h-0 min-w-0 flex-col bg-slate-50">
      <AnalysisHeader analysis={activeAnalysis} staleSources={staleSources} onUpdateDataBind={handleUpdateDataBind} />
      <div className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden overscroll-contain">
        {loadingMessages ? (
          <div className="flex h-full items-center justify-center text-sm text-slate-500">Loading messages</div>
        ) : (
          <MessageList
            messages={messages}
            scrollTo={scrollTo}
            onScrollDone={() => setScrollTo(null)}
            businessQuestions={activeAnalysis?.business_questions ?? []}
            onSelectQuestion={handleSend}
          />
        )}
      </div>
      {messages.length > 0 && (
        <SuggestedQuestionsBar questions={activeAnalysis?.business_questions ?? []} onSelect={handleSend} />
      )}
      <ChatInput disabled={!activeAnalysis} streaming={streamState !== "idle"} onSend={handleSend} onHelp={handleHelp} />
    </main>
  );

  const reportPane = reportCollapsed ? (
    <div className="flex h-full min-h-0 flex-col border-l border-slate-200 bg-white p-2">
      <button
        type="button"
        title="Restore report sidebar"
        aria-label="Restore report sidebar"
        onClick={() => setReportCollapsed(false)}
        className="flex h-10 w-10 items-center justify-center rounded-md text-slate-500 hover:bg-slate-100 hover:text-slate-900"
      >
        <PanelRightOpen className="h-4 w-4" />
      </button>
    </div>
  ) : (
    <div className="h-full min-h-0 overflow-y-auto border-l border-slate-200 bg-white p-4">
      <ReportSidebar
        analysis={activeAnalysis}
        userId={session?.user_id}
        onCollapse={() => setReportCollapsed(true)}
        fullscreen={reportFullscreen}
        onToggleFullscreen={() => setReportFullscreen((value) => !value)}
      />
    </div>
  );

  const renderAnalysisAgent = () => (
    <ResizablePanelGroup direction="horizontal" className="h-full min-h-0">
      <ResizablePanel
        id="analysis-chat-panel"
        order={1}
        minSize={reportFullscreen ? 0 : 25}
        className={cx("min-h-0", reportFullscreen && "hidden")}
      >
        {chatPane}
      </ResizablePanel>
      {/* Report pane + handle are desktop-only; mobile reaches the report via the bottom-sheet rendered further below. */}
      {!reportFullscreen && <ResizableHandle withHandle className="hidden lg:flex" />}
      <ResizablePanel
        id="analysis-report-panel"
        order={2}
        minSize={reportFullscreen ? 100 : reportCollapsed ? 4 : 25}
        maxSize={reportFullscreen ? 100 : reportCollapsed ? 4 : 75}
        defaultSize={reportFullscreen ? 100 : reportCollapsed ? 4 : reportWidthPercent}
        onResize={(size) => {
          if (!reportCollapsed && !reportFullscreen) setReportWidthPercent(size);
        }}
        className="hidden min-h-0 lg:block"
      >
        {reportPane}
      </ResizablePanel>
    </ResizablePanelGroup>
  );

  return (
    <div className="workspace-shell min-h-screen bg-slate-100 text-slate-900">
      <header className="sticky top-0 z-40 flex h-14 items-center justify-between border-b border-slate-200 bg-white px-3 lg:hidden">
        <button
          type="button"
          title="Open workspace menu"
          aria-label="Open workspace menu"
          onClick={() => setMobileNavOpen(true)}
          className="flex h-10 w-10 items-center justify-center rounded-md text-slate-600 hover:bg-slate-100 hover:text-slate-950"
        >
          <Menu className="h-5 w-5" />
        </button>
        <div className="min-w-0 text-center">
          <p className="truncate text-sm font-semibold text-slate-950">{activeMenuLabel}</p>
          <p className="text-xs text-slate-500">Data Eyond</p>
        </div>
        {activeMenu === "analysis-agent" ? (
          <button
            type="button"
            title="Open report panel"
            aria-label="Open report panel"
            onClick={() => setMobileReportOpen(true)}
            className="flex h-10 w-10 items-center justify-center rounded-md text-slate-600 hover:bg-slate-100 hover:text-slate-950"
          >
            <FileText className="h-5 w-5" />
          </button>
        ) : (
          <div className="h-10 w-10" />
        )}
      </header>

      {mobileNavOpen && (
        <div className="fixed inset-0 z-50 lg:hidden" role="dialog" aria-modal="true" aria-label="Workspace navigation">
          <button
            type="button"
            aria-label="Close workspace menu overlay"
            onClick={() => setMobileNavOpen(false)}
            className="absolute inset-0 bg-slate-950/40"
          />
          <div className="relative h-full w-72 max-w-[86vw] bg-white shadow-2xl">
            <AppNavigation
              active={activeMenu}
              userName={session?.name}
              analyses={analyses}
              activeAnalysisId={activeAnalysis?.id}
              loadingAnalyses={loadingAnalyses}
              onChange={changeMenu}
              onLogout={logout}
              onNewAnalysis={() => {
                setNewAnalysisOpen(true);
                setMobileNavOpen(false);
              }}
              onSelectAnalysis={(analysis) => {
                selectAnalysis(analysis);
                setMobileNavOpen(false);
              }}
              onDeleteAnalysis={handleDelete}
            />
            <button
              type="button"
              title="Close workspace menu"
              aria-label="Close workspace menu"
              onClick={() => setMobileNavOpen(false)}
              className="absolute right-3 top-3 flex h-9 w-9 items-center justify-center rounded-md text-slate-500 hover:bg-slate-100 hover:text-slate-950"
            >
              <X className="h-4 w-4" />
            </button>
          </div>
        </div>
      )}
      {mobileReportOpen && activeMenu === "analysis-agent" && (
        <div className="fixed inset-0 z-50 lg:hidden" role="dialog" aria-modal="true" aria-label="Report panel">
          <button
            type="button"
            aria-label="Close report panel overlay"
            onClick={() => setMobileReportOpen(false)}
            className="absolute inset-0 bg-slate-950/40"
          />
          <div className="absolute inset-x-0 bottom-0 max-h-[86vh] overflow-y-auto rounded-t-xl bg-white p-4 shadow-2xl">
            <ReportSidebar analysis={activeAnalysis} userId={session?.user_id} onCollapse={() => setMobileReportOpen(false)} />
          </div>
        </div>
      )}

      <div className={cx("lg:grid lg:min-h-0", activeMenu === "analysis-agent" ? "h-[calc(100vh-3.5rem)] min-h-0 overflow-hidden lg:h-screen" : "min-h-screen", desktopNavCollapsed ? "lg:grid-cols-[4.5rem_minmax(0,1fr)]" : "lg:grid-cols-[16rem_minmax(0,1fr)]")}>
        <div className="hidden min-h-0 lg:block">
          <AppNavigation
            active={activeMenu}
            userName={session?.name}
            collapsed={desktopNavCollapsed}
            analyses={analyses}
            activeAnalysisId={activeAnalysis?.id}
            loadingAnalyses={loadingAnalyses}
            onChange={setActiveMenu}
            onLogout={logout}
            onToggleCollapsed={() => setDesktopNavCollapsed((value) => !value)}
            onNewAnalysis={() => setNewAnalysisOpen(true)}
            onSelectAnalysis={selectAnalysis}
            onDeleteAnalysis={handleDelete}
          />
        </div>

        <div className={cx("min-w-0 min-h-0", activeMenu === "analysis-agent" && "h-full min-h-0 overflow-hidden")}>
          {activeMenu === "home" && <HomeDashboard hasAnalyses={analyses.length > 0} onNavigate={changeMenu} />}
          {activeMenu === "knowledge" && <KnowledgeManagement open onClose={() => changeMenu("home")} variant="page" />}
          {activeMenu === "analysis-agent" && renderAnalysisAgent()}
        </div>
      </div>

      <NewAnalysisDialog open={newAnalysisOpen} onClose={() => setNewAnalysisOpen(false)} onCreated={handleCreated} />
    </div>
  );
}