from typing import Any, Optional, List, 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"]),
]


def _as_string(value: Any) -> Optional[str]:
    return str(value).strip() if value 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


_RELATION_COMPLEMENT_BLOCKLIST = {
    "is",
    "are",
    "was",
    "were",
    "be",
    "has",
    "have",
    "had",
    "do",
    "does",
}


def _derive_complement_from_relation(relation: str) -> Optional[str]:
    if not relation:
        return None
    candidate = relation.strip()
    lower = candidate.lower()
    if lower in _RELATION_COMPLEMENT_BLOCKLIST:
        return None
    if lower.endswith("ies") and len(lower) > 3:
        return candidate[:-3] + "y"
    if lower.endswith("es") and len(lower) > 2:
        return candidate[:-2]
    if lower.endswith("s") and len(lower) > 1:
        return candidate[:-1]
    return candidate


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_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:
    parsed_data = ParsedData()

    for field_name, json_keys in _SIMPLE_STRING_FIELDS:
        for key in json_keys:
            val = payload.get(key)
            if val:
                setattr(parsed_data, field_name, _as_string(val))
                break

    for field_name, json_keys in _FLOAT_FIELDS:
        for key in json_keys:
            val = payload.get(key)
            if val is not None:
                setattr(parsed_data, field_name, _parse_float(val))
                break

    for field_name, json_keys in _LIST_FIELDS:
        for key in json_keys:
            val = payload.get(key)
            if val and isinstance(val, list):
                if field_name in ("causes", "effects"):
                    nested_data_list = []
                    for nested_dict in val:
                        if isinstance(nested_dict, dict):
                            nested_data_list.append(_parse_payload_data(nested_dict))
                    setattr(parsed_data, field_name, nested_data_list)
                else:
                    setattr(parsed_data, field_name, val)
                break

    subject_raw = payload.get("subject_caption") or payload.get("subject")
    subject_caption, subject_attributes = _parse_notion_value(subject_raw)
    parsed_data.subject = subject_caption
    parsed_data.subject_attributes = subject_attributes

    predicate = payload.get("predicate")
    if not isinstance(predicate, dict):
        predicate = {}
    predicate_complement = predicate.get("complement_caption") or predicate.get("complement")
    if not predicate_complement:
        predicate_complement = payload.get("object") or payload.get("complement") or payload.get("complement_caption")

    predicate_relation = predicate.get("relation")
    if not predicate_relation:
        predicate_relation = (
            payload.get("relationship")
            or payload.get("relationship_phrase")
            or payload.get("relation")
            or payload.get("relation_name")
        )

    if not predicate_complement and predicate_relation:
        derived_complement = _derive_complement_from_relation(str(predicate_relation))
        if derived_complement:
            predicate_complement = derived_complement
            predicate_relation = "do"

    complement_caption, complement_attributes = _parse_notion_value(predicate_complement)
    parsed_data.complement = complement_caption
    parsed_data.complement_attributes = complement_attributes
    parsed_data.relationship_phrase = _as_string(predicate_relation)

    if parsed_data.subject_attributes is None and isinstance(parsed_data.attributes, list):
        parsed_data.subject_attributes = parsed_data.attributes

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

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

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

    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

    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)

    return parsed_data


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


def parse_sentences(data: dict) -> List[ParsedSentence]:
    sentences = []

    if "sentences" in data:
        items = data["sentences"]
        if items:
            for item in items:
                parsed = parse_sentence_payload(item)
                if parsed:
                    sentences.append(parsed)
    elif "intent" in data or "intent_type" in data:
        parsed = parse_sentence_payload(data)
        if parsed:
            sentences.append(parsed)

    return sentences
