from __future__ import annotations

from dataclasses import asdict, dataclass, field


@dataclass
class IntentFrame:
    domain: str = ""
    action: str = ""
    operation: str = ""
    provider_scope: str = "default"
    source_scope: str = "default"
    folder_scope: str = "Inbox"
    result_shape: str = "list"
    search_query: str = ""
    target_entity: dict = field(default_factory=dict)
    target_reference: dict = field(default_factory=dict)
    slots: dict = field(default_factory=dict)
    time_filter: str = "none"
    since: str = ""
    until: str = ""
    time_label: str = ""
    days_ahead: int = 7
    followup_relation: str = "new_task"
    requires_live_data: bool = True
    can_answer_from_context: bool = False
    confidence: float = 0.0
    source_text: str = ""

    def to_dict(self) -> dict:
        return asdict(self)


def build_intent_frame(task_state: dict | None) -> IntentFrame | None:
    task = dict(task_state or {})
    if not task:
        return None

    task_type = str(task.get("task_type") or "").strip().lower()
    if task_type == "email_lookup":
        return IntentFrame(
            domain="email",
            operation=str(task.get("operation") or "find_received"),
            provider_scope=str(task.get("provider_scope") or "default"),
            folder_scope=str(task.get("folder_scope") or "Inbox"),
            result_shape=str(task.get("result_shape") or "list"),
            target_entity=dict(task.get("target_entity") or {}),
            target_reference=dict(task.get("target_reference") or {}),
            slots=dict(task.get("slots") or {}),
            time_filter=str(task.get("time_filter") or "none"),
            since=str(task.get("since") or ""),
            until=str(task.get("until") or ""),
            time_label=str(task.get("time_label") or ""),
            days_ahead=7,
            followup_relation=str(task.get("followup_relation") or "new_task"),
            requires_live_data=bool(task.get("requires_live_data", True)),
            can_answer_from_context=bool(task.get("can_answer_from_context", False)),
            confidence=float(task.get("confidence") or 0.0),
            source_text=str(task.get("source_text") or ""),
        )

    if task_type == "email_action":
        return IntentFrame(
            domain="email",
            action=str(task.get("operation") or task.get("action") or "send"),
            operation=str(task.get("operation") or task.get("action") or "send"),
            provider_scope=str(task.get("provider_scope") or "default"),
            folder_scope=str(task.get("folder_scope") or "Inbox"),
            target_reference=dict(task.get("target_reference") or {}),
            slots=dict(task.get("slots") or {}),
            followup_relation=str(task.get("followup_relation") or "new_task"),
            requires_live_data=bool(task.get("requires_live_data", True)),
            can_answer_from_context=bool(task.get("can_answer_from_context", False)),
            confidence=float(task.get("confidence") or 0.0),
            source_text=str(task.get("source_text") or ""),
        )

    if task_type == "calendar_lookup":
        return IntentFrame(
            domain="calendar",
            operation=str(task.get("operation") or "calendar_read"),
            provider_scope=str(task.get("provider_scope") or "default"),
            target_reference=dict(task.get("target_reference") or {}),
            slots=dict(task.get("slots") or {}),
            time_filter=str(task.get("time_filter") or "none"),
            since=str(task.get("since") or ""),
            until=str(task.get("until") or ""),
            time_label=str(task.get("time_label") or ""),
            days_ahead=int(task.get("days_ahead") or 7),
            followup_relation=str(task.get("followup_relation") or "new_task"),
            requires_live_data=bool(task.get("requires_live_data", True)),
            can_answer_from_context=bool(task.get("can_answer_from_context", False)),
            confidence=float(task.get("confidence") or 0.0),
            source_text=str(task.get("source_text") or ""),
        )

    if task_type == "calendar_action":
        return IntentFrame(
            domain="calendar",
            action=str(task.get("operation") or task.get("action") or "create"),
            operation=str(task.get("operation") or task.get("action") or "create"),
            provider_scope=str(task.get("provider_scope") or "default"),
            target_reference=dict(task.get("target_reference") or {}),
            slots=dict(task.get("slots") or {}),
            followup_relation=str(task.get("followup_relation") or "new_task"),
            requires_live_data=bool(task.get("requires_live_data", True)),
            can_answer_from_context=bool(task.get("can_answer_from_context", False)),
            confidence=float(task.get("confidence") or 0.0),
            source_text=str(task.get("source_text") or ""),
        )

    if task_type == "search_lookup":
        return IntentFrame(
            domain="search",
            operation=str(task.get("operation") or "search"),
            source_scope=str(task.get("source_scope") or "default"),
            search_query=str(task.get("search_query") or "").strip(),
            followup_relation=str(task.get("followup_relation") or "new_task"),
            requires_live_data=bool(task.get("requires_live_data", True)),
            can_answer_from_context=bool(task.get("can_answer_from_context", False)),
            confidence=float(task.get("confidence") or 0.0),
            source_text=str(task.get("source_text") or ""),
        )

    if task.get("domain"):
        return intent_frame_from_payload(task)

    return None


