from typing import TYPE_CHECKING

from analogai.deepthink.startup.factories.domain_service_factory import DomainServiceFactory
from analogai.deepthink.infrastructure.gateways.llm.gateway import LlmGateway
from analogai.deepthink.infrastructure.gateways.llm.enums.llm_provider import LlmProvider
from analogai.deepthink.deepthink_engine import DeepthinkEngine
from analogai.deepthink.internal.processors.deepthinker_processor import DeepthinkPipelineProcessor
from analogai.deepthink.internal.processors.large_text_processor import FactBatcher
from analogai.deepthink.internal.processors.chat_synthesizer import ChatResponseSynthesizer
from analogai.deepthink.messaging.fact.factories.fact_factory import FactServiceFactory
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.factories.pipeline_factory import MessagePipelineFactory
from analogai.deepthink.messaging.factories.command_factory import ChatCommandFactory
from analogai.deepthink.messaging.factories.chat_flow_factory import ChatFlowBuilder
from analogai.deepthink.messaging.factories.user_agent_resolver_factory import UserAgentResolverFactory
from analogai.deepthink.messaging.question_answering.factories.question_answering_service_factory import (
    QuestionAnsweringServiceFactory,
)
from analogai.deepthink.messaging.question_answering.question_routing_service import (
    QuestionRoutingService,
)
from analogai.foundation.infrastructure.services.embedding_service import EmbeddingService
from analogai.foundation.modules.fact.subscribers.fact_observer_registry import FactObserverRegistry
from analogai.foundation.settings import config as infra_config
from analogai.foundation.settings import config as app_config
from analogai.foundation.common.logging.app_logger import app_logger

if TYPE_CHECKING:
    from analogai.deepthink.application.usecases.learning.make_direct_statement.ports import MakeStatementOutputPort
    from analogai.deepthink.application.usecases.learning.learn_statement.learn_statement_usecase import MakeStatementUseCase
from analogai.deepthink.application.usecases.learning.make_direct_statement.states.common.response_builder import LearnerResponseBuilder
from analogai.deepthink.application.usecases.learning.make_direct_statement.states.context.learner_context import LearnerContext
from analogai.deepthink.application.usecases.learning.make_direct_statement.states.context.learner_context_handler import LearnerContextHandler
from analogai.deepthink.presentation.presenters.learning.make_direct_statement_presenter import MakeDirectStatementPresenter
from analogai.deepthink.presentation.controllers.learning_controller import LearningController
from analogai.deepthink.presentation.factories.intents_factory import IntentsFactory
from analogai.deepthink.presentation.factories.interpreters_factory import InterpretersFactory
from analogai.deepthink.presentation.intents.intents_processor import IntentsProcessor
from analogai.deepthink.presentation.interpreters.interpreter_pipeline import InterpreterPipeline
from analogai.deepthink.presentation.pipeline.pipeline import MessagePipeline
from analogai.deepthink.presentation.preprocessing.previous_messages_stack import PreviousMessagesStack


def _create_domain_services() -> DomainServiceFactory:
    return DomainServiceFactory()


def _create_extensions_factory(domain_service_factory: DomainServiceFactory):
    from analogai.deepthink.startup.extensions_service_factory import ExtensionsServiceFactory
    return ExtensionsServiceFactory(domain_service_factory)


