from __future__ import annotations

from abc import ABC, abstractmethod
from datetime import datetime
from typing import Optional, List
from uuid import uuid4

from analogai.foundation.infrastructure.services.embedding_service import EmbeddingService
from analogai.deepthink.messaging.fact.fact import Fact
from analogai.deepthink.messaging.fact.repositories.fact_repository import FactRepositoryPort
from analogai.deepthink.messaging.fact.repositories.infrastructure.fact_repository import (
    InMemoryFactRepository,
    MongoFactRepository,
)
from analogai.deepthink.messaging.fact.enums.fact_search_mode import FactSearchMode
from analogai.deepthink.messaging.fact.enums.fact_embedding_mode import FactEmbeddingMode
from analogai.deepthink.messaging.text.sentence_chunker import split_into_sentences


class FactServicePort(ABC):
    @abstractmethod
    def record_turn(
        self,
        user_id: str,
        agent_id: str,
        text: str,
        role: str,
        user_name: Optional[str] = None,
        timestamp: Optional[datetime] = None,
    ) -> str:
        raise NotImplementedError

    @abstractmethod
    def link_statement(self, statement_id: str, turn_id: str) -> None:
        raise NotImplementedError

    @abstractmethod
    def link_belief(self, belief_id: str, turn_id: str) -> None:
        raise NotImplementedError

    @abstractmethod
    def link_notion(self, notion_id: str, turn_id: str) -> None:
        raise NotImplementedError

    @abstractmethod
    def link_hypothetical_statement(self, statement_id: str, turn_id: str) -> None:
        raise NotImplementedError

    @abstractmethod
    def get_facts_for_turn(self, turn_id: str) -> List[Fact]:
        raise NotImplementedError

    @abstractmethod
    def get_facts_for_statement(self, statement_id: str) -> List[Fact]:
        raise NotImplementedError

    @abstractmethod
    def get_facts_for_belief(self, belief_id: str) -> List[Fact]:
        raise NotImplementedError

    @abstractmethod
    def get_facts_for_notion(self, notion_id: str) -> List[Fact]:
        raise NotImplementedError

    @abstractmethod
    def get_facts_for_hypothetical_statement(self, statement_id: str) -> List[Fact]:
        raise NotImplementedError

    @abstractmethod
    def get_nearby_turn_facts(
        self,
        turn_id: str,
        user_id: str,
        agent_id: str,
        window: int = 2,
    ) -> List[Fact]:
        raise NotImplementedError

    @abstractmethod
    def search_facts(
        self,
        user_id: str,
        agent_id: str,
        query: str,
        top_k: int = 5,
        similarity_cutoff: Optional[float] = None,
        mode: FactSearchMode = FactSearchMode.VECTOR_THEN_TEXT,
    ) -> List[Fact]:
        raise NotImplementedError


