'use client';

import { useMemo, useRef, useState } from 'react';
import { ChevronDown } from 'lucide-react';
import type { Agent, Conversation, Message, OllamaData } from '../../lib/types';
import { addMessage, updateConversation, updateMessage } from '../../lib/db';
import { InputBar } from './InputBar';
import { MessageThread } from './MessageThread';
import { ContextBar } from './ContextBar';
import { estimateTokens, getModelContextWindow } from '../../lib/format';
import { autoDetectDefaults } from '../../lib/router';
import type { ModelDefaults } from '../../lib/types';

interface ConversationViewProps {
  conversation: Conversation;
  initialMessages: Message[];
  initialTemplate?: string;
  agent?: Agent;
  onConversationUpdated?: (next: Conversation) => void;
}

export function ConversationView({
  conversation,
  initialMessages,
  initialTemplate,
  agent,
  onConversationUpdated,
}: ConversationViewProps) {
  const [title, setTitle] = useState(conversation.title);
  const [messages, setMessages] = useState<Message[]>(initialMessages);
  const [streaming, setStreaming] = useState(false);
  const [selectedModel, setSelectedModel] = useState(conversation.model || '__auto__');
  const [models, setModels] = useState<string[]>([]);
  const [showModels, setShowModels] = useState(false);
  const [modelDefaults, setModelDefaults] = useState<ModelDefaults | null>(null);
  const abortRef = useRef<AbortController | null>(null);
  const [streamingId, setStreamingId] = useState<string | undefined>();
  const [streamTokens, setStreamTokens] = useState(0);
  const [streamTokPerSec, setStreamTokPerSec] = useState(0);
  const streamStartRef = useRef(0);
  const streamTokenCountRef = useRef(0);

  const usedTokens = useMemo(() => {
    return messages.reduce((sum, m) => sum + estimateTokens(m.content), 0);
  }, [messages]);

  const maxTokens = getModelContextWindow(selectedModel);

  const ensureModels = async () => {
    if (models.length > 0 && modelDefaults) return;
    const res = await fetch('/api/ollama');
    if (!res.ok) return;
    const data = (await res.json()) as OllamaData;
    const installed = data.models.map(m => m.name);
    setModels(installed);

    const stored = localStorage.getItem('llm-model-defaults');
    if (stored) {
      try {
        setModelDefaults(JSON.parse(stored) as ModelDefaults);
        return;
      } catch {
        // fall through
      }
    }

    const detected = autoDetectDefaults(installed);
    setModelDefaults(detected);
    localStorage.setItem('llm-model-defaults', JSON.stringify(detected));
  };

  const persistConversationMeta = async (nextMessages: Message[]) => {
    const now = Date.now();
    const tokenTotal = nextMessages.reduce((sum, m) => sum + (m.metrics?.totalTokens ?? 0), 0);
    const next = await updateConversation(conversation.id, {
      model: selectedModel,
      updatedAt: now,
      messageCount: nextMessages.length,
      metadata: {
        ...conversation.metadata,
        totalTokens: tokenTotal,
        totalPrompts: nextMessages.filter(m => m.role === 'user').length,
        models: Array.from(
          new Set(nextMessages.map(m => m.model).filter((m): m is string => Boolean(m)))
        ),
      },
    });
    if (next && onConversationUpdated) onConversationUpdated(next);
  };

  const maybeAutoTitle = async (firstUserMessage: string, model: string) => {
    if (title !== 'New Conversation' || !firstUserMessage.trim()) return;

    try {
      const res = await fetch('/api/ollama/title', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message: firstUserMessage, model }),
      });
      const data = (await res.json()) as { title?: string };
      const nextTitle = (data.title || 'New Conversation').trim();
      if (!nextTitle) return;

      setTitle(nextTitle);
      await updateConversation(conversation.id, { title: nextTitle, updatedAt: Date.now() });
    } catch {
      // Keep default title on failure
    }
  };

  const onSend = async (text: string) => {
    await ensureModels();

    const requestedModel = selectedModel !== '__auto__' ? selectedModel : undefined;

    const now = Date.now();
    const userMessage: Message = {
      id: crypto.randomUUID(),
      conversationId: conversation.id,
      role: 'user',
      content: text,
      timestamp: now,
      model: requestedModel,
    };

    await addMessage(userMessage);
    setMessages(prev => [...prev, userMessage]);

    const controller = new AbortController();
    abortRef.current = controller;
    setStreaming(true);
    setStreamTokens(0);
    setStreamTokPerSec(0);
    streamStartRef.current = Date.now();
    streamTokenCountRef.current = 0;

    const chatPayload = [...messages, userMessage].map(m => ({
      role: m.role,
      content: m.content,
    }));

    let assistantContent = '';
    const assistantId = crypto.randomUUID();
    setStreamingId(assistantId);

    try {
      const res = await fetch('/api/ollama/chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          model: requestedModel ?? '__auto__',
          messages: chatPayload,
        }),
        signal: controller.signal,
      });

      if (!res.ok || !res.body) {
        throw new Error('Failed to stream response');
      }

      const routedModel = res.headers.get('x-by-model') || requestedModel || '';
      if (!routedModel) {
        throw new Error('Router did not return a model');
      }
      const previousUserModel = userMessage.model;
      userMessage.model = routedModel;
      if (routedModel !== previousUserModel) {
        await updateMessage(userMessage.id, { model: routedModel });
      }
      setMessages(prev =>
        prev.map(message => (message.id === userMessage.id ? userMessage : message))
      );

      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let buffer = '';
      let totalTokens = 0;
      let durationMs = 0;

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split('\n');
        buffer = lines.pop() || '';

        for (const line of lines) {
          if (!line.trim()) continue;
          const chunk = JSON.parse(line) as {
            message?: { content?: string };
            done?: boolean;
            eval_count?: number;
            eval_duration?: number;
          };

          if (chunk.message?.content) {
            assistantContent += chunk.message.content;
            streamTokenCountRef.current += 1;
            const elapsed = (Date.now() - streamStartRef.current) / 1000;
            setStreamTokens(streamTokenCountRef.current);
            setStreamTokPerSec(elapsed > 0 ? streamTokenCountRef.current / elapsed : 0);
            const tempAssistant: Message = {
              id: assistantId,
              conversationId: conversation.id,
              role: 'assistant',
              content: assistantContent,
              timestamp: Date.now(),
              model: routedModel,
            };

            setMessages(prev => {
              const withoutTemp = prev.filter(m => m.id !== assistantId);
              return [...withoutTemp, tempAssistant];
            });
          }

          if (chunk.done && chunk.eval_count && chunk.eval_duration) {
            totalTokens = chunk.eval_count;
            durationMs = chunk.eval_duration / 1e6;
          }
        }
      }

      const tokensPerSec = durationMs > 0 ? (totalTokens / durationMs) * 1000 : 0;
      const finalAssistant: Message = {
        id: assistantId,
        conversationId: conversation.id,
        role: 'assistant',
        content: assistantContent,
        timestamp: Date.now(),
        model: routedModel,
        metrics: {
          tokensPerSec,
          totalTokens,
          promptTokens: estimateTokens(text),
          durationMs,
        },
      };

      await addMessage(finalAssistant);
      const nextMessages = [...messages, userMessage, finalAssistant];
      await persistConversationMeta(nextMessages);

      const userCount = nextMessages.filter(m => m.role === 'user').length;
      if (userCount === 1) {
        await maybeAutoTitle(text, routedModel);
      }
    } catch (err) {
      if (!controller.signal.aborted) {
        const failed: Message = {
          id: assistantId,
          conversationId: conversation.id,
          role: 'assistant',
          content: `Error: ${String(err)}`,
          timestamp: Date.now(),
          model: requestedModel,
        };
        setMessages(prev => {
          const withoutTemp = prev.filter(m => m.id !== assistantId);
          return [...withoutTemp, failed];
        });
        await addMessage(failed);
      }
    } finally {
      abortRef.current = null;
      setStreaming(false);
      setStreamingId(undefined);
      setShowModels(false);
    }
  };

  const onCancel = () => {
    abortRef.current?.abort();
    abortRef.current = null;
    setStreaming(false);
    setStreamingId(undefined);
  };

  const onRegenerate = async (messageId: string, model?: string) => {
    const idx = messages.findIndex(m => m.id === messageId);
    if (idx < 1) return;
    const userMsg = messages[idx - 1];
    if (!userMsg || userMsg.role !== 'user') return;
    if (model) setSelectedModel(model);
    await onSend(userMsg.content);
  };

  const onRate = async (messageId: string, rating: 'up' | 'down') => {
    await updateMessage(messageId, { rating });
    setMessages(prev => prev.map(m => (m.id === messageId ? { ...m, rating } : m)));
  };

  return (
    <div className="flex h-screen flex-col">
      <header className="border-b border-white/10 bg-[var(--bg-elevated)] px-4 py-3">
        <div className="flex items-center justify-between gap-3">
          <div>
            <h1 className="text-lg font-semibold text-[var(--text-primary)]">{title}</h1>
            <p className="text-xs text-[var(--text-tertiary)]">{conversation.id}</p>
            {agent ? (
              <p className="mt-1 inline-flex items-center gap-1 rounded border border-white/10 bg-[var(--surface-muted)] px-2 py-0.5 text-[10px] text-[var(--text-secondary)]">
                <span>{agent.icon}</span>
                <span>{agent.name}</span>
              </p>
            ) : null}
          </div>

          <div className="relative">
            <button
              type="button"
              onClick={async () => {
                await ensureModels();
                setShowModels(prev => !prev);
              }}
              className="inline-flex items-center gap-1 rounded border border-white/10 bg-[var(--surface-card)] px-2 py-1 text-xs text-[var(--text-secondary)]"
            >
              {selectedModel === '__auto__' ? 'Auto' : selectedModel || 'Select model'}
              <ChevronDown size={12} />
            </button>
            {showModels && models.length > 0 ? (
              <div className="absolute right-0 z-20 mt-1 max-h-64 w-64 overflow-auto rounded border border-white/10 bg-[var(--surface-card)] p-1">
                <button
                  type="button"
                  onClick={() => {
                    setSelectedModel('__auto__');
                    setShowModels(false);
                  }}
                  className="block w-full rounded px-2 py-1 text-left text-xs text-[var(--accent-secondary)] hover:bg-white/5"
                >
                  Auto (recommended)
                </button>
                {models.map(model => (
                  <button
                    key={model}
                    type="button"
                    onClick={() => {
                      setSelectedModel(model);
                      setShowModels(false);
                    }}
                    className="block w-full rounded px-2 py-1 text-left text-xs text-[var(--text-secondary)] hover:bg-white/5 hover:text-[var(--text-primary)]"
                  >
                    {model}
                  </button>
                ))}
              </div>
            ) : null}
          </div>
        </div>

        <ContextBar usedTokens={usedTokens} maxTokens={maxTokens} />
      </header>

      <MessageThread
        messages={messages}
        streamingId={streamingId}
        streamTokens={streamTokens}
        streamTokPerSec={streamTokPerSec}
        onRegenerate={(mid, mdl) => void onRegenerate(mid, mdl)}
        onRate={(mid, r) => void onRate(mid, r)}
        models={models}
      />
      <InputBar
        onSend={onSend}
        streaming={streaming}
        onCancel={onCancel}
        disabled={!selectedModel}
        initialText={initialTemplate || ''}
        examplePrompts={agent?.examplePrompts || []}
      />
    </div>
  );
}
