from __future__ import annotations

from dataclasses import dataclass, field
import re

from backend.action_intents import (
    BaseActionIntent,
    EMAIL_ADDRESS_RE,
    apply_reference,
    build_reference_spec,
    detect_keyword_action,
    extract_quoted_value,
    merge_base_intent_fields,
    score_action_intent,
)


_EMAIL_FORWARD_RE = re.compile(r"\b(?:forward|doorstuur\w*|doorstur\w*|stuur\s+door)\b", re.IGNORECASE)
_EMAIL_REPLY_RE = re.compile(r"\b(?:reply|respond|antwoord\w*|beantwoord\w*|reageer\w*|reply\s+to|reply\s+all)\b", re.IGNORECASE)
_EMAIL_ACTION_MARKERS_RE = re.compile(r"\b(?:email|e-mail|mail|gmail|outlook|bericht|message|messages|newsletter|nieuwsbrief)\b", re.IGNORECASE)
_EMAIL_CC_RE = re.compile(r"\b(?:cc|kopie(?:\s+aan)?|carbon copy)\s*[:\-]?\s*", re.IGNORECASE)
_EMAIL_CC_ADDR_RE = re.compile(r"[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}", re.IGNORECASE)
_DUTCH_HINT_RE = re.compile(
    r"\b(?:onderwerp|inhoud|tekst|stuur|verstuur|wijzig|verwijder|mail|bericht|aan|"
    r"antwoord|beantwoord|zoek|vind|toon|laat|nieuwe?|laatste|ongelezen|"
    r"bijlage|doorstuur|afspraak|herinnering|vandaag|morgen|gisteren|"
    r"schrijf|maak|voeg|hoi|hallo|geachte|groet(?:en|jes)?|bedankt|"
    r"dringend|belangrijk|alsjeblieft|svp|graag)\b",
    re.IGNORECASE,
)

_ACTION_TERMS = {
    "send": {"send", "stuur", "sturen", "verstuur", "versturen", "compose", "draft"},
    "reply": {"reply", "respond", "antwoord", "beantwoord", "reageer", "reply to", "reply all"},
    "forward": {"forward", "doorstuur", "doorsturen", "doorstur", "stuur door"},
    "update": {"update", "edit", "change", "wijzig", "bewerk", "aanpas"},
    "delete": {"delete", "remove", "verwijder", "trash", "prullenbak", "cancel"},
}


@dataclass
class EmailIntent(BaseActionIntent):
    provider: str = ""
    to: str = ""
    cc: str = ""
    subject: str = ""
    body: str = ""
    body_instruction: str = ""

    def to_dict(self) -> dict:
        return {
            "action": self.action,
            "provider": self.provider,
            "selection_query": self.selection_query,
            "reference_kind": self.reference_kind,
            "reference_ordinal": self.reference_ordinal,
            "reference_temporal_hint": self.reference_temporal_hint,
            "to": self.to,
            "cc": self.cc,
            "subject": self.subject,
            "body": self.body,
            "body_instruction": self.body_instruction,
            "confidence": self.confidence,
            "missing_fields": list(self.missing_fields),
            "used_llm_fallback": self.used_llm_fallback,
        }


def detect_email_action(text: str) -> str:
    action = detect_keyword_action(text, _ACTION_TERMS, required_marker=_EMAIL_ACTION_MARKERS_RE)
    return _normalize_email_action(action)


def parse_email_intent(text: str) -> EmailIntent:
    raw = str(text or "").strip()
    intent = EmailIntent(action=detect_email_action(raw))
    if not raw:
        return validate_email_intent(intent)

    payload = _extract_send_like_fields(raw)
    intent.provider = _extract_provider(raw)
    intent.to = payload["to"]
    intent.cc = payload["cc"]
    intent.subject = payload["subject"]
    intent.body = payload["body"]
    intent.body_instruction = payload["body_instruction"]

    if intent.action in {"update", "delete"} or _is_reply_or_forward_request(raw):
        apply_reference(intent, build_reference_spec(raw, _extract_selection_query(raw)))
    return validate_email_intent(intent)


def validate_email_intent(intent: EmailIntent) -> EmailIntent:
    payload = intent.to_dict()
    payload["action"] = _normalize_email_action(payload.get("action"))
    validated = EmailIntent(**payload)
    validated.missing_fields = []
    if validated.action == "send":
        if not validated.to:
            validated.missing_fields.append("to")
        if not validated.subject:
            validated.missing_fields.append("subject")
        if not validated.body:
            validated.missing_fields.append("body")
    elif validated.action == "update":
        if not validated.selection_query:
            validated.missing_fields.append("selection_query")
        if not any([validated.to, validated.cc, validated.subject, validated.body]):
            validated.missing_fields.append("changes")
    elif validated.action == "delete":
        if not validated.selection_query and validated.reference_ordinal is None:
            validated.missing_fields.append("selection_query")
    validated.confidence = _score_intent(validated)
    return validated


