from typing import Dict, List, Optional, Tuple

from analogai.foundation.modules.belief.belief import Belief
from analogai.foundation.modules.notion.notion import Notion
from analogai.foundation.modules.notion.notion_service import NotionService
from analogai.foundation.modules.shared.statement_base import StatementBase
from analogai.foundation.modules.direct_statement.statement_service_base import StatementServiceBase
from analogai.foundation.core.value_objects.modifier import Modifier
from analogai.foundation.core.value_objects.relation import Relation
from analogai.foundation.core.value_objects.time import Time
from analogai.foundation.core.value_objects.attribute import Attribute
from analogai.foundation.common.dtos.notion_expression import NotionExpression
from analogai.foundation.common.logging.app_logger import app_logger
from analogai.foundation.common.utils.modifier_utils.modifier_matcher import ModifierMatcher


BeliefKey = Tuple[str, str, Relation, str, Optional[Tuple]]


class BeliefFilterer:
    def __init__(
        self,
        statement_service: StatementServiceBase,
        notion_service: NotionService,
        belief_assembler,
        modifier_matcher: Optional[ModifierMatcher] = None,
    ):
        self._statement_service = statement_service
        self._notion_service = notion_service
        self._belief_assembler = belief_assembler
        self._matcher = modifier_matcher or ModifierMatcher()

    def apply_quantity_modifier(self, modifier: Optional[Modifier], quantity: Optional[float]) -> Optional[Modifier]:
        if quantity is None:
            return modifier
        if modifier is None:
            return Modifier(quantity=quantity)
        return Modifier(
            location=modifier.location,
            deontic_modality=modifier.deontic_modality,
            from_time=modifier.from_time,
            to_time=modifier.to_time,
            attributes=list(modifier.attributes) if modifier.attributes else None,
            quantity=quantity,
        )

    def _time_key(self, time: Optional[Time]) -> Optional[Tuple[Optional[int], Optional[int], Optional[int], Optional[int], Optional[int]]]:
        if time is None:
            return None
        return (time.year, time.month, time.day, time.hour, time.minute)

    def _attributes_key(self, attributes: Optional[List[Attribute]]) -> Optional[Tuple[Tuple[Optional[str], Optional[float], Optional[str]], ...]]:
        if attributes is None:
            return None
        return tuple((attribute.tag, attribute.value, attribute.unit) for attribute in attributes)

    def _modifier_has_context(self, modifier: Optional[Modifier]) -> bool:
        if modifier is None:
            return False
        if modifier.location is not None:
            return True
        if modifier.from_time is not None:
            return True
        if modifier.to_time is not None:
            return True
        if modifier.deontic_modality is not None:
            return True
        if modifier.attributes:
            return True
        if modifier.quantity is not None:
            return True
        return False

    def _modifier_key(self, modifier: Optional[Modifier]) -> Optional[Tuple]:
        if not self._modifier_has_context(modifier):
            return None
        assert modifier is not None
        deontic = modifier.deontic_modality.value if modifier.deontic_modality is not None else None
        return (
            modifier.location,
            deontic,
            self._time_key(modifier.from_time),
            self._time_key(modifier.to_time),
            self._attributes_key(modifier.attributes),
            modifier.quantity,
        )

    def belief_key_from_values(
        self,
        agent_id: str,
        subject_id: str,
        object_id: str,
        relation: Relation,
        modifier: Optional[Modifier],
    ) -> BeliefKey:
        if not isinstance(relation, Relation):
            raise TypeError("relation must be a Relation instance")
        return (
            agent_id,
            subject_id,
            relation,
            object_id,
            self._modifier_key(modifier),
        )

    def belief_key_from_statement(self, statement: StatementBase) -> BeliefKey:
        return self.belief_key_from_values(
            agent_id=statement.agent_id,
            subject_id=statement.subject_notion.id,
            object_id=statement.complement_notion.id,
            relation=statement.relation,
            modifier=statement.modifier,
        )

    def _modifier_matches(self, stored: Optional[Modifier], query: Optional[Modifier]) -> bool:
        stored_normalized = stored if self._modifier_has_context(stored) else None
        query_normalized = query if self._modifier_has_context(query) else None
        return self._matcher.context_matches(stored_normalized, query_normalized)

    def _modifier_exact_matches(self, stored: Optional[Modifier], query: Optional[Modifier]) -> bool:
        stored_normalized = stored if self._modifier_has_context(stored) else None
        query_normalized = query if self._modifier_has_context(query) else None
        return self._matcher.context_exact_matches(stored_normalized, query_normalized)

    def base_beliefs_for_agent(
        self,
        agent_id: str,
        modifier: Optional[Modifier],
        quantity: Optional[float],
    ) -> Dict[BeliefKey, Belief]:
        modifier = self.apply_quantity_modifier(modifier, quantity)
        statements = self._statement_service.find_by_agent_id(agent_id)
        if modifier is None:
            filtered = list(statements)
        else:
            filtered = [
                stmt for stmt in statements
                if self._modifier_exact_matches(stmt.modifier, modifier)
            ]
        groups = self._belief_assembler.group_statements(filtered, self.belief_key_from_statement)
        return {
            belief_key: self._belief_assembler.build_belief(group)
            for belief_key, group in groups.items()
        }

    def search_by_notions(
        self,
        subject_notion: Notion,
        complement_notion: Notion,
        modifier: Optional[Modifier] = None,
    ) -> List[Belief]:
        statements = self._statement_service.find_by_subject(subject_notion)
        filtered = [
            stmt for stmt in statements
            if stmt.complement_notion.id == complement_notion.id
            and self._modifier_matches(stmt.modifier, modifier)
        ]
        groups = self._belief_assembler.group_statements(filtered, self.belief_key_from_statement)
        beliefs = [self._belief_assembler.build_belief(group) for group in groups.values()]
        beliefs.sort(key=lambda belief: (belief.updated_at, belief.id), reverse=True)
        app_logger.debug(
            "Found %s beliefs - subject='%s', object='%s'",
            len(beliefs),
            subject_notion.caption,
            complement_notion.caption,
        )
        return beliefs

    def search_by_subject_notion(
        self,
        subject_notion: Notion,
        modifier: Optional[Modifier] = None,
    ) -> List[Belief]:
        statements = self._statement_service.find_by_subject(subject_notion)
        filtered = [stmt for stmt in statements if self._modifier_matches(stmt.modifier, modifier)]
        groups = self._belief_assembler.group_statements(filtered, self.belief_key_from_statement)
        beliefs = [self._belief_assembler.build_belief(group) for group in groups.values()]
        beliefs.sort(key=lambda belief: (belief.updated_at, belief.id), reverse=True)
        app_logger.debug("Found %s beliefs - subject='%s'", len(beliefs), subject_notion.caption)
        return beliefs

    def search_by_complement_notion(
        self,
        complement_notion: Notion,
        modifier: Optional[Modifier] = None,
    ) -> List[Belief]:
        statements = self._statement_service.find_by_object(complement_notion)
        filtered = [stmt for stmt in statements if self._modifier_matches(stmt.modifier, modifier)]
        groups = self._belief_assembler.group_statements(filtered, self.belief_key_from_statement)
        beliefs = [self._belief_assembler.build_belief(group) for group in groups.values()]
        beliefs.sort(key=lambda belief: (belief.updated_at, belief.id), reverse=True)
        app_logger.debug("Found %s beliefs - object='%s'", len(beliefs), complement_notion.caption)
        return beliefs

    def search_by_subject_notion_and_relation(
        self,
        subject_notion: Notion,
        relation: Relation,
        modifier: Optional[Modifier] = None,
    ) -> List[Belief]:
        beliefs = self.search_by_subject_notion(subject_notion=subject_notion, modifier=modifier)
        return [b for b in beliefs if b.relation == relation]

    def search_by_complement_notion_and_relation(
        self,
        complement_notion: Notion,
        relation: Relation,
        modifier: Optional[Modifier] = None,
    ) -> List[Belief]:
        beliefs = self.search_by_complement_notion(complement_notion=complement_notion, modifier=modifier)
        return [b for b in beliefs if b.relation == relation]

    def search_by_subject_notion_expression(
        self,
        agent_id: str,
        subject_notion_expression: NotionExpression,
        modifier: Optional[Modifier] = None,
    ) -> Optional[List[Belief]]:
        subject_notion = self._notion_service.find(agent_id, subject_notion_expression)
        if not subject_notion:
            return None
        return self.search_by_subject_notion(subject_notion=subject_notion, modifier=modifier)

    def search_by_complement_notion_expression(
        self,
        agent_id: str,
        complement_notion_expression: NotionExpression,
        modifier: Optional[Modifier] = None,
    ) -> Optional[List[Belief]]:
        complement_notion = self._notion_service.find(agent_id, complement_notion_expression)
        if not complement_notion:
            return None
        return self.search_by_complement_notion(complement_notion=complement_notion, modifier=modifier)

    def search_by_notion_captions(
        self,
        agent_id: str,
        captions: List[str],
        modifier: Optional[Modifier] = None,
        max_depth: int = 1,
    ) -> List[Belief]:
        statements: List[StatementBase] = []
        finder = getattr(self._statement_service, "find_by_captions", None)
        if callable(finder):
            statements = finder(agent_id, captions, max_depth=max_depth) or []
        else:
            captions_clean = [caption for caption in captions if caption and caption.strip()]
            seen_ids = set()
            for caption in captions_clean:
                expression = NotionExpression(caption=caption)
                notion = self._notion_service.find(agent_id, expression)
                if notion is None:
                    continue
                for stmt in self._statement_service.find_by_subject(notion) or []:
                    if stmt.id in seen_ids:
                        continue
                    seen_ids.add(stmt.id)
                    statements.append(stmt)
                for stmt in self._statement_service.find_by_object(notion) or []:
                    if stmt.id in seen_ids:
                        continue
                    seen_ids.add(stmt.id)
                    statements.append(stmt)

        if modifier is None:
            filtered = list(statements)
        else:
            filtered = [stmt for stmt in statements if self._modifier_matches(stmt.modifier, modifier)]
        groups = self._belief_assembler.group_statements(filtered, self.belief_key_from_statement)
        beliefs = [self._belief_assembler.build_belief(group) for group in groups.values()]
        beliefs.sort(key=lambda belief: (belief.updated_at, belief.id), reverse=True)
        app_logger.debug(
            "Found %s beliefs - captions=%s depth=%s",
            len(beliefs),
            [caption for caption in captions if caption],
            max_depth,
        )
        return beliefs
