from __future__ import annotations

from collections import defaultdict
import logging
from dataclasses import dataclass
from typing import TYPE_CHECKING

from pydantic import BaseModel, ConfigDict

from kele.knowledge_bases.rule_utils import RulePreprocessor
from kele.syntax.base_classes import Assertion, Constant

if TYPE_CHECKING:
    from collections.abc import Sequence

    from kele.syntax import FACT_TYPE, ConflictRule, Rule

logger = logging.getLogger(__name__)


class ConflictReason(BaseModel):
    """Structured description of why inference terminated on conflict."""

    model_config = ConfigDict(extra="forbid")

    rule_name: str
    rule_body: str
    evidence: list[str]


@dataclass
class _ConflictEntry:
    violation_symbol: str
    rule_name: str
    rule_body: str
    compiled_rule: Rule


class ConflictEvaluator:
    """Compile conflict rules and match conflict evidence.

    Conflict probing is scheduled by engine mode flow; this evaluator only
    keeps conflict-rule compilation and evidence matching responsibilities.
    """

    def __init__(
        self,
        *,
        conflict_rules: Sequence[ConflictRule],
    ) -> None:
        self._entries: list[_ConflictEntry] = []

        preprocessor = RulePreprocessor()
        for source_rule in conflict_rules:
            compiled_units = preprocessor.preprocess_rule(source_rule.to_violation_rule())
            for compiled_rule in compiled_units:
                self._entries.append(
                    _ConflictEntry(
                        violation_symbol=source_rule.violation_symbol,
                        rule_name=source_rule.name or compiled_rule.name,
                        rule_body=str(source_rule.body),
                        compiled_rule=compiled_rule,
                    ),
                )

    @property
    def compiled_conflict_rules(self) -> list[Rule]:
        """Return compiled internal conflict rules used by conflict-check mode."""
        return [entry.compiled_rule for entry in self._entries]

    def has_rules(self) -> bool:
        """Return whether any compiled conflict rule is registered."""
        return bool(self._entries)

    def check(
        self,
        *,
        evidence_facts: Sequence[FACT_TYPE],
    ) -> ConflictReason | None:
        """Match provided evidence facts against compiled conflict rules."""
        if not self._entries:
            return None

        if not evidence_facts:
            return None

        logger.info("[conflict] check started with %d rules", len(self._entries))
        symbols = {entry.violation_symbol for entry in self._entries}
        matched_by_symbol: dict[str, list[str]] = defaultdict(list)
        for fact in evidence_facts:
            if not isinstance(fact, Assertion):
                continue
            if not isinstance(fact.lhs, Constant):
                continue
            symbol = str(fact.lhs)
            if symbol not in symbols:
                continue
            matched_by_symbol[symbol].append(str(fact))

        for entry in self._entries:
            matched_evidence = matched_by_symbol.get(entry.violation_symbol, [])
            if not matched_evidence:
                continue

            reason = ConflictReason(
                rule_name=entry.rule_name,
                rule_body=entry.rule_body,
                evidence=matched_evidence,
            )
            logger.warning(
                "[conflict] conflict detected by rule '%s' with evidence: %s",
                reason.rule_name,
                ", ".join(reason.evidence),
            )
            return reason

        return None

    @staticmethod
    def reset() -> None:
        """Reset evaluator state for a new inference run."""
