from __future__ import annotations

from typing import Optional, Dict, Tuple

from analogai.deepthink.application.value_objects.user_agent import UserAgent
from analogai.deepthink.settings import config as app_config
from analogai.foundation.common.utils.env_utils import resolve_cpu_count
from analogai.foundation.common.logging.app_logger import app_logger
from analogai.foundation.extensions.conversation.services.conversation_service import ConversationService
from analogai.deepthink.messaging.commands import GenerateBackgroundInfoCommand, RememberCommand, SearchCommand
from analogai.deepthink.messaging.flows.chat_flow import ChatFlow
from analogai.deepthink.internal.processors.chat_synthesizer import ChatResponseSynthesizer
from analogai.deepthink.internal.processors.fact_execution_strategy import FactExecutionMode
from analogai.deepthink.internal.processors.fact_task_runner import FactTaskRunner
from analogai.deepthink.internal.processors.large_text_processor import SentenceBatcher
from analogai.deepthink.messaging.fact.services.fact_service import FactServicePort
from analogai.deepthink.messaging.fact.services.fact_turn_recorder import FactTurnTracker
from analogai.deepthink.messaging.text.sentence_chunker import split_into_sentences
from analogai.deepthink.messaging.identity.user_agent_resolver import UserAgentResolver
from analogai.deepthink.messaging.filters.math_content_filter import RememberEligibilityPolicy
from analogai.deepthink.presentation.preprocessing.previous_messages_stack import PreviousMessagesStack
from analogai.deepthink.messaging.question_answering.enums.synthesized_text_type import (
    SynthesizedTextType,
)


class _RememberFactHandler:
    def __init__(
        self,
        processor: "DeepthinkPipelineProcessor",
        user_agent: UserAgent,
        stack_snapshot: PreviousMessagesStack,
    ) -> None:
        self._processor = processor
        self._user_agent = user_agent
        self._stack_snapshot = stack_snapshot

    def handle(self, segment: str) -> Optional[str]:
        return self._processor._remember_fact(
            segment,
            self._user_agent,
            self._stack_snapshot,
        )


class _BackgroundInfoHandler:
    def __init__(self, command: GenerateBackgroundInfoCommand, user_agent: UserAgent) -> None:
        self._command = command
        self._user_agent = user_agent

    def handle(self, segment: str) -> Optional[str]:
        return self._command.execute(segment, self._user_agent)


