from typing import Any, Iterable, Optional, List, Protocol, cast

from analogai.foundation.common.enums.certainty import Certainty
from analogai.deepthink.presentation.parsers.parsed_sentence import ParsedSentence, ParsedData, AttributeDict


_SIMPLE_STRING_FIELDS = [
    ("location", ["location"]),
    ("to_time", ["to_time", "time_to"]),
    ("deontic_modality", ["deontic_modality", "deontic"]),
    ("quantity", ["quantity"]),
]


_FLOAT_FIELDS = []


_LIST_FIELDS = [
    ("attributes", ["attributes"]),
    ("causes", ["causes"]),
    ("effects", ["effects"]),
]


class ListFieldHandler(Protocol):
    def parse(self, value: list) -> Any:
        ...


def _as_string(value: Any) -> Optional[str]:
    return str(value).strip() if value else None


def _as_list(value: Any) -> Optional[list]:
    return value if isinstance(value, list) else None


def _as_dict(value: Any) -> Optional[dict]:
    return value if isinstance(value, dict) else None


def _parse_notion_value(raw: Any) -> tuple[Optional[str], Optional[List[AttributeDict]]]:
    if isinstance(raw, dict):
        caption = (
            raw.get("caption")
            or raw.get("subject")
            or raw.get("name")
            or raw.get("value")
        )
        attributes = _ensure_attribute_dicts(raw.get("attributes"))
        return _as_string(caption), attributes
    return _as_string(raw), None




def _parse_float(value: Any) -> Optional[float]:
    if value is None:
        return None
    try:
        return float(str(value).replace(",", "."))
    except (ValueError, TypeError):
        return None


def _parse_nested_data_list(value: list) -> list[ParsedData]:
    nested_data_list = []
    for nested_item in value:
        nested_payload = _as_dict(nested_item)
        if nested_payload is not None:
            nested_data_list.append(_parse_payload_data(nested_payload))
    return nested_data_list


def _parse_attributes_list(value: list) -> list:
    return value


_LIST_FIELD_HANDLERS: dict[str, ListFieldHandler] = {
    "causes": _parse_nested_data_list,
    "effects": _parse_nested_data_list,
    "attributes": _parse_attributes_list,
}


def parse_sentence_payload(payload: dict) -> Optional[ParsedSentence]:
    if not payload:
        return None

    intent_val = payload.get("intent_type") or payload.get("intent")
    intent_type = _as_string(intent_val)

    if not intent_type:
        return None

    parsed_data = _parse_payload_data(payload)

    return ParsedSentence(
        intent_type=intent_type,
        data=parsed_data,
    )


def _parse_payload_data(payload: dict) -> ParsedData:
    return ParsedDataBuilder().build(payload)


def _ensure_attribute_dicts(value: Any) -> Optional[List[AttributeDict]]:
    if not isinstance(value, list):
        return None
    cleaned: List[AttributeDict] = []
    for item in value:
        item_dict = _as_dict(item)
        if item_dict is not None:
            cleaned.append(cast(AttributeDict, item_dict))
    return cleaned if cleaned else None


def parse_sentences(data: dict) -> List[ParsedSentence]:
    sentences = []
    sentence_payloads = _as_list(data.get("sentences"))
    if sentence_payloads:
        for sentence_payload in sentence_payloads:
            payload_dict = _as_dict(sentence_payload)
            if payload_dict is None:
                continue
            parsed_sentence = parse_sentence_payload(payload_dict)
            if parsed_sentence:
                sentences.append(parsed_sentence)
    elif "intent" in data or "intent_type" in data:
        parsed_sentence = parse_sentence_payload(data)
        if parsed_sentence:
            sentences.append(parsed_sentence)

    return sentences


class PayloadAccessor:
    def as_list(self, value: Any) -> Optional[list]:
        return value if isinstance(value, list) else None

    def as_dict(self, value: Any) -> Optional[dict]:
        return value if isinstance(value, dict) else None

    def get_first(self, payload: dict, keys: Iterable[str]) -> Any:
        for key in keys:
            value = payload.get(key)
            if value:
                return value
        return None


class SimpleFieldParser:
    def __init__(self, accessor: PayloadAccessor) -> None:
        self._accessor = accessor

    def parse(self, payload: dict, target: ParsedData) -> None:
        for field_name, json_keys in _SIMPLE_STRING_FIELDS:
            value = self._accessor.get_first(payload, json_keys)
            if value is not None:
                setattr(target, field_name, _as_string(value))


class FloatFieldParser:
    def __init__(self, accessor: PayloadAccessor) -> None:
        self._accessor = accessor

    def parse(self, payload: dict, target: ParsedData) -> None:
        for field_name, json_keys in _FLOAT_FIELDS:
            for key in json_keys:
                value = payload.get(key)
                if value is not None:
                    setattr(target, field_name, _parse_float(value))
                    break


class ListFieldParser:
    def __init__(self, accessor: PayloadAccessor) -> None:
        self._accessor = accessor

    def parse(self, payload: dict, target: ParsedData) -> None:
        for field_name, json_keys in _LIST_FIELDS:
            for key in json_keys:
                list_val = self._accessor.as_list(payload.get(key))
                if list_val:
                    handler = _LIST_FIELD_HANDLERS.get(field_name)
                    if handler is not None:
                        setattr(target, field_name, handler(list_val))
                    break


