from __future__ import annotations

from typing import Protocol, Optional, List, Dict, cast

from analogai.foundation.common.utils.statement_serializers import serialize_statement
from analogai.foundation.common.utils.repr_utils import format_statement_repr as format_statement_repr_util
from analogai.foundation.core.value_objects.modifier import Modifier
from analogai.foundation.core.value_objects.relation import Relation
from analogai.foundation.core.value_objects.attribute import Attribute
from analogai.foundation.common.enums.relationship_type import RelationshipType


class NotionLike(Protocol):
    id: str
    user_id: str
    agent_id: str
    caption: str
    attributes: Optional[List[Attribute]]


class StatementLike(Protocol):
    subject_notion: NotionLike
    complement_notion: NotionLike
    relation: Relation | RelationshipType | str
    modifier: Optional[Modifier]
    probability: float


class BeliefLike(Protocol):
    id: str
    statements: List[object]
    subject_notion: NotionLike
    complement_notion: NotionLike
    relation: Relation | RelationshipType | str
    modifier: Optional[Modifier]
    probability: float


class AttributeValueFormatter(Protocol):
    def format(self, value: object) -> str:
        ...


class ScalarValueFormatter:
    def format(self, value: object) -> str:
        return str(value)


class ListValueFormatter:
    def format(self, value: object) -> str:
        items = cast(List[object], value)
        return ", ".join(str(item) for item in items)


class AttributeFormatter:
    def __init__(
        self,
        value_formatters: Optional[Dict[type, AttributeValueFormatter]] = None,
        default_formatter: Optional[AttributeValueFormatter] = None,
    ):
        self._value_formatters = value_formatters or {}
        self._default_formatter = default_formatter or ScalarValueFormatter()

    def format(self, attribute: Attribute) -> Optional[str]:
        if attribute.value is None:
            return attribute.tag or None

        formatter = self._value_formatters.get(type(attribute.value), self._default_formatter)
        value_str = formatter.format(attribute.value)
        unit_str = f" {attribute.unit}" if attribute.unit else ""
        if attribute.tag:
            return f"{attribute.tag}={value_str}{unit_str}"
        return f"{value_str}{unit_str}"


class NotionStringFormatter:
    def __init__(self, attribute_formatter: AttributeFormatter):
        self._attribute_formatter = attribute_formatter

    def format(self, notion: NotionLike) -> str:
        if not notion.attributes:
            return notion.caption

        parts: List[str] = []
        for attr in notion.attributes:
            formatted = self._attribute_formatter.format(attr)
            if formatted:
                parts.append(formatted)

        if not parts:
            return notion.caption
        return f"{notion.caption} ({', '.join(parts)})"


_DEFAULT_ATTRIBUTE_FORMATTER = AttributeFormatter(
    value_formatters={list: ListValueFormatter()},
    default_formatter=ScalarValueFormatter(),
)
_DEFAULT_NOTION_FORMATTER = NotionStringFormatter(_DEFAULT_ATTRIBUTE_FORMATTER)


def format_notion_str(notion: NotionLike) -> str:
    return _DEFAULT_NOTION_FORMATTER.format(notion)


def format_notion_repr(notion: NotionLike) -> str:
    return (
        "Notion(id={!r}, user_id={!r}, agent_id={!r}, caption={!r}, attributes={!r})"
    ).format(
        notion.id,
        notion.user_id,
        notion.agent_id,
        notion.caption,
        notion.attributes,
    )


def format_user_repr(user) -> str:
    return (
        "User(" f"id='{user.id}', " f"name='{user.name}', " f"email='{user.email}', " f"timezone='{user.timezone}'" ")"
    )


def format_statement_str(statement: StatementLike) -> str:
    return serialize_statement(
        subject_notion_caption=statement.subject_notion.caption,
        complement_notion_caption=statement.complement_notion.caption,
        relation=statement.relation,
        probability=float(statement.probability),
        modifier=statement.modifier,
        subject_attributes=statement.subject_notion.attributes,
        complement_attributes=statement.complement_notion.attributes,
    )


def format_statement_repr(statement: StatementLike) -> str:
    relation = (
        statement.relation.value
        if isinstance(statement.relation, RelationshipType)
        else statement.relation
    )
    return format_statement_repr_util(
        subject_caption=statement.subject_notion.caption,
        relation=relation,
        object_caption=statement.complement_notion.caption,
        modifier=statement.modifier,
    )


def format_direct_statement_repr(statement: StatementLike) -> str:
    return format_statement_repr(statement)


def format_belief_str(belief: BeliefLike) -> str:
    if not belief.statements:
        return f"Belief(id={belief.id})"
    return serialize_statement(
        subject_notion_caption=belief.subject_notion.caption,
        complement_notion_caption=belief.complement_notion.caption,
        relation=belief.relation,
        probability=float(belief.probability),
        modifier=belief.modifier,
        subject_attributes=belief.subject_notion.attributes,
        complement_attributes=belief.complement_notion.attributes,
    )


def format_belief_repr(belief: BeliefLike) -> str:
    if not belief.statements:
        return f"Belief(id={belief.id})"
    relation = (
        belief.relation.value
        if isinstance(belief.relation, RelationshipType)
        else belief.relation
    )
    return format_statement_repr_util(
        subject_caption=belief.subject_notion.caption,
        relation=relation,
        object_caption=belief.complement_notion.caption,
        modifier=belief.modifier,
    )


def format_categorical_deduction_repr(deduction) -> str:
    return (
        f"CategoricalDeduction(id={deduction.id!r}, agent_id={deduction.agent_id!r}, "
        f"major_premise={deduction.major_premise!r}, minor_premise={deduction.minor_premise!r}, "
        f"subject_notion={deduction.subject_notion!r}, complement_notion={deduction.complement_notion!r}, "
        f"relation={deduction.relation!r})"
    )


def format_conditional_deduction_repr(deduction) -> str:
    return (
        f"ConditionalDeduction(id={deduction.id!r}, agent_id={deduction.agent_id!r}, "
        f"hypothetical_statement_id={deduction.hypothetical_statement_id!r}, "
        f"subject_notion={deduction.subject_notion!r}, complement_notion={deduction.complement_notion!r}, "
        f"relation={deduction.relation!r})"
    )