class DeepthinkPipelineProcessor:
    def __init__(
        self,
        chat_flow: ChatFlow,
        remember_command: RememberCommand,
        search_command: SearchCommand,
        background_info_command: GenerateBackgroundInfoCommand,
        user_agent_resolver: UserAgentResolver,
        large_text_processor: SentenceBatcher,
        fact_service: FactServicePort,
        turn_recorder: FactTurnTracker,
        conversation_service: Optional[ConversationService] = None,
        chat_synthesizer: Optional[ChatResponseSynthesizer] = None,
        remember_policy: Optional[RememberEligibilityPolicy] = None,
    ):
        self._chat_flow = chat_flow
        self._remember_command = remember_command
        self._search_command = search_command
        self._background_info_command = background_info_command
        self._user_agent_resolver = user_agent_resolver
        self._large_text_processor = large_text_processor
        self._fact_service = fact_service
        self._turn_recorder = turn_recorder
        self._conversation_service = conversation_service
        self._chat_synthesizer = chat_synthesizer
        self._fact_task_runner = FactTaskRunner()
        self._internal_stacks: Dict[Tuple[str, str], PreviousMessagesStack] = {}
        self._remember_policy = remember_policy or RememberEligibilityPolicy()

    def deepthink(
        self,
        input_text: str,
        user_agent: Optional[UserAgent] = None,
        previous_messages_stack: Optional[PreviousMessagesStack] = None,
    ) -> Optional[str]:
        return self.chat(
            input_text=input_text,
            user_agent=user_agent,
            previous_messages_stack=previous_messages_stack,
        )

    def chat(
        self,
        input_text: str,
        user_agent: Optional[UserAgent] = None,
        previous_messages_stack: Optional[PreviousMessagesStack] = None,
    ) -> Optional[str]:
        resolved_user_agent = self._resolve_user_agent(user_agent)
        stack = self._resolve_prev_message_stack(previous_messages_stack, resolved_user_agent)
        sentences = split_into_sentences(input_text)
        if not sentences:
            return None
        statements: list[str] = []
        questions: list[str] = []
        for sentence in sentences:
            cleaned = sentence.strip()
            if not cleaned:
                continue
            if self._chat_flow.detect_question(cleaned):
                questions.append(cleaned)
            else:
                statements.append(cleaned)

        statement_text = " ".join(statements).strip() if statements else None
        question_text = " ".join(questions).strip() if questions else None

        statement_response: Optional[str] = None
        question_response: Optional[str] = None
        if statement_text:
            with self._turn_recorder.record_user_turn(statement_text, resolved_user_agent):
                statement_results = self._remember_facts(
                    statement_text,
                    resolved_user_agent,
                    stack,
                )
            statement_response = _aggregate_responses(statement_results)
            stack.add_user(statement_text)
            if statement_response:
                stack.add_agent(statement_response)
        if question_text:
            question_response = self._chat_flow.handle_question(
                question_text,
                resolved_user_agent,
                stack,
            )

        combined = " ".join(
            part for part in [statement_response, question_response] if part
        ).strip()
        final_response: Optional[str]
        if combined:
            final_response = self._apply_chat_synthesizer(combined)
        elif question_response is not None:
            final_response = self._apply_chat_synthesizer(question_response)
        elif statement_response is not None:
            final_response = self._apply_chat_synthesizer(statement_response)
        else:
            final_response = input_text.strip() or None

        self._record_conversation_turn(resolved_user_agent, input_text, final_response)
        return final_response

    def remember(
        self,
        input_text: str,
        user_agent: Optional[UserAgent] = None,
        previous_messages_stack: Optional[PreviousMessagesStack] = None,
    ) -> Optional[str]:
        resolved_user_agent = self._resolve_user_agent(user_agent)
        stack = self._resolve_prev_message_stack(previous_messages_stack, resolved_user_agent)
        with self._turn_recorder.record_user_turn(input_text, resolved_user_agent):
            results = self._remember_facts(
                input_text,
                resolved_user_agent,
                stack,
            )
        response_text = results[-1] if results else None
        stack.add_user(input_text)
        if response_text:
            stack.add_agent(response_text)
        self._record_conversation_turn(resolved_user_agent, input_text, response_text)
        return response_text

    def _remember_facts(
        self,
        input_text: str,
        user_agent: UserAgent,
        stack: PreviousMessagesStack,
    ) -> list[Optional[str]]:
        stack_snapshot = self._clone_stack(stack)
        segments = self._large_text_processor.chunk_input(input_text)
        handler = _RememberFactHandler(self, user_agent, stack_snapshot)
        multithread_enabled = app_config.get(
            "pipelines.multithread_remember_enabled",
            True,
        )
        max_workers = _resolve_remember_max_workers()
        return self._fact_task_runner.run(
            segments,
            handler,
            mode=(
                FactExecutionMode.PARALLEL
                if multithread_enabled
                else FactExecutionMode.SEQUENTIAL
            ),
            max_workers=max_workers,
        )

    def _remember_fact(
        self,
        segment: Optional[str],
        user_agent: UserAgent,
        stack_snapshot: PreviousMessagesStack,
    ) -> Optional[str]:
        if not segment or not self._remember_policy.should_remember(segment):
            return None
        safe_segment = segment.replace('"', "") if segment else segment
        stack = self._clone_stack(stack_snapshot)
        return self._remember_command.execute(
            safe_segment,
            user_agent,
            stack,
        )

    def search(
        self,
        input_text: str,
        user_agent: Optional[UserAgent] = None,
        previous_messages_stack: Optional[PreviousMessagesStack] = None,
        synthesized_text_type: Optional[SynthesizedTextType] = None,
    ) -> Optional[str]:
        resolved_user_agent = self._resolve_user_agent(user_agent)
        stack = self._resolve_prev_message_stack(previous_messages_stack, resolved_user_agent)
        response = self._search_command.execute(
            input_text,
            resolved_user_agent,
            stack,
            synthesized_text_type=synthesized_text_type,
        )
        self._record_conversation_turn(resolved_user_agent, input_text, response)
        return response

    def _record_conversation_turn(
        self,
        user_agent: UserAgent,
        user_text: Optional[str],
        agent_response: Optional[str],
    ) -> None:
        if self._conversation_service is None:
            return
        if not user_text or not user_text.strip():
            return
        try:
            conversation = self._conversation_service.ensure_conversation_exists(
                user_id=user_agent.user_id,
                agent_id=user_agent.agent_id,
            )
            self._conversation_service.add_turn(
                conversation.id,
                user_agent.user_id,
                user_agent.agent_id,
                user_text,
                agent_response or "",
            )
        except Exception:
            try:
                app_logger.debug("[DeepthinkPipelineProcessor] Failed to persist conversation turn")
            except Exception:
                pass

    def generate_background_info(
        self,
        input_text: str,
        user_agent: Optional[UserAgent] = None,
        previous_messages_stack: Optional[PreviousMessagesStack] = None,
    ) -> str:
        resolved_user_agent = self._resolve_user_agent(user_agent)
        segments = self._large_text_processor.chunk_input(input_text)
        handler = _BackgroundInfoHandler(self._background_info_command, resolved_user_agent)
        contexts = self._fact_task_runner.run(
            segments,
            handler,
            mode=FactExecutionMode.SEQUENTIAL,
        )
        return "\n\n".join(context for context in contexts if context)

    def get_background_info(
        self,
        input_text: str,
        user_agent: Optional[UserAgent] = None,
        previous_messages_stack: Optional[PreviousMessagesStack] = None,
    ) -> str:
        return self.generate_background_info(
            input_text=input_text,
            user_agent=user_agent,
            previous_messages_stack=previous_messages_stack,
        )

    def _resolve_prev_message_stack(
        self,
        stack: Optional[PreviousMessagesStack],
        user_agent: UserAgent,
    ) -> PreviousMessagesStack:
        if stack is not None:
            return stack
        key = (user_agent.user_id, user_agent.agent_id)
        if key not in self._internal_stacks:
            self._internal_stacks[key] = PreviousMessagesStack()
        return self._internal_stacks[key]

    def _resolve_user_agent(
        self,
        user_agent: Optional[UserAgent],
    ) -> UserAgent:
        return self._user_agent_resolver.resolve_user_agent(user_agent)

    @staticmethod
    def _clone_stack(stack: PreviousMessagesStack) -> PreviousMessagesStack:
        cloned = PreviousMessagesStack()
        for role, text in stack.get_history_pairs():
            if role == "user":
                cloned.add_user(text)
            else:
                cloned.add_agent(text)
        return cloned

    def _synthesize_chat_response(
        self,
        responses: list[Optional[str]],
    ) -> Optional[str]:
        aggregated = _aggregate_responses(responses)
        if aggregated is None:
            return None
        return self._apply_chat_synthesizer(aggregated)

    def _apply_chat_synthesizer(self, text: str) -> str:
        if not text:
            return text
        if self._chat_synthesizer is None:
            return text
        return self._chat_synthesizer.synthesize(text)


def _aggregate_responses(responses: list[Optional[str]]) -> Optional[str]:
    parts = [item.strip() for item in responses if item and item.strip()]
    if not parts:
        return None
    aggregated = " ".join(parts).strip()
    return aggregated or None


def _default_remember_max_workers() -> int:
    cpu_count = resolve_cpu_count()
    computed = (cpu_count - 2) * 2
    return max(1, computed)


def _resolve_remember_max_workers() -> int:
    configured = app_config.get(
        "limits.remember_max_workers",
        _default_remember_max_workers(),
    )
    if configured <= 0:
        configured = _default_remember_max_workers()
    return max(1, configured)
