from __future__ import annotations

from typing import Iterable, List, Optional

from analogai.deepthink.application.value_objects.user_agent import UserAgent
from analogai.deepthink.messaging.question_answering.question_entity_service import (
    QuestionEntityExtractionService,
)
from analogai.deepthink.messaging.question_answering.services.formatting.fact_formatter import (
    QuestionFactFormatter,
)
from analogai.deepthink.messaging.question_answering.services.formatting.history_formatter import (
    QuestionHistoryFormatter,
)
from analogai.deepthink.messaging.question_answering.services.background.prompt_background_info_builder import (
    QuestionPromptBackgroundInfoBuilder,
)
from analogai.deepthink.messaging.question_answering.adapters.belief_search_adapter import (
    BeliefSearchAdapter,
)
from analogai.deepthink.messaging.question_answering.adapters.notion_search_adapter import (
    NotionSearchAdapter,
)
from analogai.deepthink.messaging.question_answering.adapters.fact_search_adapter import (
    FactSearchAdapter,
)
from analogai.deepthink.messaging.question_answering.services.assembly.answer_fact_assembler import (
    AnswerFactAssembler,
)
from analogai.deepthink.messaging.fact.services.fact_response_decorator import FactResponseDecorator
from analogai.deepthink.messaging.fact.services.fact_context_assembler import FactContextBuilder
from analogai.deepthink.messaging.fact.services.fact_context_service import FactContextWindowService
from analogai.deepthink.messaging.question_answering.question_answering_llm_client import (
    QuestionAnsweringLlmClient,
)
from analogai.deepthink.messaging.question_answering.qa_synthesizer_prompt_composer import (
    QASynthesizerPromptComposer,
)
from analogai.deepthink.infrastructure.gateways.llm.llm_gateway_provider_factory import (
    LlmGatewayProviderFactory,
)
from analogai.foundation.common.logging.app_logger import app_logger
from analogai.deepthink.settings import config as app_config
from analogai.deepthink.messaging.question_answering.enums.synthesized_text_type import (
    SynthesizedTextType,
)
from analogai.deepthink.messaging.question_answering.strategies.synthesis_strategy import (
    SYNTHESIS_STRATEGIES,
)
from analogai.deepthink.presentation.preprocessing.previous_messages_stack import PreviousMessagesStack


def _resolve_default_synthesized_text_type(value: str | None) -> SynthesizedTextType:
    if value is None:
        raise ValueError(
            "DEFAULT_SYNTHESIZED_TEXT_TYPE must be set to one of: exact_match, detailed"
        )
    normalized = str(value).strip().lower()
    try:
        return SynthesizedTextType(normalized)
    except ValueError as exc:
        raise ValueError(
            "DEFAULT_SYNTHESIZED_TEXT_TYPE must be set to one of: exact_match, detailed"
        ) from exc


_DEFAULT_SYNTHESIZED_TEXT_TYPE = _resolve_default_synthesized_text_type(
    app_config.get("qa.default_synthesized_text_type", "detailed")
)