def _create_learning_use_cases(domain_service_factory: DomainServiceFactory, learning_presenter=None):
    from analogai.deepthink.application.usecases.learning.learn_statement.learn_statement_usecase import MakeStatementUseCase
    from analogai.deepthink.application.usecases.learning.learn_statement.learn_statement_usecase import DirectStatementProvider
    from analogai.deepthink.application.usecases.learning.make_direct_statement.usecase import MakeDirectStatementUseCase
    from analogai.deepthink.application.usecases.learning.make_notion_statement.usecase import UpsertNotionStatementUseCase
    from analogai.deepthink.application.usecases.learning.make_hypothetical_statement.usecase import (
        MakeHypotheticalStatementUseCase,
    )
    from analogai.deepthink.presentation.presenters.learning.make_hypothetical_statement_presenter import (
        MakeHypotheticalStatementPresenter,
    )
    from analogai.deepthink.presentation.presenters.learning.make_notion_statement_presenter import MakeNotionStatementPresenter

    notion_service = domain_service_factory.get_notion_service()
    if learning_presenter is None:
        learning_presenter = MakeDirectStatementPresenter()
    direct_statement_service = domain_service_factory.get_direct_statement_service()

    class _DirectStatementProvider(DirectStatementProvider):
        def __init__(self):
            self._instance = None

        def get(self):
            if self._instance is None:
                context = LearnerContext()
                context_handler = LearnerContextHandler(context, notion_service)
                response_builder = LearnerResponseBuilder(context)
                self._instance = MakeDirectStatementUseCase(
                    statement_service=direct_statement_service,
                    notion_service=notion_service,
                    output_port=learning_presenter,
                    context_handler=context_handler,
                    response_builder=response_builder,
                )
            return self._instance
    fact_service = None
    make_notion_statement = UpsertNotionStatementUseCase(
        notion_service=notion_service,
        output_port=MakeNotionStatementPresenter(fact_service=fact_service),
    )
    make_statement = MakeStatementUseCase(
        direct_statement_provider=_DirectStatementProvider(),
        make_notion_statement=make_notion_statement,
    )
    hypothetical_presenter = MakeHypotheticalStatementPresenter()
    make_hypothetical_statement = MakeHypotheticalStatementUseCase(
        domain_service_factory=domain_service_factory,
        output_port=hypothetical_presenter,
    )
    return make_statement, make_hypothetical_statement




def _build_pipeline(
    domain_service_factory: DomainServiceFactory,
    notion_enrichment_enabled: bool | None = None,
    llm_gateway_instance=None,
) -> MessagePipeline:
    from analogai.deepthink.infrastructure.gateways.llm.llm_factory import LlmGatewayFactory

    learning_presenter = MakeDirectStatementPresenter()

    make_statement, make_hypothetical_statement = _create_learning_use_cases(
        domain_service_factory, learning_presenter=learning_presenter
    )
    if llm_gateway_instance is not None:
        LlmGatewayFactory.set_default_gateway_instance(llm_gateway_instance)

    # Automatic notion enrichment removed; use NotionEnrichmentSleeper instead.
    learning_controller = LearningController(
        learn_statement_usecase=make_statement,
        make_hypothetical_statement_usecase=make_hypothetical_statement,
    )
    intents_factory = IntentsFactory(
        domain_service_factory=domain_service_factory,
        learning_controller=learning_controller,
    )
    intents_processor = IntentsProcessor(intents_factory)
    interpreters_factory = InterpretersFactory()
    interpreters_processor = InterpreterPipeline(interpreters_factory)
    return MessagePipeline(
        interpreters=interpreters_processor,
        intents_processor=intents_processor,
    )


def create_pipeline(
    domain_service_factory=None,
    notion_enrichment_enabled: bool | None = None,
    llm_gateway_instance=None,
):
    if domain_service_factory is None:
        domain_service_factory = _create_domain_services()
    return _build_pipeline(
        domain_service_factory,
        notion_enrichment_enabled=notion_enrichment_enabled,
        llm_gateway_instance=llm_gateway_instance,
    )


def _resolve_llm_provider(value: str | LlmProvider | None) -> LlmProvider | None:
    if value is None:
        return None
    if isinstance(value, LlmProvider):
        return value
    normalized = str(value).strip().lower().replace("-", "_")
    legacy_map = {
        "claude": LlmProvider.ANTHROPIC,
        "azureopenai": LlmProvider.AZURE_OPENAI,
        "xai": LlmProvider.XAI,
    }
    if normalized in legacy_map:
        return legacy_map[normalized]
    return LlmProvider(normalized)


