from typing import Optional

from analogai.foundation.common.utils.expression_factory import ExpressionFactory
from analogai.foundation.core.value_objects.attribute import Attribute as CoreAttribute
from analogai.foundation.core.value_objects.relation import Relation

from analogai.deepthink.presentation.intent_type import IntentType
from analogai.deepthink.presentation.parsers.parsed_sentence import ParsedSentence, ParsedData, AttributeDict
from analogai.deepthink.presentation.parsers.propagated_sentence import PropagatedSentence, PropagatedData, Attribute
from analogai.deepthink.presentation.parsers.utils.attribute_tag_utils import attribute_tag_from_tag
from analogai.deepthink.presentation.parsers.utils.intent_mapper import map_intent
from analogai.deepthink.presentation.parsers.utils.relationship_type_normalizer import tag_key_to_relationship_type


def build_propagated_sentence(parsed: ParsedSentence) -> PropagatedSentence:
    intent_type = map_intent(parsed.intent_type)
    if not intent_type:
        intent_type = IntentType.MAKING_STATEMENT

    if not parsed.data:
        parsed.data = ParsedData()

    propagated_data = _build_propagated_data(parsed.data)

    return PropagatedSentence(
        intent_type=intent_type,
        data=propagated_data,
    )


def _build_propagated_data(parsed_data: ParsedData) -> PropagatedData:
    relation = _resolve_relation(parsed_data.relationship_phrase)

    attributes = _build_attributes_from_dicts(_collect_attribute_dicts(parsed_data))

    expression_factory = ExpressionFactory()
    subject_attributes = parsed_data.subject_attributes or parsed_data.attributes
    complement_attributes = parsed_data.complement_attributes
    subject_core_attributes = _build_core_attributes(subject_attributes)
    complement_core_attributes = _build_core_attributes(complement_attributes)
    subject = (
        expression_factory.create_notion_expression(parsed_data.subject, attributes=subject_core_attributes)
        if parsed_data.subject else None
    )
    complement_expr = (
        expression_factory.create_notion_expression(parsed_data.complement, attributes=complement_core_attributes)
        if parsed_data.complement else None
    )

    quantity = _parse_quantity(parsed_data.quantity)

    causes = []
    if parsed_data.causes:
        for cause_data in parsed_data.causes:
            causes.append(_build_propagated_data(cause_data))

    effects = []
    if parsed_data.effects:
        for effect_data in parsed_data.effects:
            effects.append(_build_propagated_data(effect_data))

    return PropagatedData(
        subject=subject,
        complement=complement_expr,
        location=parsed_data.location,
        relation=relation,
        attributes=attributes,
        from_time=parsed_data.from_time,
        to_time=parsed_data.to_time,
        time=parsed_data.time,
        quantity=quantity,
        probability=parsed_data.probability,
        certainty=parsed_data.certainty,
        causes=causes,
        effects=effects,
        deontic_modality=parsed_data.deontic_modality,
    )


def _resolve_relation(relationship_phrase: Optional[str]) -> Optional[Relation]:
    if not relationship_phrase:
        return None
    rel_type = tag_key_to_relationship_type(relationship_phrase)
    if rel_type is not None:
        return Relation.from_type(rel_type)
    return Relation.from_string(relationship_phrase)


def _collect_attribute_dicts(parsed_data: ParsedData) -> list[AttributeDict]:
    merged: list[AttributeDict] = []
    sources = [parsed_data.attributes, parsed_data.subject_attributes, parsed_data.complement_attributes]
    seen_sources = set()
    for source in sources:
        if not source or not isinstance(source, list):
            continue
        source_id = id(source)
        if source_id in seen_sources:
            continue
        seen_sources.add(source_id)
        for attr_dict in source:
            if isinstance(attr_dict, dict):
                merged.append(attr_dict)
    return merged


def _build_attributes_from_dicts(attribute_dicts: list[AttributeDict]) -> list[Attribute]:
    attributes = []

    for attr_dict in attribute_dicts:
        if not isinstance(attr_dict, dict):
            continue

        tag_str = attr_dict.get("tag") or attr_dict.get("kind")
        value_str = attr_dict.get("value")
        unit_str = _normalize_unit(attr_dict.get("unit"))

        if not tag_str:
            continue

        tag = attribute_tag_from_tag(str(tag_str))
        if not tag:
            continue

        values = []
        if value_str is not None:
            try:
                value_float = float(value_str)
                values.append(value_float)
            except (ValueError, TypeError):
                pass

        if values or unit_str:
            attributes.append(Attribute(
                tag=tag,
                values=values,
                unit=unit_str,
            ))

    return attributes


def _build_core_attributes(attribute_dicts: Optional[list[AttributeDict]]) -> list[CoreAttribute]:
    attributes: list[CoreAttribute] = []
    if not attribute_dicts or not isinstance(attribute_dicts, list):
        return attributes
    for attr_dict in attribute_dicts:
        if not isinstance(attr_dict, dict):
            continue
        tag_str = attr_dict.get("tag") or attr_dict.get("kind")
        value = attr_dict.get("value")
        unit_str = _normalize_unit(attr_dict.get("unit"))
        if not tag_str:
            continue
        tag = attribute_tag_from_tag(str(tag_str))
        if not tag:
            continue
        if value is not None:
            try:
                value_float = float(value)
                value = value_float
            except (ValueError, TypeError):
                pass
        attributes.append(CoreAttribute(tag=str(tag), value=value, unit=unit_str))
    return attributes


def _normalize_unit(unit: object) -> Optional[str]:
    if unit is None:
        return None
    unit_str = str(unit).strip()
    if not unit_str:
        return None
    return unit_str.upper() if unit_str.isalpha() else unit_str


def _parse_quantity(quantity) -> Optional[float]:
    if quantity is None:
        return None
    try:
        return float(quantity)
    except (ValueError, TypeError):
        return None