def email_intent_to_send_payload(intent: EmailIntent) -> dict[str, str]:
    return {
        "to": intent.to,
        "cc": intent.cc,
        "subject": intent.subject,
        "body": intent.body,
        "body_instruction": intent.body_instruction,
    }


def email_intent_to_update_payload(intent: EmailIntent) -> dict[str, str]:
    return {
        "to": intent.to,
        "cc": intent.cc,
        "subject": intent.subject,
        "body": intent.body,
    }


def email_intent_from_llm_payload(payload: dict) -> EmailIntent:
    selection_query = str(payload.get("selection_query") or payload.get("target_email") or "").strip()
    reference_kind = str(payload.get("reference_kind") or "").strip()
    reference_ordinal = payload.get("reference_ordinal") if payload.get("reference_ordinal") is not None else None
    reference_temporal_hint = str(payload.get("reference_temporal_hint") or "").strip()
    if selection_query and not reference_kind and reference_ordinal is None and not reference_temporal_hint:
        inferred = build_reference_spec(selection_query, selection_query)
        reference_kind = inferred.kind
        reference_ordinal = inferred.ordinal
        reference_temporal_hint = inferred.temporal_hint
    return validate_email_intent(EmailIntent(
        action=str(payload.get("action") or "").strip().lower(),
        provider=str(payload.get("provider") or "").strip().lower(),
        selection_query=selection_query,
        reference_kind=reference_kind,
        reference_ordinal=reference_ordinal,
        reference_temporal_hint=reference_temporal_hint,
        to=str(payload.get("to") or "").strip(),
        cc=str(payload.get("cc") or "").strip(),
        subject=str(payload.get("subject") or "").strip(),
        body=str(payload.get("body") or "").strip(),
        body_instruction=str(payload.get("body_instruction") or "").strip(),
        used_llm_fallback=True,
    ))


def merge_email_intents(primary: EmailIntent, fallback: EmailIntent) -> EmailIntent:
    merged = EmailIntent(
        **merge_base_intent_fields(primary, fallback),
        provider=primary.provider or fallback.provider,
        to=primary.to or fallback.to,
        cc=primary.cc or fallback.cc,
        subject=primary.subject or fallback.subject,
        body=primary.body or fallback.body,
        body_instruction=primary.body_instruction or fallback.body_instruction,
    )
    return validate_email_intent(merged)


def _extract_provider(text: str) -> str:
    lowered = str(text or "").lower()
    if "gmail" in lowered:
        return "gmail"
    if "outlook" in lowered:
        return "outlook"
    return ""


def _normalize_email_action(action: str) -> str:
    value = str(action or "").strip().lower()
    if value in {"reply", "forward"}:
        return "send"
    return value


def _is_reply_or_forward_request(text: str) -> bool:
    raw = str(text or "")
    return bool(_EMAIL_FORWARD_RE.search(raw) or _EMAIL_REPLY_RE.search(raw))


def _extract_send_like_fields(text: str) -> dict[str, str]:
    raw = str(text or "").strip()
    addresses = [str(match.group(1) or "").strip() for match in EMAIL_ADDRESS_RE.finditer(raw)]
    cc_pos = _EMAIL_CC_RE.search(raw)
    cc = ""
    if cc_pos:
        cc_tail = raw[cc_pos.end():]
        cc_addrs = _EMAIL_CC_ADDR_RE.findall(cc_tail)
        cc = ", ".join(cc_addrs) if cc_addrs else ""
    to_match = re.search(r"\b(?:aan|to)\s+([A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,})", raw, re.IGNORECASE)
    to = str(to_match.group(1) or "").strip() if to_match else ""
    if not to:
        for address in addresses:
            if address and address.lower() != cc.lower():
                to = address
                break
    subject = _extract_subject(raw)
    instruction = _extract_body_instruction(raw)
    body = _compose_body_from_instruction(instruction, dutch=_looks_dutch(raw)) if instruction else ""
    return {
        "to": to,
        "cc": cc,
        "subject": subject,
        "body": body,
        "body_instruction": instruction,
    }


def _extract_subject(text: str) -> str:
    # Terminators: explicit body marker, inline write cue, sentence-level write
    # cue, or end-of-line.
    _body_cue = r"\s*\b(?:body|tekst|inhoud)\s*[:\-]"
    _inline_write_cue = r"\s+(?:en|ens|and)\s+(?:schrijf|chrijf|write)\b"
    _sentence_write_cue = r"[.!?]\s*(?:en|ens|and)?\s*(?:schrijf|chrijf|write)\b"
    patterns = [
        r"\b(?:onderwerp|subject)\s*[:\-]?\s*(.+?)(?=(?:" + _body_cue + r"|" + _inline_write_cue + r"|" + _sentence_write_cue + r"|\n|$))",
        r"\b(?:met\s+onderwerp|with\s+subject)\s+(.+?)(?=(?:" + _body_cue + r"|" + _inline_write_cue + r"|" + _sentence_write_cue + r"|\n|$))",
    ]
    for pattern in patterns:
        match = re.search(pattern, text, re.IGNORECASE | re.DOTALL)
        if match:
            value = str(match.group(1) or "").strip(" \t\r\n.,;:!?\"'")
            if value:
                return value
    return ""