class NotionValueParser:
    def __init__(self, accessor: PayloadAccessor) -> None:
        self._accessor = accessor

    def parse(self, raw: Any) -> tuple[Optional[str], Optional[List[AttributeDict]]]:
        return _parse_notion_value(raw)


class PredicateParser:
    def __init__(self, accessor: PayloadAccessor) -> None:
        self._accessor = accessor

    def resolve(self, payload: dict) -> tuple[Optional[str], Optional[str]]:
        predicate = self._accessor.as_dict(payload.get("predicate")) or {}
        complement = predicate.get("complement_caption") or predicate.get("complement")
        if not complement:
            complement = self._accessor.get_first(
                payload,
                ("object", "complement", "complement_caption"),
            )
        relation = predicate.get("relation")
        if not relation:
            relation = self._accessor.get_first(
                payload,
                ("relationship", "relationship_phrase", "relation", "relation_name"),
            )
        return complement, relation


class AttributeApplier:
    def apply(self, parsed_data: ParsedData, payload: dict) -> None:
        if parsed_data.subject_attributes is None and isinstance(parsed_data.attributes, list):
            parsed_data.subject_attributes = parsed_data.attributes

        payload_attributes = _ensure_attribute_dicts(payload.get("attributes"))
        if parsed_data.attributes is None and payload_attributes is not None:
            parsed_data.attributes = payload_attributes
            if parsed_data.subject_attributes is None:
                parsed_data.subject_attributes = parsed_data.attributes

        tag_value = payload.get("tag")
        if tag_value and not parsed_data.attributes and not parsed_data.subject_attributes:
            parsed_data.attributes = [{"tag": str(tag_value)}]
            parsed_data.subject_attributes = parsed_data.attributes


class CertaintyParser:
    def apply(self, parsed_data: ParsedData, payload: dict) -> None:
        certainty_val = payload.get("certainty")
        if certainty_val is not None:
            certainty_str = _as_string(certainty_val)
            if certainty_str:
                try:
                    parsed_data.certainty = Certainty(certainty_str.lower())
                except ValueError:
                    parsed_data.certainty = None
        if payload.get("not"):
            parsed_data.certainty = Certainty.NEGATED
        elif payload.get("might"):
            parsed_data.certainty = Certainty.UNCERTAIN


class TimeRangeParser:
    def apply(self, parsed_data: ParsedData, payload: dict) -> None:
        time_val = payload.get("time")
        time_from_val = payload.get("from_time") or payload.get("time_from")

        if time_val and time_from_val:
            time_str = _as_string(time_val)
            time_from_str = _as_string(time_from_val)
            if time_str and time_str.lower() in ("today", "tomorrow", "yesterday"):
                if time_from_str and ":" in time_from_str:
                    parsed_data.from_time = f"{time_str.lower()} {time_from_str}"
                else:
                    parsed_data.from_time = time_from_str
                if parsed_data.to_time and ":" in parsed_data.to_time:
                    parsed_data.to_time = f"{time_str.lower()} {parsed_data.to_time}"
            else:
                parsed_data.from_time = time_from_str
            parsed_data.time = time_str
        elif time_val:
            time_str = _as_string(time_val)
            if time_str:
                time_lower = time_str.lower()
                if time_lower.startswith("until ") or time_lower.startswith("till "):
                    parsed_data.to_time = time_str.split(" ", 1)[1].strip()
                    parsed_data.from_time = None
                elif time_lower.startswith("after "):
                    parsed_data.from_time = time_str.split(" ", 1)[1].strip()
                else:
                    parsed_data.from_time = time_str
            parsed_data.time = time_str
        elif time_from_val:
            parsed_data.from_time = _as_string(time_from_val)


class ParsedDataBuilder:
    def __init__(self) -> None:
        self._accessor = PayloadAccessor()
        self._simple_fields = SimpleFieldParser(self._accessor)
        self._float_fields = FloatFieldParser(self._accessor)
        self._list_fields = ListFieldParser(self._accessor)
        self._notion_parser = NotionValueParser(self._accessor)
        self._predicate_parser = PredicateParser(self._accessor)
        self._attribute_applier = AttributeApplier()
        self._certainty_parser = CertaintyParser()
        self._time_parser = TimeRangeParser()

    def build(self, payload: dict) -> ParsedData:
        parsed_data = ParsedData()
        self._simple_fields.parse(payload, parsed_data)
        self._float_fields.parse(payload, parsed_data)
        self._list_fields.parse(payload, parsed_data)

        subject_raw = self._accessor.get_first(payload, ("subject_caption", "subject"))
        subject_caption, subject_attributes = self._notion_parser.parse(subject_raw)
        parsed_data.subject = subject_caption
        parsed_data.subject_attributes = subject_attributes

        predicate_complement, predicate_relation = self._predicate_parser.resolve(payload)
        complement_caption, complement_attributes = self._notion_parser.parse(predicate_complement)
        parsed_data.complement = complement_caption
        parsed_data.complement_attributes = complement_attributes
        parsed_data.relationship_phrase = _as_string(predicate_relation)

        prob_val = payload.get("probability")
        if prob_val is not None:
            parsed_data.probability = _parse_float(prob_val)

        self._attribute_applier.apply(parsed_data, payload)
        self._certainty_parser.apply(parsed_data, payload)
        self._time_parser.apply(parsed_data, payload)
        return parsed_data
