"""Avatar execution status mapping — timeline phases, model chip, plan quality (no GTK)."""

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
import re
from typing import Any

from .avatar_local_plan_fallback import is_get_status_stub_plan
from .i18n import translate


class ExecutionPhase(str, Enum):
    INTENT = "intent"
    CLASSIFY = "classify"
    PLANNER = "planner"
    POLICY = "policy"
    EXECUTE = "execute"
    VERIFIER = "verifier"
    DONE = "done"
    ERROR = "error"


@dataclass(frozen=True)
class PlanFetchMeta:
    planner_mode: str = ""
    planner_request_id: str = ""
    error: str | None = None
    used_local_fallback: bool = False


@dataclass(frozen=True)
class TimelineEvent:
    phase: ExecutionPhase
    ok: bool = True
    detail: str = ""
    failed_layer: str = ""


_SECRET_PATTERNS = (
    re.compile(r"(?i)(authorization|bearer|token|api[_-]?key|password|secret)\s*[:=]\s*\S+"),
    re.compile(r"eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+"),
    re.compile(r"\bsk-[A-Za-z0-9_-]{8,}\b"),
    re.compile(r"(?i)\bbearer\s+\S+"),
)


def should_show_advisory_consultation(
    request_text: str,
    plan: dict[str, Any] | None,
    *,
    is_compound: bool,
    defer_to_planner: bool,
) -> bool:
    """True when cloud plan-v2 left a get_status stub for a non-device compound goal."""
    if not is_get_status_stub_plan(plan):
        return False
    try:
        from .avatar_intent_router import (
            is_folder_analysis_intent,
            is_local_computer_job_search_intent,
            is_local_search_intent,
            is_plan_only_intent,
            is_web_research_intent,
            is_workspace_file_creation_intent,
        )

        if is_plan_only_intent(request_text):
            return False
        if is_workspace_file_creation_intent(request_text):
            return False
        if is_folder_analysis_intent(request_text):
            return False
        if is_web_research_intent(request_text):
            return False
        if is_local_search_intent(request_text) or is_local_computer_job_search_intent(request_text):
            return False
    except ImportError:
        pass
    if not (is_compound or defer_to_planner):
        return False
    text = str(request_text or "").strip()
    if not text:
        return False
    return True


def parse_planner_model_label(planner_mode: str) -> str:
    """Turn planner_mode like orchestra_openai:gpt-4o into gpt-4o."""
    raw = str(planner_mode or "").strip()
    if not raw:
        return ""
    if ":" in raw:
        return raw.rsplit(":", 1)[-1].strip()
    if raw.startswith("orchestra_"):
        return raw.removeprefix("orchestra_").replace("_", " ")
    if raw.startswith("rules"):
        return ""
    return raw


def format_model_chip(
    *,
    lang: str,
    route_path: str = "",
    planner_mode: str = "",
    orchestra_state: str = "",
    local_model: str = "",
) -> str:
    """Compact provider · model label for the active Avatar task."""
    model = parse_planner_model_label(planner_mode) or str(local_model or "").strip()
    path = str(route_path or "").strip().lower()
    state = str(orchestra_state or "").strip().lower()

    if model and (state == "cloud_active" or path == "cloud_orchestra" or planner_mode.startswith("orchestra")):
        provider = translate(lang, "status.model.chip.orchestra")
        return f"{provider} · {model}"

    if path == "local_ollama" or (local_model and state != "cloud_active"):
        provider = translate(lang, "status.model.chip.local")
        label = local_model or model or translate(lang, "status.model.chip.local_default")
        return f"{provider} · {label}"

    if path == "regex" or planner_mode.startswith("rules"):
        provider = translate(lang, "status.model.chip.rules")
        return provider

    if state == "cloud_active":
        provider = translate(lang, "status.model.chip.orchestra")
        return f"{provider} · {model}" if model else provider

    if state == "local_fallback":
        provider = translate(lang, "status.model.chip.local")
        label = local_model or translate(lang, "status.model.chip.local_default")
        return f"{provider} · {label}"

    return translate(lang, "status.model.chip.offline")