class RepositoryBackedFactService(FactServicePort):
    def __init__(
        self,
        repository: FactRepositoryPort,
        embedding_service: Optional[EmbeddingService] = None,
        embedding_mode: FactEmbeddingMode = FactEmbeddingMode.ENABLED,
        max_chunk_chars: int = 800,
        overlap_sentences: int = 1,
    ) -> None:
        self._repository = repository
        self._embedding_service = embedding_service
        self._embedding_mode = embedding_mode
        self._max_chunk_chars = max(200, int(max_chunk_chars))
        self._overlap_sentences = max(0, int(overlap_sentences))

    def record_turn(
        self,
        user_id: str,
        agent_id: str,
        text: str,
        role: str,
        user_name: Optional[str] = None,
        timestamp: Optional[datetime] = None,
    ) -> str:
        turn_id = str(uuid4())
        self._repository.register_turn(user_id, agent_id, turn_id)
        fact_chunks = self._split_text_into_facts(text)
        embeddings = self._embed_chunks(fact_chunks)
        fact_ids = [str(uuid4()) for _ in fact_chunks]
        for index, fact_text in enumerate(fact_chunks):
            embedding_vector = embeddings[index] if index < len(embeddings) else None
            prev_fact_id = fact_ids[index - 1] if index > 0 else None
            next_fact_id = fact_ids[index + 1] if index + 1 < len(fact_ids) else None
            fact = Fact(
                id=fact_ids[index],
                turn_id=turn_id,
                statement_id=None,
                user_id=user_id,
                agent_id=agent_id,
                text=fact_text,
                embedding=embedding_vector,
                prev_id=prev_fact_id,
                next_id=next_fact_id,
            )
            self._repository.save_fact(fact)
        return turn_id

    def link_statement(self, statement_id: str, turn_id: str) -> None:
        self._repository.link_statement(statement_id, turn_id)
        self._repository.update_facts_statement_id(turn_id, statement_id)

    def link_belief(self, belief_id: str, turn_id: str) -> None:
        self._repository.link_belief(belief_id, turn_id)

    def link_notion(self, notion_id: str, turn_id: str) -> None:
        self._repository.link_notion(notion_id, turn_id)
        self._repository.update_facts_notion_id(turn_id, notion_id)

    def link_hypothetical_statement(self, statement_id: str, turn_id: str) -> None:
        self._repository.link_hypothetical_statement(statement_id, turn_id)
        self._repository.update_facts_hypothetical_statement_id(turn_id, statement_id)

    def get_facts_for_turn(self, turn_id: str) -> List[Fact]:
        return self._repository.get_facts_for_turn(turn_id)

    def get_facts_for_statement(self, statement_id: str) -> List[Fact]:
        turn_id = self._repository.get_turn_id_for_statement(statement_id)
        return self._repository.get_facts_for_turn(turn_id) if turn_id else []

    def get_facts_for_belief(self, belief_id: str) -> List[Fact]:
        turn_id = self._repository.get_turn_id_for_belief(belief_id)
        return self._repository.get_facts_for_turn(turn_id) if turn_id else []

    def get_facts_for_notion(self, notion_id: str) -> List[Fact]:
        turn_id = self._repository.get_turn_id_for_notion(notion_id)
        return self._repository.get_facts_for_turn(turn_id) if turn_id else []

    def get_facts_for_hypothetical_statement(self, statement_id: str) -> List[Fact]:
        turn_id = self._repository.get_turn_id_for_hypothetical_statement(statement_id)
        return self._repository.get_facts_for_turn(turn_id) if turn_id else []

    def get_nearby_turn_facts(
        self,
        turn_id: str,
        user_id: str,
        agent_id: str,
        window: int = 2,
    ) -> List[Fact]:
        index = self._repository.get_turn_ids(user_id, agent_id)
        if not index or turn_id not in index:
            return []
        position = index.index(turn_id)
        start = max(0, position - max(0, int(window)))
        end = min(len(index), position + max(0, int(window)) + 1)
        nearby_ids = index[start:end]
        results: List[Fact] = []
        for tid in nearby_ids:
            results.extend(self._repository.get_facts_for_turn(tid))
        return results

    def search_facts(
        self,
        user_id: str,
        agent_id: str,
        query: str,
        top_k: int = 5,
        similarity_cutoff: Optional[float] = None,
        mode: FactSearchMode = FactSearchMode.VECTOR_THEN_TEXT,
    ) -> List[Fact]:
        if not query or not query.strip():
            return []
        if mode == FactSearchMode.TEXT_ONLY:
            return self._repository.search_text(user_id, agent_id, query, top_k=top_k)
        if mode == FactSearchMode.VECTOR_ONLY:
            return self._search_vector_only(user_id, agent_id, query, top_k, similarity_cutoff)
        if mode == FactSearchMode.VECTOR_THEN_TEXT:
            results = self._search_vector_only(user_id, agent_id, query, top_k, similarity_cutoff)
            return results or self._repository.search_text(user_id, agent_id, query, top_k=top_k)
        return []

    def _embed_chunks(self, chunks: List[str]) -> List[List[float]]:
        if not chunks:
            return []
        if self._embedding_mode != FactEmbeddingMode.ENABLED:
            return []
        if self._embedding_service is None:
            raise RuntimeError("Fact embedding service unavailable")
        return self._embedding_service.encode_batch(chunks)

    def _search_vector_only(
        self,
        user_id: str,
        agent_id: str,
        query: str,
        top_k: int,
        similarity_cutoff: Optional[float],
    ) -> List[Fact]:
        if self._embedding_mode != FactEmbeddingMode.ENABLED:
            return []
        if self._embedding_service is None:
            raise RuntimeError("Fact embedding service unavailable")
        query_embedding = self._embedding_service.encode(query)
        if not query_embedding:
            return []
        return self._repository.search_similar(
            user_id,
            agent_id,
            query_embedding,
            top_k=top_k,
            similarity_cutoff=similarity_cutoff,
        )

    def _split_text_into_facts(self, text: str) -> List[str]:
        if not text or not text.strip():
            return []
        sentence_list = split_into_sentences(text)
        if not sentence_list:
            return [text.strip()]
        fact_chunks: List[str] = []
        current_sentences: List[str] = []
        current_length = 0
        for sentence in sentence_list:
            cleaned_sentence = sentence.strip()
            if not cleaned_sentence:
                continue
            extra_length = len(cleaned_sentence) + (1 if current_sentences else 0)
            if current_sentences and current_length + extra_length > self._max_chunk_chars:
                fact_chunks.append(" ".join(current_sentences).strip())
                overlap_sentences = (
                    current_sentences[-self._overlap_sentences :]
                    if self._overlap_sentences
                    else []
                )
                current_sentences = list(overlap_sentences)
                current_length = sum(len(s) for s in current_sentences) + max(0, len(current_sentences) - 1)
            current_sentences.append(cleaned_sentence)
            current_length = sum(len(s) for s in current_sentences) + max(0, len(current_sentences) - 1)
        if current_sentences:
            fact_chunks.append(" ".join(current_sentences).strip())
        return fact_chunks


class InMemoryFactService(RepositoryBackedFactService):
    def __init__(
        self,
        embedding_service: Optional[EmbeddingService] = None,
        embedding_mode: FactEmbeddingMode = FactEmbeddingMode.ENABLED,
    ):
        super().__init__(
            InMemoryFactRepository(),
            embedding_service=embedding_service,
            embedding_mode=embedding_mode,
        )


class MongoFactService(RepositoryBackedFactService):
    def __init__(
        self,
        repository: Optional[FactRepositoryPort] = None,
        embedding_service: Optional[EmbeddingService] = None,
        embedding_mode: FactEmbeddingMode = FactEmbeddingMode.ENABLED,
    ):
        super().__init__(
            repository or MongoFactRepository(),
            embedding_service=embedding_service,
            embedding_mode=embedding_mode,
        )


