from __future__ import annotations

import logging
from functools import partial
from typing import Annotated, Any

import anyio
import yaml
from fastapi import Body, HTTPException

from lionagi._paths import LIONAGI_HOME
from lionagi.libs.frontmatter import parse_frontmatter as _parse_frontmatter

from ..registry import studio_route
from ._path_safety import public_path, safe_path_join

_AGENTS_ROOT = LIONAGI_HOME / "agents"
_log = logging.getLogger(__name__)


def _normalize_frontmatter(fm: dict[str, Any]) -> dict[str, Any]:
    """Return canonical Studio agent frontmatter without mutating caller state."""
    normalized = dict(fm)
    if "reasoning_effort" in normalized:
        if "effort" not in normalized:
            normalized["effort"] = normalized["reasoning_effort"]
        normalized.pop("reasoning_effort", None)
        _log.warning("Agent frontmatter key 'reasoning_effort' is deprecated; use 'effort'")
    return normalized


def _canonical_model(model: Any, provider: Any) -> str:
    model_s = str(model or "").strip()
    if not model_s:
        return ""
    if "/" in model_s:
        return model_s
    provider_s = str(provider or "").strip()
    return f"{provider_s}/{model_s}" if provider_s else model_s


def list_agents() -> list[dict[str, Any]]:
    if not _AGENTS_ROOT.exists():
        return []
    out = []
    for path in sorted(_AGENTS_ROOT.glob("*.md")):
        try:
            text = path.read_text()
        except OSError:
            continue
        fm, _ = _parse_frontmatter(text)
        fm = _normalize_frontmatter(fm)
        entry: dict[str, Any] = {
            "name": path.stem,
            "path": public_path(path),
            "provider": str(fm.get("provider") or ""),
            "model": str(fm.get("model") or ""),
            "description": str(fm.get("description") or ""),
            **{k: v for k, v in fm.items() if k not in ("model", "description", "provider")},
        }
        if path.is_symlink():
            try:
                entry["symlink_target"] = public_path(path.resolve())
            except OSError:
                pass
        out.append(entry)
    return out


def get_agent(name: str) -> dict[str, Any] | None:
    safe_path_join(_AGENTS_ROOT, name)

    stem = name.removesuffix(".md")
    path = _AGENTS_ROOT / f"{stem}.md"
    if not path.exists():
        return None
    try:
        text = path.read_text()
    except OSError:
        return None
    fm, body = _parse_frontmatter(text)
    fm = _normalize_frontmatter(fm)

    result: dict[str, Any] = {
        "name": stem,
        "path": public_path(path),
        "provider": str(fm.get("provider") or ""),
        "model": str(fm.get("model") or ""),
        "system_prompt": fm.get("system_prompt") or (body if body else None),
        "guidance": fm.get("guidance") or None,
    }

    result["yolo"] = bool(fm.get("yolo", False))
    result["fast_mode"] = bool(fm.get("fast_mode", False))
    result["lion_system"] = bool(fm.get("lion_system", True))

    for optional_key in ("permission_mode", "effort", "description"):
        if optional_key in fm:
            result[optional_key] = fm[optional_key]

    if path.is_symlink():
        try:
            result["symlink_target"] = public_path(path.resolve())
        except OSError:
            pass

    return result


_KNOWN_FRONTMATTER_KEYS = (
    "provider",
    "model",
    "description",
    "guidance",
    "permission_mode",
    "effort",
    "yolo",
    "fast_mode",
    "lion_system",
)


def update_agent(name: str, data: dict[str, Any]) -> dict[str, Any] | None:
    """Write an agent profile back to disk; preserves unknown frontmatter keys; follows symlinks."""
    safe_path_join(_AGENTS_ROOT, name)
    stem = name.removesuffix(".md")
    path = _AGENTS_ROOT / f"{stem}.md"
    if not path.exists():
        return None

    try:
        existing_text = path.read_text()
    except OSError:
        existing_text = ""
    existing_fm, existing_body = _parse_frontmatter(existing_text)
    existing_fm = _normalize_frontmatter(existing_fm)

    fm: dict[str, Any] = dict(existing_fm)
    incoming = _normalize_frontmatter(data)
    for key in _KNOWN_FRONTMATTER_KEYS:
        if key not in incoming:
            continue
        value = incoming[key]
        if value in (None, ""):
            fm.pop(key, None)
        else:
            fm[key] = value

    if "model" in fm:
        model = _canonical_model(fm.get("model"), fm.get("provider"))
        if model:
            fm["model"] = model
        else:
            fm.pop("model", None)

    new_body = data.get("system_prompt")
    if new_body is None:
        new_body = existing_body
    new_body = (new_body or "").strip()

    if fm:
        fm_text = yaml.safe_dump(fm, sort_keys=False, allow_unicode=True).rstrip()
        new_text = f"---\n{fm_text}\n---\n\n{new_body}\n" if new_body else f"---\n{fm_text}\n---\n"
    else:
        new_text = f"{new_body}\n" if new_body else ""

    path.write_text(new_text)

    return get_agent(name)


@studio_route("/agents/", method="GET", area="agents", name="list_agents")
async def list_agents_route() -> dict[str, Any]:
    agents = await anyio.to_thread.run_sync(list_agents)
    return {"agents": agents}


@studio_route("/agents/{name}", method="GET", area="agents", name="get_agent")
async def get_agent_route(name: str) -> dict[str, Any]:
    agent = await anyio.to_thread.run_sync(partial(get_agent, name))
    if agent is None:
        raise HTTPException(status_code=404, detail=f"Agent '{name}' not found")
    return agent


@studio_route("/agents/{name}", method="POST", area="agents")
async def create_agent(name: str) -> dict[str, Any]:
    # TODO(lift-backend-writes)
    raise HTTPException(status_code=501, detail="Not implemented")


@studio_route("/agents/{name}", method="PUT", area="agents", name="update_agent")
async def update_agent_route(
    name: str, body: Annotated[dict[str, Any], Body(...)]
) -> dict[str, Any]:
    updated = await anyio.to_thread.run_sync(partial(update_agent, name, body))
    if updated is None:
        raise HTTPException(status_code=404, detail=f"Agent '{name}' not found")
    return updated


@studio_route("/agents/{name}", method="DELETE", area="agents")
async def delete_agent(name: str) -> dict[str, Any]:
    # TODO(lift-backend-writes)
    raise HTTPException(status_code=501, detail="Not implemented")


@studio_route("/agents/{name}/validate", method="POST", area="agents")
async def validate_agent(name: str, body: Annotated[dict[str, Any], Body(...)]) -> dict[str, Any]:
    errors: list[str] = []
    if not (body.get("name") or "").strip():
        errors.append("name is required")
    if not (body.get("provider") or "").strip():
        errors.append("provider is required")
    if not (body.get("model") or "").strip():
        errors.append("model is required")
    return {"ok": not errors, "errors": errors or None}
