"""Pydantic models and formatting helpers for Apollo entities and plans."""

from __future__ import annotations

import json
from datetime import datetime, timezone


# ---------------------------------------------------------------------------
# Status icon maps
# ---------------------------------------------------------------------------

STATUS_ICONS = {
    "HEALTHY": "✓", "IN_SYNC": "✓", "ROLLED_OUT": "✓",
    "UNHEALTHY": "✗", "OUT_OF_SYNC": "✗",
    "ROLLING_OUT": "⟳", "IN_PROGRESS": "⟳",
    "UNKNOWN": "?", "NOT_APPLICABLE": "-",
}

PLAN_STATUS_ICONS = {
    "SUCCESSFUL": "✓", "SUCCESS": "✓",
    "UNSUCCESSFUL": "✗", "FAILURE": "✗",
    "ACTIVE": "⟳", "PENDING_INTERRUPTION": "⏸",
    "ABORTED": "✗", "TIMEOUT": "✗", "INTERRUPTED": "✗",
}

TASK_STATUS_ICONS = {
    "SUCCEEDED": "✓", "ACTIVE": "⟳", "FAILED": "✗",
    "ABANDONED": "—", "INTERRUPTED": "✗",
}


# ---------------------------------------------------------------------------
# Formatting helpers
# ---------------------------------------------------------------------------

def format_duration(start_str: str, end_str: str | None = None) -> str:
    """Format duration between two ISO timestamps, or elapsed since start."""
    if not start_str:
        return ""
    try:
        start = datetime.fromisoformat(start_str.replace("Z", "+00:00"))
        if end_str:
            end = datetime.fromisoformat(end_str.replace("Z", "+00:00"))
        else:
            end = datetime.now(timezone.utc)
        total_secs = int((end - start).total_seconds())
        if total_secs < 60:
            return f"{total_secs}s"
        elif total_secs < 3600:
            return f"{total_secs // 60}m {total_secs % 60}s"
        else:
            hours = total_secs // 3600
            mins = (total_secs % 3600) // 60
            return f"{hours}h {mins}m"
    except Exception:
        return ""


