import { useCallback, useEffect, useRef, useState } from 'react';
import { toast } from 'sonner';

import { apiFetch } from '@/lib/loading/api-fetch';

import type { ChatbotMessage, ChatbotResponse } from './types';

const csrfToken = (): string =>
    document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') ?? '';

let idCounter = 0;
const nextId = (): string => `m-${Date.now()}-${idCounter++}`;

/** Number of prior turns replayed for context (kept small to bound token cost). */
const HISTORY_LIMIT = 6;

/** localStorage keys + cap — the conversation survives close/reopen and refresh. */
const STORAGE_KEY = 'tll:chatbot:conversation:v2';
/** v1 stored a bare message array (no savedAt) — dropped on load. */
const LEGACY_STORAGE_KEY = 'tll:chatbot:conversation:v1';
const CONV_ID_KEY = 'tll:chatbot:conversation-id:v1';
const MAX_STORED = 60;

/** Conversations idle for longer than this restart from a blank slate. */
const CONVERSATION_TTL_MS = 24 * 60 * 60 * 1000;

/** Shape persisted under STORAGE_KEY — savedAt drives the 24h expiry. */
interface StoredConversation {
    savedAt: number;
    messages: ChatbotMessage[];
}

function newId(): string {
    try {
        return crypto.randomUUID();
    } catch {
        return `c-${Date.now()}-${Math.round(Math.random() * 1e9)}`;
    }
}

/** Stable id grouping all messages of the current conversation (for DB audit). */
function loadConversationId(): string {
    if (typeof window === 'undefined') {
        return '';
    }
    try {
        let id = window.localStorage.getItem(CONV_ID_KEY);
        if (!id) {
            id = newId();
            window.localStorage.setItem(CONV_ID_KEY, id);
        }
        return id;
    } catch {
        return newId();
    }
}

/**
 * Restore the persisted conversation. Anything older than CONVERSATION_TTL_MS
 * is dropped along with its conversation id — keeping the old id would make
 * the backend silently replay a history the user can no longer see.
 */
function loadPersisted(): { messages: ChatbotMessage[]; conversationId: string } {
    if (typeof window === 'undefined') {
        return { messages: [], conversationId: newId() };
    }
    let messages: ChatbotMessage[] = [];
    try {
        window.localStorage.removeItem(LEGACY_STORAGE_KEY);
        const raw = window.localStorage.getItem(STORAGE_KEY);
        if (raw) {
            const parsed = JSON.parse(raw) as Partial<StoredConversation> | null;
            const fresh = typeof parsed?.savedAt === 'number' && Date.now() - parsed.savedAt < CONVERSATION_TTL_MS;
            if (fresh && Array.isArray(parsed?.messages)) {
                messages = parsed.messages;
            } else {
                window.localStorage.removeItem(STORAGE_KEY);
                window.localStorage.removeItem(CONV_ID_KEY);
            }
        } else {
            // No persisted messages (fresh browser, v1→v2 migration): a surviving
            // conversation id would make the backend replay a history the user
            // cannot see — drop it so a fresh conversation starts.
            window.localStorage.removeItem(CONV_ID_KEY);
        }
    } catch {
        // Corrupt payload / storage failure: same rule — never keep an id whose
        // messages could not be restored.
        messages = [];
        try {
            window.localStorage.removeItem(STORAGE_KEY);
            window.localStorage.removeItem(CONV_ID_KEY);
        } catch {
            // storage unavailable — loadConversationId falls back to a new id too.
        }
    }
    return { messages, conversationId: loadConversationId() };
}

/**
 * Conversation state + send logic for the analytics chatbot. Posts to
 * /chatbot/query via apiFetch (so the global loading bar tracks it). The
 * conversation is persisted to localStorage so it survives closing the window
 * and page reloads; `reset` starts a fresh one.
 */
export function useChatbot() {
    // Lazy init: read localStorage (and apply the 24h expiry) exactly once.
    const [initial] = useState(loadPersisted);
    const [messages, setMessages] = useState<ChatbotMessage[]>(initial.messages);
    const [loading, setLoading] = useState(false);
    const messagesRef = useRef<ChatbotMessage[]>(messages);
    const conversationIdRef = useRef<string>(initial.conversationId);
    const hydratedRef = useRef(false);

    // Keep the ref in sync and persist with a savedAt timestamp (cap the stored
    // size). The mount pass is skipped: re-stamping savedAt on every page load
    // would keep an idle conversation alive past the 24h TTL.
    useEffect(() => {
        messagesRef.current = messages;
        if (!hydratedRef.current) {
            hydratedRef.current = true;
            return;
        }
        try {
            const payload: StoredConversation = { savedAt: Date.now(), messages: messages.slice(-MAX_STORED) };
            window.localStorage.setItem(STORAGE_KEY, JSON.stringify(payload));
        } catch {
            // storage full / unavailable — degrade silently to in-memory only.
        }
    }, [messages]);

    // Adopt the conversation id echoed by the backend so the next turn reuses
    // it and server-side history kicks in.
    const adoptConversationId = useCallback((id: string | undefined) => {
        if (!id || id === conversationIdRef.current) {
            return;
        }
        conversationIdRef.current = id;
        try {
            window.localStorage.setItem(CONV_ID_KEY, id);
        } catch {
            // ignore — the ref still carries the id for this session.
        }
    }, []);

    const send = useCallback(
        async (text: string) => {
            const trimmed = text.trim();
            if (!trimmed || loading) {
                return;
            }

            // Build context from the turns BEFORE this new message.
            const history = messagesRef.current
                .filter((m) => m.role === 'user' || (m.role === 'assistant' && !m.error))
                .slice(-HISTORY_LIMIT)
                .map((m) => ({ role: m.role, content: m.content }));

            setMessages((prev) => [...prev, { id: nextId(), role: 'user', content: trimmed }]);
            setLoading(true);

            try {
                const res = await apiFetch('/chatbot/query', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        Accept: 'application/json',
                        'X-CSRF-TOKEN': csrfToken(),
                        'X-Requested-With': 'XMLHttpRequest',
                    },
                    body: JSON.stringify({ message: trimmed, history, conversation_id: conversationIdRef.current }),
                });

                if (!res.ok) {
                    const payload = (await res.json().catch(() => ({}))) as { error?: string; conversation_id?: string };
                    // The 503 path still mints/echoes an id — keep it so the turn
                    // stays attached to the same server-side conversation.
                    adoptConversationId(payload.conversation_id);
                    throw new Error(payload.error || `Erreur ${res.status}`);
                }

                const data = (await res.json()) as ChatbotResponse;
                adoptConversationId(data.conversation_id);
                setMessages((prev) => [
                    ...prev,
                    { id: nextId(), role: 'assistant', content: data.answer || '', blocks: data.blocks ?? [] },
                ]);
            } catch (e) {
                const message = e instanceof Error ? e.message : 'Erreur inconnue';
                setMessages((prev) => [
                    ...prev,
                    { id: nextId(), role: 'assistant', content: message, error: true },
                ]);
                toast.error("L'assistant n'a pas pu répondre", { description: message });
            } finally {
                setLoading(false);
            }
        },
        [loading, adoptConversationId],
    );

    const reset = useCallback(() => {
        setMessages([]);
        conversationIdRef.current = newId();
        try {
            window.localStorage.removeItem(STORAGE_KEY);
            window.localStorage.setItem(CONV_ID_KEY, conversationIdRef.current);
        } catch {
            // ignore
        }
    }, []);

    return { messages, loading, send, reset };
}