def _provider_to_scope(provider: str) -> str:
    normalized = str(provider or "").strip().lower()
    if normalized == "gmail":
        return "gmail_only"
    if normalized == "outlook":
        return "outlook_only"
    return "default"


def _scope_to_provider(scope: str) -> str:
    normalized = str(scope or "").strip().lower()
    if normalized == "gmail_only":
        return "gmail"
    if normalized == "outlook_only":
        return "outlook"
    return ""


def intent_frame_from_payload(payload: dict | None, domain_hint: str = "") -> IntentFrame | None:
    raw = dict(payload or {})
    if not raw:
        return None

    target_reference = dict(raw.get("target_reference") or {})
    selection_query = str(raw.get("selection_query") or raw.get("search_query") or target_reference.get("query") or "").strip()
    if selection_query and not target_reference.get("query"):
        target_reference["query"] = selection_query
    if "reference_kind" in raw and not target_reference.get("kind"):
        target_reference["kind"] = str(raw.get("reference_kind") or "").strip()
    if raw.get("reference_ordinal") is not None and target_reference.get("ordinal") is None:
        target_reference["ordinal"] = raw.get("reference_ordinal")
    if "reference_temporal_hint" in raw and not target_reference.get("temporal_hint"):
        target_reference["temporal_hint"] = str(raw.get("reference_temporal_hint") or "").strip()

    slots = dict(raw.get("slots") or {})
    for key in (
        "to", "cc", "subject", "body", "body_instruction",
        "summary", "start", "end", "location", "description", "attendees", "all_day",
    ):
        if key in raw and key not in slots:
            slots[key] = raw.get(key)

    domain = str(raw.get("domain") or domain_hint or "").strip().lower()
    action = str(raw.get("action") or "").strip().lower()
    if not domain:
        if any(key in slots for key in ("to", "subject", "body", "body_instruction")):
            domain = "email"
        elif any(key in slots for key in ("summary", "start", "end", "location", "description", "attendees", "all_day")):
            domain = "calendar"
        elif raw.get("source_scope") or action == "search":
            domain = "search"

    return IntentFrame(
        domain=domain,
        action=action,
        operation=str(raw.get("operation") or "").strip(),
        provider_scope=str(raw.get("provider_scope") or _provider_to_scope(raw.get("provider")) or "default").strip(),
        source_scope=str(raw.get("source_scope") or "default").strip(),
        folder_scope=str(raw.get("folder_scope") or raw.get("folder") or "Inbox").strip(),
        result_shape=str(raw.get("result_shape") or "list").strip(),
        search_query=str(raw.get("search_query") or "").strip(),
        target_entity=dict(raw.get("target_entity") or {}),
        target_reference=target_reference,
        slots=slots,
        time_filter=str(raw.get("time_filter") or "none").strip(),
        since=str(raw.get("since") or "").strip(),
        until=str(raw.get("until") or "").strip(),
        time_label=str(raw.get("time_label") or "").strip(),
        days_ahead=int(raw.get("days_ahead") or 7),
        followup_relation=str(raw.get("followup_relation") or "new_task").strip(),
        requires_live_data=bool(raw.get("requires_live_data", True)),
        can_answer_from_context=bool(raw.get("can_answer_from_context", False)),
        confidence=float(raw.get("confidence") or 0.0),
        source_text=str(raw.get("source_text") or "").strip(),
    )