def format_entity_health(entity: dict) -> list[str]:
    """Format a single entity's health data into readable lines."""
    lines: list[str] = []
    name = entity.get("displayName") or entity.get("id", "unknown")
    entity_id = entity.get("id", "unknown")
    entity_name = entity.get("entityLocator", {}).get("entityName", "")
    product_id = entity.get("product", {}).get("productId", "")
    lifecycle = entity.get("lifecycleStatus", "")

    lines.append(f"  {name} ({entity_id})")
    if entity_name or product_id:
        lines.append(f"    Entity: {entity_name}  Product: {product_id}")
    if lifecycle:
        lines.append(f"    Lifecycle: {lifecycle}")

    reported = entity.get("reportedStatus")
    if reported:
        version = reported.get("version", "unknown")
        rollout = reported.get("rolloutStatus", "unknown")
        config = reported.get("configStatus", "unknown")
        health = reported.get("healthStatus", "unknown")

        lines.append(f"    Version:  {version}")
        lines.append(
            f"    Health:   {STATUS_ICONS.get(health, '?')} {health}  |  "
            f"Rollout: {STATUS_ICONS.get(rollout, '?')} {rollout}  |  "
            f"Config: {STATUS_ICONS.get(config, '?')} {config}"
        )

        blockers = reported.get("upgradeBlockerTypes")
        if blockers:
            lines.append(f"    Blockers: {', '.join(blockers)}")
    else:
        lines.append("    Status: No reported status")

    plan_status = entity.get("planStatus")
    if plan_status:
        typename = plan_status.get("__typename", "")
        status_label = typename.replace("ApolloEntityPlanStatus_", "") if typename else "Unknown"
        lines.append(f"    Plan:     {status_label}")

    plans_conn = entity.get("nonGenericPlans")
    if plans_conn:
        plans = plans_conn.get("plans", [])
        if plans:
            lines.append(f"    Recent Plans ({len(plans)}):")
            for plan in plans:
                ps = plan.get("status", {}) or {}
                status_type = ps.get("type", "unknown")
                icon = PLAN_STATUS_ICONS.get(status_type, "?")

                tasks = plan.get("tasks", [])
                plan_desc = ""
                for t in tasks:
                    tdesc = (t.get("description") or {}).get("summary", "")
                    if "target state" in tdesc.lower() or "install" in tdesc.lower():
                        plan_desc = "Install"
                        break
                    elif "enforce" in tdesc.lower() or "config" in tdesc.lower():
                        plan_desc = "Enforce config"
                        break

                start = plan.get("startTime", "")
                end = plan.get("endTime")
                duration = format_duration(start, end)
                start_display = start[:19].replace("T", " ") if start else ""

                result_summary = (ps.get("result") or {}).get("summary", "")
                reason_summary = (ps.get("reason") or {}).get("summary", "")
                detail = result_summary or reason_summary

                plan_line = f"      {icon} {status_type}"
                if plan_desc:
                    plan_line += f" ({plan_desc})"
                if duration:
                    plan_line += f" [{duration}]"
                if start_display:
                    plan_line += f" {start_display}"
                lines.append(plan_line)

                if detail:
                    if len(detail) > 120:
                        detail = detail[:117] + "..."
                    lines.append(f"        {detail}")

                for task in tasks:
                    desc = (task.get("description") or {}).get("summary", "") or (
                        task.get("description") or {}
                    ).get("type", "")
                    ts = task.get("status") or {}
                    ttype = ts.get("type", "") if isinstance(ts, dict) else str(ts)
                    ticon = TASK_STATUS_ICONS.get(ttype, "?")
                    lines.append(f"        {ticon} {desc}: {ttype}")
        else:
            lines.append("    Recent Plans: (none)")

    lines.append("    (Use plan_details for full plan info with events and error logs)")
    return lines


def format_plan_task(task: dict, lines: list[str], indent: int = 2) -> None:
    """Format a single plan task with its events into readable lines."""
    prefix = " " * indent
    desc = task.get("description") or {}
    task_name = desc.get("summary") or desc.get("type", "unknown task")
    origin = task.get("origin", "")
    ts = task.get("status") or {}
    status_type = ts.get("type", "unknown")
    status_icon = TASK_STATUS_ICONS.get(status_type, "?")
    created = task.get("createdAt", "")
    updated = ts.get("updatedAt") or ""
    duration = format_duration(created, updated if updated else None)

    task_line = f"{prefix}{status_icon} {task_name}"
    if origin:
        task_line += f" ({origin})"
    task_line += f" {status_type}"
    if duration:
        task_line += f" {duration}"
    lines.append(task_line)

    task_status_desc = ts.get("description")
    if task_status_desc:
        summary = task_status_desc.get("summary", "")
        if summary:
            lines.append(f"{prefix}  └ {summary}")
        metadata = task_status_desc.get("metadataMap")
        if metadata and isinstance(metadata, dict):
            details = metadata.get("details") or metadata.get("message")
            if details and isinstance(details, str):
                detail_lines = details.strip().split("\n")
                for dl in detail_lines[:10]:
                    lines.append(f"{prefix}    {dl}")
                if len(detail_lines) > 10:
                    lines.append(f"{prefix}    ... ({len(detail_lines) - 10} more lines)")

    events = task.get("events", [])
    if events:
        show_events = events[-8:] if len(events) > 8 else events
        if len(events) > 8:
            lines.append(f"{prefix}  ({len(events)} events, showing last 8)")
        for event in show_events:
            ts_str = event.get("timestamp", "")
            time_display = ts_str[11:19] if len(ts_str) >= 19 else ts_str
            event_desc = (event.get("description") or {}).get("summary", "")
            if event_desc:
                lines.append(f"{prefix}  {time_display} {event_desc}")
    elif not task_status_desc:
        latest = task.get("latestEvent")
        if latest:
            event_desc = (latest.get("description") or {}).get("summary", "")
            if event_desc:
                lines.append(f"{prefix}  └ {event_desc}")

    for subtask in task.get("subtasks", []):
        format_plan_task(subtask, lines, indent=indent + 2)