class QuestionAnsweringService:
    def __init__(
        self,
        belief_service,
        notion_service,
        llm_gateway,
        synthesizer_gateway=None,
        fact_context_assembler: Optional[FactContextBuilder] = None,
        fact_context_service: Optional[FactContextWindowService] = None,
        entity_extractor: Optional[QuestionEntityExtractionService] = None,
        prompt_builder: Optional[QASynthesizerPromptComposer] = None,
        llm_caller: Optional[QuestionAnsweringLlmClient] = None,
        synthesizer_caller: Optional[QuestionAnsweringLlmClient] = None,
        belief_retriever: Optional[BeliefSearchAdapter] = None,
        notion_retriever: Optional[NotionSearchAdapter] = None,
        fact_retriever: Optional[FactSearchAdapter] = None,
        belief_formatter: Optional[QuestionFactFormatter] = None,
        history_formatter: Optional[QuestionHistoryFormatter] = None,
        facts_builder: Optional[AnswerFactAssembler] = None,
        background_info_builder: Optional[QuestionPromptBackgroundInfoBuilder] = None,
    ):
        self._entity_extractor = entity_extractor or QuestionEntityExtractionService()
        self._prompt_builder = prompt_builder or QASynthesizerPromptComposer()
        self._llm_caller = llm_caller or QuestionAnsweringLlmClient(llm_gateway)
        self._provenance_decorator = (
            FactResponseDecorator(fact_context_assembler)
            if fact_context_assembler is not None
            else None
        )
        self._fact_context_service = fact_context_service
        self._synthesizer_gateway = synthesizer_gateway
        self._synthesizer_caller = synthesizer_caller
        self._belief_retriever = belief_retriever or BeliefSearchAdapter(belief_service)
        self._notion_retriever = notion_retriever or NotionSearchAdapter(notion_service)
        self._fact_retriever = fact_retriever
        self._belief_formatter = belief_formatter or QuestionFactFormatter(
            provenance_decorator=self._provenance_decorator,
            fact_context_service=self._fact_context_service,
        )
        self._history_formatter = history_formatter or QuestionHistoryFormatter()
        self._facts_builder = facts_builder or AnswerFactAssembler(self._fact_context_service)
        self._background_info_builder = background_info_builder or QuestionPromptBackgroundInfoBuilder(
            entity_extractor=self._entity_extractor,
            belief_retriever=self._belief_retriever,
            notion_retriever=self._notion_retriever,
            belief_formatter=self._belief_formatter,
            fact_retriever=self._fact_retriever,
        )

    def answer_question(
        self,
        question_text: str,
        user_agent: UserAgent,
        synthesized_text_type: SynthesizedTextType | None = None,
        previous_messages_stack: Optional[PreviousMessagesStack] = None,
    ) -> Optional[str]:
        if not question_text or not question_text.strip():
            return None

        prompt_background_info, belief_count, line_count, beliefs, _, notion_lines, fact_lookup = (
            self._background_info_builder.build_prompt_background_info(
                question_text=question_text,
                user_agent=user_agent,
            )
        )
        if prompt_background_info is None:
            return None
        question_for_prompt, entities_for_prompt, belief_block_for_prompt = prompt_background_info
        if not beliefs and not notion_lines and not fact_lookup:
            app_logger.info(
                "[QuestionAnsweringService] no prompt background info question='%s'",
                question_text.strip(),
            )
            return None
        if belief_count + line_count == 0:
            app_logger.info(
                "[QuestionAnsweringService] empty background info counts beliefs=%d lines=%d question='%s'",
                belief_count,
                line_count,
                question_text.strip(),
            )
            return None
        if not beliefs and not notion_lines and not fact_lookup:
            app_logger.info(
                "[QuestionAnsweringService] no background facts; returning I don't know question='%s'",
                question_text.strip(),
            )
            return "I don't know"

        user_message = self._prompt_builder.build_user_message(
            question=question_for_prompt,
            entities=entities_for_prompt,
            belief_block=belief_block_for_prompt,
        )
        raw_answer = self._llm_caller.generate_answer(user_message)

        qa_history = self._history_formatter.format_qa_history(previous_messages_stack)
        user_history_block = self._history_formatter.format_user_history(previous_messages_stack)

        synthesized = self._synthesize_answer(
            question_text=question_text.strip(),
            belief_block=belief_block_for_prompt,
            raw_answer=raw_answer,
            beliefs=beliefs,
            notion_lines=notion_lines,
            fact_lookup=fact_lookup,
            synthesized_text_type=synthesized_text_type,
            qa_history=qa_history,
            user_history_block=user_history_block,
        )
        if not synthesized:
            app_logger.info(
                "[QuestionAnsweringService] empty synthesized answer question='%s' raw_answer_len=%d beliefs=%d",
                question_text.strip(),
                len(raw_answer or ""),
                len(list(beliefs or [])),
            )
        return synthesized

    def build_question_background_info(
        self,
        question_text: str,
        user_agent: UserAgent,
    ) -> str:
        if not question_text or not question_text.strip():
            return ""

        prompt_background_info, _, _, _, _, _, _ = self._background_info_builder.build_prompt_background_info(
            question_text=question_text,
            user_agent=user_agent,
        )
        if prompt_background_info is None:
            return ""
        return prompt_background_info[2]

    def get_background_info(
        self,
        question_text: str,
        user_agent: UserAgent,
    ) -> str:
        return self.build_question_background_info(
            question_text=question_text,
            user_agent=user_agent,
        )

    def search(
        self,
        question_text: str,
        user_agent: UserAgent,
        synthesized_text_type: SynthesizedTextType | None = None,
        previous_messages_stack: Optional[PreviousMessagesStack] = None,
    ) -> Optional[str]:
        return self.answer_question(
            question_text=question_text,
            user_agent=user_agent,
            synthesized_text_type=synthesized_text_type,
            previous_messages_stack=previous_messages_stack,
        )

    def generate_background_info(
        self,
        question_text: str,
        user_agent: UserAgent,
    ) -> str:
        return self.build_question_background_info(
            question_text=question_text,
            user_agent=user_agent,
        )

    def _synthesize_answer(
        self,
        question_text: str,
        belief_block: str,
        raw_answer: str,
        beliefs: Iterable,
        notion_lines: Iterable[str],
        fact_lookup: dict[str, str],
        synthesized_text_type: SynthesizedTextType | None = None,
        qa_history: str = "",
        user_history_block: str = "",
    ) -> str:
        text_type = synthesized_text_type or _DEFAULT_SYNTHESIZED_TEXT_TYPE
        strategy = SYNTHESIS_STRATEGIES[text_type]
        include_raw = strategy.include_raw_answer
        belief_lines = [self._belief_formatter.format_fact(belief) for belief in beliefs or []]
        facts = self._facts_builder.build_fact_block(
            beliefs=beliefs,
            belief_lines=belief_lines,
            notion_lines=notion_lines,
            fact_lookup=fact_lookup,
            raw_answer=raw_answer,
            qa_history=qa_history,
            user_history_block=user_history_block,
            include_raw_answer=include_raw,
        )
        if not facts:
            return "I don't know"
        if not any(line.strip() for line in facts.splitlines()):
            return "I don't know"
        prompt = self._prompt_builder.build_synthesis_prompt(
            question=question_text,
            tasks="",
            facts=facts,
            qa_history=qa_history,
            synthesized_text_type=strategy.text_type,
        )
        app_logger.info(
            "Synthesizer prompt prepared: facts=%d, prompt_chars=%d",
            len([line for line in facts.splitlines() if line.strip()]),
            len(prompt or ""),
        )
        app_logger.info("Synthesizer prompt:\n%s", prompt)
        caller = self._get_synthesizer_caller()
        return caller.generate_answer(prompt)

    def _get_synthesizer_caller(self) -> QuestionAnsweringLlmClient:
        if self._synthesizer_caller is not None:
            return self._synthesizer_caller
        if self._synthesizer_gateway is None:
            self._synthesizer_gateway = LlmGatewayProviderFactory.create_gateway(
                system_prompt="",
                max_tokens=200,
            )
        self._synthesizer_caller = QuestionAnsweringLlmClient(self._synthesizer_gateway)
        return self._synthesizer_caller