def resolve_failure_layer(
    *,
    plan_fetch_error: str | None,
    orchestra_detail: str = "",
) -> str:
    """Map errors to a user-visible layer: classify / planner / orchestra / agent."""
    err = str(plan_fetch_error or "").strip().lower()
    detail = str(orchestra_detail or "").strip().lower()
    if err in {"backend_unreachable", "http_502", "http_503", "http_504"}:
        return "orchestra"
    if err.startswith("http_"):
        return "planner"
    if "invalid_device_auth" in err:
        return "agent"
    if "orchestra" in detail or "orchestra_disabled" in detail:
        return "orchestra"
    if err:
        return "planner"
    return ""


def sanitize_backend_error_snippet(error: str | None, *, max_len: int = 140) -> str:
    """Safe one-line backend error for UI (no secrets)."""
    raw = str(error or "").strip()
    if not raw:
        return ""
    cleaned = raw
    for pattern in _SECRET_PATTERNS:
        cleaned = pattern.sub("[redacted]", cleaned)
    cleaned = re.sub(r"\s+", " ", cleaned).strip()
    if len(cleaned) > max_len:
        cleaned = f"{cleaned[: max_len - 1]}…"
    return cleaned


def format_timeline_line(event: TimelineEvent, *, lang: str) -> str:
    phase_key = f"avatar.execution.timeline.{event.phase.value}"
    if event.phase == ExecutionPhase.PLANNER and event.detail.startswith("local"):
        phase_key = "avatar.execution.timeline.planner_local"
    label = translate(lang, phase_key)
    marker = translate(lang, "avatar.execution.timeline.ok" if event.ok else "avatar.execution.timeline.fail")
    if event.phase == ExecutionPhase.INTENT and event.detail:
        return f"{label}: {event.detail} {marker}"
    line = f"{label} {marker}"
    if event.detail and event.phase in {
        ExecutionPhase.PLANNER,
        ExecutionPhase.EXECUTE,
        ExecutionPhase.POLICY,
        ExecutionPhase.VERIFIER,
    }:
        line = f"{line} ({event.detail})"
    if not event.ok and event.failed_layer:
        layer_key = f"avatar.execution.timeline.layer.{event.failed_layer}"
        layer = translate(lang, layer_key)
        if layer != layer_key:
            line = f"{line} — {layer}"
    return line


def build_timeline_block(events: list[TimelineEvent], *, lang: str) -> str:
    if not events:
        return ""
    lines = [format_timeline_line(event, lang=lang) for event in events]
    return "\n".join(lines)


def resolve_planner_timeline_detail(
    *,
    planner_mode: str = "",
    route_path: str = "",
    orchestra_state: str = "",
    local_model: str = "",
) -> str:
    """Timeline planner detail — avoid labeling cloud advisory as local fallback."""
    model = parse_planner_model_label(planner_mode)
    if model:
        return model
    raw_mode = str(planner_mode or "").strip()
    if raw_mode:
        if raw_mode.startswith("rules"):
            return "rules"
        return raw_mode

    path = str(route_path or "").strip().lower()
    state = str(orchestra_state or "").strip().lower()
    if path == "cloud_orchestra":
        return local_model or ""
    if path == "local_ollama":
        return local_model or "local"
    if path == "regex":
        return "rules"
    if state == "cloud_active" and path not in {"regex", "local_ollama", "unavailable"}:
        return local_model or ""
    if state == "local_fallback" or path in {"unavailable", "unknown"}:
        return "local"
    return ""


def format_intent_timeline_label(intent_label: str, *, lang: str) -> str:
    raw = str(intent_label or "").strip()
    if not raw:
        return ""
    prefix = translate(lang, "avatar.execution.timeline.intent")
    return f"{prefix}: {raw}"