def format_plan_details(entity: dict, env_name: str, plan_index: int = 0) -> str:
    """Format full plan details for an entity. Returns formatted string."""
    ename = entity.get("displayName") or entity.get("id", "unknown")
    product_id = entity.get("product", {}).get("productId", "")
    lifecycle = entity.get("lifecycleStatus", "")
    version = (entity.get("reportedStatus") or {}).get("version", "")

    plans = entity.get("nonGenericPlans", {}).get("plans", [])
    if not plans:
        return f"No plans found for entity '{ename}' in {env_name}."

    if plan_index >= len(plans):
        return (
            f"Plan index {plan_index} out of range. "
            f"Entity '{ename}' has {len(plans)} plan(s) (use 0-{len(plans)-1})."
        )

    plan = plans[plan_index]
    ps = plan.get("status", {}) or {}
    status_type = ps.get("type", "unknown")
    status_icon = PLAN_STATUS_ICONS.get(status_type, "?")
    agent_id = plan.get("agentId", "unknown")
    start = plan.get("startTime", "")
    end = plan.get("endTime")
    duration = format_duration(start, end)
    start_display = start[:19].replace("T", " ") if start else ""
    end_display = end[:19].replace("T", " ") if end else ""

    changes = plan.get("stateChanges", [])
    change_types = [c.get("changeType", "") for c in changes]
    plan_type = "Install" if "INSTALL" in " ".join(change_types).upper() else "Plan"
    if any("ENFORCE" in ct.upper() or "CONFIG" in ct.upper() for ct in change_types):
        plan_type = "Enforce config"
    elif any("UPGRADE" in ct.upper() for ct in change_types):
        plan_type = "Upgrade"
    elif any("UNINSTALL" in ct.upper() for ct in change_types):
        plan_type = "Uninstall"

    lines = [
        f"Plan Details: {ename} on {env_name}",
        "=" * 60,
        "",
        f"  {status_icon} {plan_type} {version or ''}",
        f"  Status:     {status_type}",
        f"  Agent:      {agent_id}",
        f"  Product:    {product_id}",
        f"  Lifecycle:  {lifecycle}",
    ]
    if start_display:
        lines.append(f"  Started:    {start_display}")
    if end_display:
        lines.append(f"  Ended:      {end_display}")
    if duration:
        lines.append(f"  Duration:   {duration}")

    result = ps.get("result")
    reason = ps.get("reason")
    if result:
        lines.append("")
        lines.append(f"  Result: {result.get('summary', '')}")
        metadata = result.get("metadataMap")
        if metadata and isinstance(metadata, dict):
            details = metadata.get("details") or metadata.get("resultDetails") or metadata.get("message")
            if details:
                lines.append("")
                lines.append("  Result Details:")
                lines.append("  " + "-" * 56)
                if isinstance(details, str):
                    for dl in details.split("\n"):
                        lines.append(f"  {dl}")
                else:
                    lines.append(f"  {json.dumps(details, indent=2)}")
                lines.append("  " + "-" * 56)
            else:
                for k, v in metadata.items():
                    if isinstance(v, str) and len(v) > 200:
                        lines.append(f"  {k}: {v[:200]}...")
                    else:
                        lines.append(f"  {k}: {v}")

    if reason and reason.get("summary"):
        lines.append(f"  Reason: {reason.get('summary', '')}")

    tasks = plan.get("tasks", [])
    if tasks:
        lines.append("")
        lines.append("Tasks:")
        lines.append("-" * 60)
        for task in tasks:
            format_plan_task(task, lines, indent=2)

    lines.append("")
    if plan_index == 0 and len(plans) > 1:
        lines.append(f"This is the most recent plan. Use plan_index=1..{len(plans)-1} to see older plans.")

    return "\n".join(lines)