def intent_frame_from_direct_intent(intent: dict | None, *, source_text: str = "") -> IntentFrame | None:
    raw = dict(intent or {})
    if not raw:
        return None
    action = str(raw.get("intent") or "").strip().lower()
    if action == "calendar_read":
        return IntentFrame(
            domain="calendar",
            operation="calendar_read",
            provider_scope=str(raw.get("provider_scope") or "default"),
            target_reference=dict(raw.get("target_reference") or {}),
            time_filter=str(raw.get("time_filter") or "none"),
            since=str(raw.get("since") or ""),
            until=str(raw.get("until") or ""),
            time_label=str(raw.get("time_label") or ""),
            days_ahead=int(raw.get("days_ahead") or 7),
            source_text=source_text,
        )
    if action == "search_lookup":
        return IntentFrame(
            domain="search",
            operation="search",
            source_scope=str(raw.get("source_scope") or "default"),
            search_query=str(raw.get("search_query") or "").strip(),
            source_text=source_text,
        )
    if action in {"inbox_read", "email_search"}:
        return IntentFrame(
            domain="email",
            operation="find_received",
            provider_scope=str(raw.get("provider_scope") or "default"),
            folder_scope=str(raw.get("folder") or "Inbox"),
            search_query=str(raw.get("search_query") or "").strip(),
            target_reference=dict(raw.get("target_reference") or {}),
            time_filter=str(raw.get("time_filter") or "none"),
            since=str(raw.get("since") or ""),
            until=str(raw.get("until") or ""),
            days_ahead=7,
            source_text=source_text,
        )
    return None


def email_intent_to_frame(intent, *, source_text: str = "") -> IntentFrame:
    return IntentFrame(
        domain="email",
        action=str(intent.action or ""),
        provider_scope=_provider_to_scope(getattr(intent, "provider", "")),
        target_reference={
            "query": str(getattr(intent, "selection_query", "") or ""),
            "kind": str(getattr(intent, "reference_kind", "") or ""),
            "ordinal": getattr(intent, "reference_ordinal", None),
            "temporal_hint": str(getattr(intent, "reference_temporal_hint", "") or ""),
        },
        slots={
            "to": str(getattr(intent, "to", "") or ""),
            "cc": str(getattr(intent, "cc", "") or ""),
            "subject": str(getattr(intent, "subject", "") or ""),
            "body": str(getattr(intent, "body", "") or ""),
            "body_instruction": str(getattr(intent, "body_instruction", "") or ""),
        },
        confidence=float(getattr(intent, "confidence", 0.0) or 0.0),
        source_text=source_text,
    )


def email_intent_from_frame(frame: IntentFrame):
    from backend.email_intents import EmailIntent, validate_email_intent

    slots = dict(frame.slots or {})
    target_reference = dict(frame.target_reference or {})
    intent = EmailIntent(
        action=str(frame.action or "").strip().lower(),
        provider=_scope_to_provider(frame.provider_scope),
        selection_query=str(target_reference.get("query") or "").strip(),
        reference_kind=str(target_reference.get("kind") or "").strip(),
        reference_ordinal=target_reference.get("ordinal"),
        reference_temporal_hint=str(target_reference.get("temporal_hint") or "").strip(),
        to=str(slots.get("to") or "").strip(),
        cc=str(slots.get("cc") or "").strip(),
        subject=str(slots.get("subject") or "").strip(),
        body=str(slots.get("body") or "").strip(),
        body_instruction=str(slots.get("body_instruction") or "").strip(),
    )
    return validate_email_intent(intent)


def appointment_intent_to_frame(intent, *, source_text: str = "") -> IntentFrame:
    return IntentFrame(
        domain="calendar",
        action=str(intent.action or ""),
        target_reference={
            "query": str(getattr(intent, "selection_query", "") or ""),
            "kind": str(getattr(intent, "reference_kind", "") or ""),
            "ordinal": getattr(intent, "reference_ordinal", None),
            "temporal_hint": str(getattr(intent, "reference_temporal_hint", "") or ""),
        },
        slots={
            "summary": str(getattr(intent, "summary", "") or ""),
            "start": str(getattr(intent, "start", "") or ""),
            "end": str(getattr(intent, "end", "") or ""),
            "location": str(getattr(intent, "location", "") or ""),
            "description": str(getattr(intent, "description", "") or ""),
            "attendees": str(getattr(intent, "attendees", "") or ""),
            "all_day": bool(getattr(intent, "all_day", False)),
        },
        confidence=float(getattr(intent, "confidence", 0.0) or 0.0),
        source_text=source_text,
    )