def format_policy_timeline_detail(routing_mode: str, *, lang: str) -> str:
    mode = str(routing_mode or "safe").strip().lower()
    key = f"avatar.execution.timeline.policy.{mode}"
    label = translate(lang, key)
    return label if label != key else mode


def build_pipeline_timeline_chip(
    events: list[TimelineEvent],
    *,
    lang: str,
    intent_label: str = "",
    routing_mode: str = "",
) -> str:
    """Compact single-line pipeline summary for the timeline chip."""
    parts: list[str] = []
    intent_line = format_intent_timeline_label(intent_label, lang=lang)
    if intent_line:
        parts.append(intent_line)
    for event in events:
        if event.phase in {ExecutionPhase.INTENT, ExecutionPhase.ERROR}:
            continue
        line = format_timeline_line(event, lang=lang)
        if line:
            parts.append(line)
    policy_detail = format_policy_timeline_detail(routing_mode, lang=lang)
    if policy_detail and not any(event.phase == ExecutionPhase.POLICY for event in events):
        policy_prefix = translate(lang, "avatar.execution.timeline.policy")
        parts.insert(min(2, len(parts)), f"{policy_prefix}: {policy_detail}")
    return " · ".join(parts)


def build_chat_timeline_events(
    *,
    classify_ok: bool = True,
    intent_label: str = "",
    routing_mode: str = "",
    planner_mode: str = "",
    plan_fetch_error: str | None = None,
    plan_preview: bool = False,
    advisory_consultation: bool = False,
    orchestra_detail: str = "",
    route_path: str = "",
    orchestra_state: str = "",
    local_model: str = "",
    verifier_ok: bool | None = None,
    lang: str = "en",
) -> list[TimelineEvent]:
    """Phases for intent → classify → planner → policy → (execute) → verifier → done."""
    events: list[TimelineEvent] = []
    if intent_label:
        events.append(
            TimelineEvent(phase=ExecutionPhase.INTENT, ok=True, detail=intent_label),
        )
    events.append(TimelineEvent(phase=ExecutionPhase.CLASSIFY, ok=classify_ok))
    planner_ok = not plan_fetch_error and not advisory_consultation
    planner_detail = resolve_planner_timeline_detail(
        planner_mode=planner_mode,
        route_path=route_path,
        orchestra_state=orchestra_state,
        local_model=local_model,
    )
    if plan_fetch_error or advisory_consultation:
        failed_layer = resolve_failure_layer(
            plan_fetch_error=plan_fetch_error,
            orchestra_detail=orchestra_detail,
        )
        events.append(
            TimelineEvent(
                phase=ExecutionPhase.PLANNER,
                ok=False,
                detail=planner_detail,
                failed_layer=failed_layer or "planner",
            )
        )
        events.append(
            TimelineEvent(
                phase=ExecutionPhase.ERROR,
                ok=False,
                failed_layer=failed_layer or "planner",
            )
        )
        return events

    events.append(
        TimelineEvent(
            phase=ExecutionPhase.PLANNER,
            ok=planner_ok,
            detail=planner_detail,
        )
    )
    if routing_mode:
        events.append(
            TimelineEvent(
                phase=ExecutionPhase.POLICY,
                ok=True,
                detail=format_policy_timeline_detail(routing_mode, lang=lang),
            )
        )
    if plan_preview:
        if verifier_ok is not None:
            events.append(
                TimelineEvent(
                    phase=ExecutionPhase.VERIFIER,
                    ok=bool(verifier_ok),
                )
            )
        events.append(TimelineEvent(phase=ExecutionPhase.DONE, ok=True))
    return events


def build_execute_timeline_event(*, step_index: int, step_total: int, step_label: str) -> TimelineEvent:
    detail = f"{step_index}/{step_total}: {step_label}"
    return TimelineEvent(phase=ExecutionPhase.EXECUTE, ok=True, detail=detail)


def build_done_timeline_event(*, ok: bool = True) -> TimelineEvent:
    return TimelineEvent(phase=ExecutionPhase.DONE, ok=ok)