def _log_llm_model_choice(llm_model: str) -> None:
    if llm_model:
        print(f"LLM model selected: {llm_model}")
    else:
        print("LLM model selected: (empty)")


def _log_license_notice() -> None:
    app_logger.info(
        "[License] Protected by Copiright Functional Source License, Version 1.1. "
        "Change Date: 2028-03-02; afterwards the software will be available under the MIT License."
        " For inquiries, please contact www.analogai.net",
    )


def create_deepthink_engine(
    llm_gateway_instance: LlmGateway | None = None,
    llm_provider: LlmProvider | None = None,
    llm_model: str | None = None,
    domain_service_factory: DomainServiceFactory | None = None,
    conversation_service: object | None = None,
    embedding_service: EmbeddingService | None = None,
    fact_service: object | None = None,
    observer_registry: FactObserverRegistry | None = None,
    pipeline: object | None = None,
    question_answering_service: object | None = None,
    chat_synthesizer: ChatResponseSynthesizer | None = None,
):
    from analogai.deepthink.infrastructure.gateways.llm.llm_gateway_provider_factory import (
        LlmGatewayProviderFactory,
    )
    from analogai.deepthink.infrastructure.gateways.llm.llm_factory import LlmGatewayFactory

    resolved_gateway_instance = llm_gateway_instance
    resolved_provider = llm_provider
    resolved_model = llm_model

    _log_license_notice()

    if resolved_provider is None:
        resolved_provider = _resolve_llm_provider(
            app_config.get("llm.preprocessing.provider")
        )
    if not resolved_model:
        resolved_model = app_config.get("llm.preprocessing.model")

    if not resolved_model:
        raise ValueError("llm_model must be provided (set llm.preprocessing.model)")
    if resolved_provider is None:
        raise ValueError("llm_provider must be provided (set llm.preprocessing.provider)")

    _log_llm_model_choice(resolved_model)
    LlmGatewayProviderFactory.set_default_gateway_type(resolved_provider)
    LlmGatewayProviderFactory.set_default_model(resolved_model)
    if resolved_gateway_instance is not None:
        resolved_gateway_instance.set_model(resolved_model)
        LlmGatewayFactory.set_default_gateway_instance(resolved_gateway_instance)
    else:
        LlmGatewayFactory.set_default_gateway_type(resolved_provider)

    if domain_service_factory is None:
        domain_service_factory = _create_domain_services()

    extensions_factory = None
    if conversation_service is None:
        extensions_factory = _create_extensions_factory(domain_service_factory)
        conversation_service = extensions_factory.get_conversation_service()
    if conversation_service is None:
        raise ValueError("conversation_service must be configured")

    cleanup_callbacks = []
    if embedding_service is None:
        embedding_service = EmbeddingService()

    try:
        app_logger.info(
            "[VectorSearchConfig] VECTOR_ENABLED=%s GRAPH_CUTOFF=%s RAG_CUTOFF=%s",
            app_config.get("vector_search.enabled", True),
            app_config.get(
                "vector_search.graph_similarity_cutoff",
                app_config.get("vector_search.similarity_cutoff", infra_config.get("vector.cutoff")),
            ),
            app_config.get(
                "vector_search.rag_similarity_cutoff",
                app_config.get(
                    "vector_search.fact_similarity_cutoff",
                    infra_config.get("FACT_SIMILARITY_CUTOFF"),
                ),
            ),
        )
    except Exception:
        pass

    fact_observer_registry = observer_registry
    source_service = None
    if fact_service is None:
        if extensions_factory is None:
            extensions_factory = _create_extensions_factory(domain_service_factory)
        statement_service = None
        notion_service = None
        try:
            statement_service = domain_service_factory.get_direct_statement_service()
        except Exception:
            statement_service = None
        try:
            notion_service = domain_service_factory.get_notion_service()
        except Exception:
            notion_service = None
        if fact_observer_registry is None:
            fact_observer_registry = FactObserverRegistry()
        fact_service_factory = FactServiceFactory(
            embedding_service=embedding_service,
            statement_service=statement_service,
            notion_service=notion_service,
            observer_registry=fact_observer_registry,
        )
        fact_service = fact_service_factory.create()
        fact_observer_registry = fact_service_factory.get_observer_registry()
        source_service = getattr(fact_service, "_source_service", None)
    else:
        source_service = getattr(fact_service, "_source_service", None)

    context_time_window = _coerce_int(
        app_config.get("qa.fact_context_time_window_seconds"),
        300,
    )
    if context_time_window <= 0:
        context_time_window = None
    fact_context_assembler = FactContextBuilder(
        fact_service,
        time_window_seconds=context_time_window,
    )
    fact_context_service = FactContextWindowService(
        fact_service,
        window=5,
        time_window_seconds=context_time_window,
    )

    if pipeline is None:
        pipeline_factory = MessagePipelineFactory(domain_service_factory)
        pipeline = pipeline_factory.create_message_pipeline(
            notion_enrichment_enabled=app_config.get("app.features.notion_enrichment", False),
            llm_gateway_instance=resolved_gateway_instance,
        )

    if question_answering_service is None:
        qa_factory = QuestionAnsweringServiceFactory(domain_service_factory)
        question_answering_service = qa_factory.create(
            llm_gateway_instance=resolved_gateway_instance,
            fact_context_assembler=fact_context_assembler,
            fact_context_service=fact_context_service,
            fact_service=fact_service,
        )

    router = QuestionRoutingService(question_answering_service)
    command_factory = ChatCommandFactory(
        pipeline=pipeline,
        question_answering_service=question_answering_service,
        observer_registry=fact_observer_registry,
        source_service=source_service,
    )
    remember_command = command_factory.create_remember_command()
    search_command = command_factory.create_search_command()
    background_info_command = command_factory.create_background_info_command()
    chat_flow_factory = ChatFlowBuilder(
        input_router=router,
        remember_command=remember_command,
        search_command=search_command,
    )
    chat_flow = chat_flow_factory.create()
    user_agent_resolver_factory = UserAgentResolverFactory(
        default_user_id="default-user",
        default_agent_id="default-agent",
    )
    user_agent_resolver = user_agent_resolver_factory.create()
    max_paragraphs_per_fact = app_config.get("limits.max_paragraphs_per_fact")
    if max_paragraphs_per_fact is None:
        max_paragraphs_per_fact = app_config.get("limits.max_sentences_per_fact", 3)
    max_paragraphs_per_fact = max(1, int(max_paragraphs_per_fact))
    fact_batcher = FactBatcher(max_paragraphs_per_fact)
    processor = DeepthinkPipelineProcessor(
        chat_flow=chat_flow,
        remember_command=remember_command,
        search_command=search_command,
        background_info_command=background_info_command,
        user_agent_resolver=user_agent_resolver,
        fact_batcher=fact_batcher,
        fact_service=fact_service,
        conversation_service=conversation_service,
        chat_synthesizer=chat_synthesizer or ChatResponseSynthesizer(),
    )

    def _cleanup() -> None:
        for callback in reversed(cleanup_callbacks):
            callback()

    return DeepthinkEngine(processor=processor, on_close=_cleanup if cleanup_callbacks else None)



def _coerce_int(value: object | None, default: int) -> int:
    if value is None:
        return int(default)
    if isinstance(value, bool):
        return int(default)
    if isinstance(value, int):
        return int(value)
    if isinstance(value, float) and value.is_integer():
        return int(value)
    text = str(value).strip()
    if text.startswith(("+", "-")):
        sign = text[0]
        numeric = text[1:]
    else:
        sign = ""
        numeric = text
    if numeric.isdigit():
        return int(f"{sign}{numeric}")
    return int(default)