def appointment_intent_from_frame(frame: IntentFrame):
    from backend.appointment_intents import AppointmentIntent, validate_appointment_intent

    slots = dict(frame.slots or {})
    target_reference = dict(frame.target_reference or {})
    intent = AppointmentIntent(
        action=str(frame.action or "").strip().lower(),
        selection_query=str(target_reference.get("query") or "").strip(),
        reference_kind=str(target_reference.get("kind") or "").strip(),
        reference_ordinal=target_reference.get("ordinal"),
        reference_temporal_hint=str(target_reference.get("temporal_hint") or "").strip(),
        summary=str(slots.get("summary") or "").strip(),
        start=str(slots.get("start") or "").strip(),
        end=str(slots.get("end") or "").strip(),
        location=str(slots.get("location") or "").strip(),
        description=str(slots.get("description") or "").strip(),
        attendees=str(slots.get("attendees") or "").strip(),
        all_day=bool(slots.get("all_day", False)),
    )
    return validate_appointment_intent(intent)


def task_state_from_intent_frame(frame: IntentFrame, *, base_task_state: dict | None = None) -> dict:
    task = dict(base_task_state or {})

    if frame.domain == "email":
        task_type = "email_action" if frame.action else "email_lookup"
        task.update({
            "task_type": task_type,
            "operation": frame.operation or frame.action or "find_received",
            "provider_scope": frame.provider_scope or "default",
            "folder_scope": frame.folder_scope or "Inbox",
            "result_shape": frame.result_shape or "list",
            "target_entity": dict(frame.target_entity or {}),
            "target_reference": dict(frame.target_reference or {}),
            "slots": dict(frame.slots or {}),
            "time_filter": frame.time_filter or "none",
            "since": frame.since or "",
            "until": frame.until or "",
            "time_label": frame.time_label or "",
            "followup_relation": frame.followup_relation or "new_task",
            "requires_live_data": frame.requires_live_data,
            "can_answer_from_context": frame.can_answer_from_context,
            "confidence": frame.confidence,
            "source_text": frame.source_text or "",
            "intent_frame": frame.to_dict(),
        })
        if not task.get("since"):
            task.pop("since", None)
        if not task.get("until"):
            task.pop("until", None)
        if not task.get("time_label"):
            task.pop("time_label", None)
        if not task.get("target_entity"):
            task.pop("target_entity", None)
        return task

    if frame.domain == "calendar":
        task_type = "calendar_action" if frame.action else "calendar_lookup"
        task.update({
            "task_type": task_type,
            "operation": frame.operation or frame.action or "calendar_read",
            "provider_scope": frame.provider_scope or "default",
            "target_reference": dict(frame.target_reference or {}),
            "slots": dict(frame.slots or {}),
            "time_filter": frame.time_filter or "none",
            "since": frame.since or "",
            "until": frame.until or "",
            "time_label": frame.time_label or "",
            "days_ahead": int(frame.days_ahead or 7),
            "followup_relation": frame.followup_relation or "new_task",
            "requires_live_data": frame.requires_live_data,
            "can_answer_from_context": frame.can_answer_from_context,
            "confidence": frame.confidence,
            "source_text": frame.source_text or "",
            "intent_frame": frame.to_dict(),
        })
        if not task.get("since"):
            task.pop("since", None)
        if not task.get("until"):
            task.pop("until", None)
        if not task.get("time_label"):
            task.pop("time_label", None)
        return task

    if frame.domain == "search":
        task.update({
            "task_type": "search_lookup",
            "operation": frame.operation or "search",
            "source_scope": frame.source_scope or "default",
            "search_query": frame.search_query or "",
            "followup_relation": frame.followup_relation or "new_task",
            "requires_live_data": frame.requires_live_data,
            "can_answer_from_context": frame.can_answer_from_context,
            "confidence": frame.confidence,
            "source_text": frame.source_text or "",
            "intent_frame": frame.to_dict(),
        })
        return task

    return task


