from .utils.belief_certainties_calculator_util import calculate_belief_certainties
from typing import Optional
import uuid
from datetime import datetime
from analogai.foundation.modules.shared.statement_base import StatementBase
from analogai.foundation.core.value_objects.modifier import Modifier
from analogai.foundation.common.logging.app_logger import app_logger
from analogai.foundation.common.utils.entity_formatters import (
    format_belief_str,
    format_belief_repr,
)


class Belief:
    def __init__(
        self,
        agent_id: str,
        id: Optional[str] = None,
        created_at: Optional[datetime] = None,
        updated_at: Optional[datetime] = None,
    ):
        self.id = id if id else str(uuid.uuid4())
        self.agent_id = agent_id
        self._statements = []
        self._modifier = None
        self._probability = None
        self._validity = None
        if created_at is None:
            created_at = datetime.now()
        if updated_at is None:
            updated_at = created_at
        self.created_at = created_at
        self.updated_at = updated_at

    @property
    def subject_notion(self):
        if not self._statements:
            raise ValueError("Belief has no statements")
        return self._statements[0].subject_notion

    @property
    def complement_notion(self):
        if not self._statements:
            raise ValueError("Belief has no statements")
        return self._statements[0].complement_notion

    @property
    def relation(self):
        if not self._statements:
            raise ValueError("Belief has no statements")
        return self._statements[0].relation
    
    
    def add_statement(self, statement: StatementBase):
        if any(s.id == statement.id for s in self._statements if s.id is not None):
            app_logger.debug(f"Skipping duplicate statement in belief (statement_id: {statement.id}, belief_id: {self.id})")
            return
        
        statement_type = type(statement).__name__
        app_logger.debug(f"Adding statement to belief: type={statement_type} (statement_id: {statement.id}, belief_id: {self.id})")
        
        if not self._statements:
            self._modifier = statement.modifier
        
        self._statements.append(statement)
        self._recompute_certainties()
        self.updated_at = datetime.now()
        app_logger.debug(f"Added statement to belief, total statements: {len(self._statements)} (statement_id: {statement.id}, belief_id: {self.id})")

    @property
    def statements(self):
        return list(self._statements)
    
    @property
    def unfiltered_statements(self):
        return list(self._statements)
    
    def _calculate_certainties(self, statements) -> list:
        if not statements:
            raise ValueError("No statements to calculate certainties from")
        return [(s.validity, s.probability) for s in statements]

    def _recompute_certainties(self) -> None:
        if not self._statements:
            self._probability = None
            self._validity = None
            return
        certainties = self._calculate_certainties(self.statements)
        probability, validity = calculate_belief_certainties(certainties)
        self._probability = probability
        self._validity = validity

    @property
    def probability(self) -> float:
        if self._probability is None:
            self._recompute_certainties()
        if self._probability is None:
            raise ValueError("Trying to calculate probability for belief without statements")
        return self._probability

    @property
    def validity(self) -> float:
        if self._validity is None:
            self._recompute_certainties()
        if self._validity is None:
            raise ValueError("Trying to calculate validity for belief without statements")
        return self._validity

    @property
    def modifier(self) -> Optional[Modifier]:
        return self._modifier
    
    @property
    def location(self) -> Optional[str]:
        return self._modifier.location if self._modifier else None

    @property
    def from_time(self):
        return self._modifier.from_time if self._modifier else None

    @property
    def to_time(self):
        return self._modifier.to_time if self._modifier else None

    @property
    def deontic_modality(self):
        return self._modifier.deontic_modality if self._modifier else None

    @property
    def quantity(self) -> Optional[float]:
        return self._modifier.quantity if self._modifier else None

    def __str__(self) -> str:
        return format_belief_str(self)

    def __repr__(self) -> str:
        return format_belief_repr(self)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Belief):
            return False
        return self.id == other.id

    def __hash__(self) -> int:
        return hash(self.id)