from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from kele.config import ExecutorConfig, RunControlConfig
from kele.control import InferencePath, InferenceStatus, create_executor_manager
from kele.control.run_mode import RunMode
from kele.syntax import FACT_TYPE, CompoundTerm, Constant, Question, SankuManagementSystem, Variable, _QuestionRule
from kele.utils import summarize_items

if TYPE_CHECKING:
    from collections.abc import Mapping, Sequence

    from kele.control.status import MainLoopManager, QuerySolutionManager, StatusChecker
    from kele.equality import Equivalence
    from kele.grounder import GroundedRule
    from kele.knowledge_bases import FactBase

logger = logging.getLogger(__name__)


class Executor:
    """Check grounded rules and update the fact base."""

    def __init__(  # noqa: PLR0913
        self,
        equivalence: Equivalence,
        sk_system_handler: SankuManagementSystem,
        fact_base: FactBase,
        *,
        main_loop_manager: MainLoopManager | None = None,  # 仅用于更新主循环控制器。如在测试 executor 功能时可以为空
        solution_manager: QuerySolutionManager,
        status_checker: StatusChecker | None = None,
        inference_path: InferencePath | None = None,  # 如在测试 executor 功能时可以为空
        args: ExecutorConfig,
    ):
        """Create an executor for grounded rule checking.

        :param equivalence: Equivalence manager.
        :param sk_system_handler: Handler for the three systems (text base,
            database, knowledge base). This is currently a placeholder.
        :param fact_base: Fact base to update with derived facts.
        :param main_loop_manager: Main loop controller (optional for tests).
        :param solution_manager: Solution manager for query bindings.
        :param status_checker: Shared status checker (optional).
        :param inference_path: Inference path recorder (optional for tests).
        :param args: Executor configuration for rule selection and termination.
        """
        self.grounded_rules: Sequence[GroundedRule]
        self.args = args
        # TODO: 此时的 select num 是从 abstract rule 层面进行选择的，
        #  理想情况下可考虑对每个 abstract rule 进一步选择其候选值。
        #  另外这里的选择与 grounder 结束时的选择存在一定重复，后续可统一整理。
        self.equivalence = equivalence
        self.sk_system_handler = sk_system_handler
        self.fact_base = fact_base  # 仅用于更新全局事实，不用于推理
        self._mode: RunMode = RunMode.INFERENCE
        # TODO: Move conflict probing to stop at the first conflict fact inside
        # execute(mode=CONFLICT_CHECK), then remove this buffer/getter plumbing.
        self._conflict_probe_buffer: FACT_TYPE | None = None

        self.executor_manager = create_executor_manager(
            equivalence,
            sk_system_handler,
            solution_manager=solution_manager,
            args=self.args,
            status_checker=status_checker,
        )

        self.main_loop_manager = main_loop_manager
        self.inference_path = (
            inference_path if inference_path is not None else InferencePath(args=RunControlConfig(), equivalence=self.equivalence)
        )
        # FIXME: 只是为了本次改动不要过大做的折中，正常是 executor 需要显式传 args。

    def get_equivalence(self) -> Equivalence:
        """Return the equivalence manager."""
        return self.equivalence

    @property
    def mode(self) -> RunMode:
        """当前运行模式。"""
        return self._mode

    @mode.setter
    def mode(self, mode: RunMode) -> None:
        if not isinstance(mode, RunMode):
            raise TypeError(f"mode must be RunMode, got {type(mode).__name__}")
        self._mode = mode

    def set_mode(self, mode: RunMode) -> None:
        """显式设置当前运行模式（等价于 ``self.mode = mode``）。"""
        self.mode = mode

    def get_conflict_probe_buffer(self) -> list[FACT_TYPE]:
        """返回 conflict 探测阶段累计到的事实副本。"""
        if self._conflict_probe_buffer is None:
            return []
        return [self._conflict_probe_buffer]

    def _sort_grounded_rules(self, question: Question) -> None:
        """Sort grounded rules so higher-priority rules execute first."""
        self.grounded_rules = self.grounded_rules

    def execute(  # noqa: C901  # NOW: keep behavior stable; split flow in a follow-up refactor.
        self,
        grounded_rules: Sequence[GroundedRule],
        question: Question,
    ) -> InferenceStatus:
        """Execute grounded rules and update facts.

        Main-loop status is updated after each non-question rule in inference mode.
        In conflict-check mode, execution only collects probe facts.
        """
        mode = self.mode
        logger.info("Starting execution for %s (mode=%s)", question.description, mode.value)

        if mode == RunMode.CONFLICT_CHECK:
            self._conflict_probe_buffer = None

        self.grounded_rules = grounded_rules
        self._sort_grounded_rules(question)

        try:
            select_num = len(self.grounded_rules) if self.args.executing_rule_num == -1 else self.args.executing_rule_num
            for i in range(min(select_num, len(self.grounded_rules))):
                grounding_result = grounded_rules[i].check_grounding()
                logger.info(
                    "Generate %s%i%s grounded rules from %s%s%s",
                    "\033[91m",
                    len(grounding_result),
                    "\033[0m",
                    "\033[96m",
                    grounded_rules[i].rule,
                    "\033[0m",
                )

                if logger.isEnabledFor(logging.DEBUG):
                    grounded_rule_text = grounded_rules[i].print_all_grounded_rules()
                    summary = summarize_items(grounded_rule_text)
                    logger.debug(
                        "all grounded rule text summary (unique rows: %s): %s",
                        grounded_rules[i].total_table_unique_height(),
                        summary,
                    )

                added_facts = self._update_facts(grounding_result)
                logger.info(
                    "%s%i%s new facts are derived from Rule %s%s%s",
                    "\033[91m",
                    len(added_facts),
                    "\033[0m",
                    "\033[96m",
                    grounded_rules[i].rule,
                    "\033[0m",
                )

                if logger.isEnabledFor(logging.DEBUG):
                    summary = summarize_items(added_facts, formatter=str)
                    logger.debug("new facts summary: %s", summary)

                if (
                    mode == RunMode.INFERENCE
                    and self.main_loop_manager
                    and not isinstance(grounded_rules[i].rule, _QuestionRule)
                ):
                    self.main_loop_manager.update_normal_rule_activation(
                        new_facts=added_facts,
                        used_rule=grounded_rules[i].rule,
                    )

                solutions: list[Mapping[Variable, Constant | CompoundTerm]] = []
                question_rule: _QuestionRule | None = None
                if isinstance(grounded_rules[i].rule, _QuestionRule):
                    solutions, question_rule = grounded_rules[i].get_question_solutions()

                if mode == RunMode.CONFLICT_CHECK:
                    if added_facts:
                        return InferenceStatus.CONFLICT_DETECTED
                    continue

                # 检查执行器状态
                status = self.executor_manager.check_status(
                    new_facts=added_facts,
                    question=question,
                    solutions=solutions,
                    question_rule=question_rule,
                )
                logger.info("Step %d status: %s", self.executor_manager.step_num, status.log_message())
                if not isinstance(grounded_rules[i].rule, _QuestionRule):
                    self.executor_manager.next_step()

                if status.is_terminal_for_executor():
                    return status

        except SystemExit:
            logger.exception("Execution interrupted by SystemExit")
            return InferenceStatus.EXTERNALLY_INTERRUPTED

        return InferenceStatus.NO_MORE_RULES

    def _update_facts(
        self,
        new_facts: list[FACT_TYPE],
    ) -> list[FACT_TYPE]:
        """Update fact storage according to execution mode."""
        if self.mode == RunMode.CONFLICT_CHECK:
            if not new_facts:
                return []
            # Conflict checking stops at the first detected evidence fact,
            # so only the first item needs to be stored for reporting.
            self._conflict_probe_buffer = new_facts[0]
            return [self._conflict_probe_buffer]

        added_facts = self.fact_base.add_facts(facts=new_facts)
        self.sk_system_handler.update_facts(new_facts)
        return added_facts

    def reset(self) -> None:
        """Reset internal state for a new question."""
        self.mode = RunMode.INFERENCE
        self.executor_manager.reset_for_new_inference()
        self._conflict_probe_buffer = None