def intent_frame_to_direct_intent(frame: IntentFrame | None) -> dict | None:
    if frame is None or not frame.domain:
        return None

    if frame.domain == "calendar":
        return {
            "intent": "calendar_read",
            "provider_scope": frame.provider_scope or "default",
            "search_query": str((frame.target_reference or {}).get("query") or "").strip(),
            "target_reference": dict(frame.target_reference or {}),
            "folder": "Inbox",
            "time_filter": frame.time_filter or "none",
            "since": frame.since or "",
            "until": frame.until or "",
            "time_label": frame.time_label or "",
            "unread_only": False,
            "days_ahead": int(frame.days_ahead or 7),
        }

    if frame.domain == "search":
        return {
            "intent": "search_lookup",
            "search_query": frame.search_query or "",
            "source_scope": frame.source_scope or "default",
            "folder": "Inbox",
            "time_filter": "none",
            "unread_only": False,
            "days_ahead": 7,
        }

    if frame.domain != "email":
        return None

    entity = dict(frame.target_entity or {})
    entity_text = str(entity.get("text") or "").strip()
    if entity_text:
        entity_kind = str(entity.get("kind") or "")
        if entity_kind == "email_address":
            query_value = f"from:{entity_text}".strip()
        elif entity_kind == "sender":
            query_value = f"from {entity_text}".strip()
        else:
            query_value = entity_text
        return {
            "intent": "email_search",
            "provider_scope": frame.provider_scope or "default",
            "search_query": query_value,
            "target_reference": dict(frame.target_reference or {}),
            "folder": frame.folder_scope or "Inbox",
            "time_filter": frame.time_filter or "none",
            "since": frame.since or "",
            "until": frame.until or "",
            "unread_only": False,
            "days_ahead": 7,
        }

    search_query = (
        frame.search_query
        or str((frame.target_reference or {}).get("query") or "").strip()
    )
    return {
        "intent": "email_search" if search_query else "inbox_read",
        "provider_scope": frame.provider_scope or "default",
        "search_query": search_query,
        "target_reference": dict(frame.target_reference or {}),
        "folder": frame.folder_scope or "Inbox",
        "time_filter": frame.time_filter or "none",
        "since": frame.since or "",
        "until": frame.until or "",
        "unread_only": False,
        "days_ahead": 7,
    }


def intent_frame_display_goal(frame: IntentFrame | None) -> str:
    if frame is None or not frame.domain:
        return ""

    if frame.domain == "search":
        source_label = {
            "workspace": "workspace",
            "web": "internet",
            "google_drive": "Google Drive",
            "onedrive": "OneDrive",
            "default": "beschikbare bronnen",
        }.get(frame.source_scope or "default", "beschikbare bronnen")
        if frame.search_query:
            return f"Zoek in {source_label} naar {frame.search_query}."
        return f"Zoek in {source_label}."

    time_label = {
        "today": "vandaag",
        "yesterday": "gisteren",
        "this_week": "deze week",
        "last_week": "vorige week",
        "this_month": "deze maand",
        "last_month": "vorige maand",
        "custom_range": frame.time_label or "de gevraagde periode",
        "none": "de gevraagde periode",
    }.get(frame.time_filter or "none", "de gevraagde periode")

    provider_label = {
        "outlook_only": "Outlook",
        "gmail_only": "Gmail",
        "cross_provider": "Outlook en Gmail",
        "default": "mail",
    }.get(frame.provider_scope or "default", "mail")

    if frame.domain == "calendar":
        calendar_provider_label = {
            "outlook_only": "Outlook",
            "gmail_only": "Google Agenda",
            "cross_provider": "Outlook en Google Agenda",
            "default": "agenda",
        }.get(frame.provider_scope or "default", "agenda")
        calendar_time_label = time_label
        if (frame.time_filter or "none") == "none":
            if int(frame.days_ahead or 7) == 1:
                calendar_time_label = "vandaag"
            elif int(frame.days_ahead or 7) == 2:
                calendar_time_label = "morgen"
            elif int(frame.days_ahead or 7) == 7:
                calendar_time_label = "deze week"
            elif int(frame.days_ahead or 7) == 14:
                calendar_time_label = "volgende week"
        if calendar_provider_label == "agenda":
            return f"Bekijk agenda voor {calendar_time_label}."
        return f"Bekijk {calendar_provider_label} voor {calendar_time_label}."

    entity = dict(frame.target_entity or {})
    entity_text = str(entity.get("text") or "").strip()
    entity_kind = str(entity.get("kind") or "")
    if (frame.result_shape or "list") == "count":
        if entity_text:
            if entity_kind == "subject":
                return f"Hoeveel e-mails over {entity_text} heb ik in {provider_label} {frame.folder_scope or 'Inbox'} voor {time_label}?"
            return f"Hoeveel e-mails heb ik van {entity_text} in {provider_label} {frame.folder_scope or 'Inbox'} voor {time_label}?"
        return f"Hoeveel e-mails staan in {provider_label} {frame.folder_scope or 'Inbox'} voor {time_label}?"
    if entity_text:
        if entity_kind == "subject":
            return f"Controleer {provider_label} {frame.folder_scope or 'Inbox'} op e-mails over {entity_text} voor {time_label}."
        return f"Controleer {provider_label} {frame.folder_scope or 'Inbox'} op e-mails van {entity_text} voor {time_label}."
    return f"Bekijk {provider_label} {frame.folder_scope or 'Inbox'} voor {time_label}."