def _extract_body_instruction(text: str) -> str:
    literal_match = re.search(r"\b(?:body|tekst|inhoud)\s*[:\-]\s*(.+)$", text, re.IGNORECASE | re.DOTALL)
    if literal_match:
        return str(literal_match.group(1) or "").strip()
    instruction_match = re.search(r"\b(?:en|ens|and)?\s*(?:schrijf|chrijf|write)\s+(.+)$", text, re.IGNORECASE | re.DOTALL)
    if instruction_match:
        return str(instruction_match.group(1) or "").strip()
    return ""


def _compose_body_from_instruction(instruction: str, *, dutch: bool) -> str:
    raw = str(instruction or "").strip()
    if not raw:
        return ""
    literal = extract_quoted_value(raw)
    if literal:
        literal = str(literal).strip()
        if literal:
            return literal
    value = raw
    value = re.sub(r"^(?:een\s+|a\s+)?(?:korte\s+|short\s+)?(?:email|e-mail|mail)\b", "", value, flags=re.IGNORECASE).strip()
    value = re.sub(r"^(?:waarin|where(?:in)?|that|met)\s+", "", value, flags=re.IGNORECASE).strip()
    value = re.sub(r"^(?:je|jij|u|you)\s+uitleg\w*\s+dat\s+", "", value, flags=re.IGNORECASE).strip()
    value = re.sub(r"^(?:je|jij|u|you)\s+zegt?\s+dat\s+", "", value, flags=re.IGNORECASE).strip()
    value = value.replace(" van jou", " van mij")
    value = value.replace(" your ", " my ")
    value = value.strip(" \t\r\n.,;:!?")
    if not value:
        value = "Dit is een korte testmail van mij" if dutch else "This is a short test email from me"
    elif not re.match(r"^(?:ik|i|dit|this|het|it|er|there)\b", value, re.IGNORECASE):
        value = value[0].upper() + value[1:] if value else value
    if value[-1:] not in {".", "!", "?"}:
        value += "."
    if dutch:
        return f"Hallo,\n\n{value}\n\nMet vriendelijke groet,"
    return f"Hello,\n\n{value}\n\nKind regards,"


def _extract_selection_query(text: str) -> str:
    raw = str(text or "").strip()
    quoted = extract_quoted_value(raw)
    if quoted:
        return quoted
    subject_match = re.search(
        r"\b(?:met\s+onderwerp|with\s+subject|onderwerp|subject)\s*[:\-]?\s*(.+?)(?=(?:\s+(?:en|ens|and)\s+(?:schrijf|chrijf|write|wijzig|update|edit)\b|\s*\b(?:body|tekst|inhoud|naar|to)\b|$))",
        raw,
        re.IGNORECASE | re.DOTALL,
    )
    if subject_match:
        return str(subject_match.group(1) or "").strip(" \t\r\n.,;:!?\"'")
    noun_match = re.search(
        r"(?:de|het|mijn|my|the)?\s*(?:(?:\d+|een|twee|drie|vier|vijf|one|two|three|four|five)\s+)?(.+?)\s+(?:e-?mails?|mails?|berichten?)\s+(?:.*?)(?:verwijder\w*|delete|remove|wijzig\w*|update|edit|change)",
        raw,
        re.IGNORECASE,
    )
    if noun_match:
        candidate = noun_match.group(1).strip(" .,;:!?\"'")
        candidate = re.sub(r"^(?:kun\s+je|wil\s+je|please|could\s+you|can\s+you)\s+", "", candidate, flags=re.IGNORECASE)
        candidate = re.sub(r"^(?:de|het|mijn|my|the)\s+", "", candidate, flags=re.IGNORECASE)
        candidate = re.sub(r"^(?:\d+|een|twee|drie|vier|vijf|one|two|three|four|five)\s+", "", candidate, flags=re.IGNORECASE)
        return candidate.strip(" .,;:!?\"'")
    candidate = re.sub(r"\b(?:delete|remove|update|edit|change|verwijder\w*|wijzig\w*|bewerk\w*|aanpas\w*|email|e-mail|mail|bericht|gmail|outlook)\b", " ", raw, flags=re.IGNORECASE)
    candidate = re.sub(r"\s+", " ", candidate).strip(" .,;:!?\"'")
    return candidate


def _looks_dutch(text: str) -> bool:
    return bool(_DUTCH_HINT_RE.search(str(text or "")))


def _score_intent(intent: EmailIntent) -> float:
    weights: dict[str, float] = {}
    if intent.provider:
        weights["provider"] = 0.05
    if intent.selection_query:
        weights["selection_query"] = 0.15
    if intent.to:
        weights["to"] = 0.15
    if intent.subject:
        weights["subject"] = 0.12
    if intent.body:
        weights["body"] = 0.12
    return score_action_intent(action=intent.action, missing_fields=intent.missing_fields, base=0.1, weights=weights)