from dataclasses import dataclass
from pathlib import Path

import yaml

from otto.config import OTTO_HOME
from otto.log import get_logger

_log = get_logger("otto.subagents")


@dataclass(frozen=True)
class SubagentProfile:
    name: str
    description: str
    model: str
    fallback: str | None = None
    prompt: str | None = None
    workspace_scope: str = "shared"
    tool_permissions: tuple[str, ...] | None = None


def _parse_frontmatter(content: str) -> tuple[dict[str, object], str]:
    text = content.strip()
    if not text.startswith("---"):
        return {}, content

    rest = text[3:]
    idx = rest.find("\n---")
    if idx == -1:
        return {}, content

    raw_yaml = rest[:idx]
    body = rest[idx + 4 :].strip()
    data = yaml.safe_load(raw_yaml) or {}
    if not isinstance(data, dict):
        return {}, body
    return data, body


def _coerce_str(value: object, field_name: str, path: Path) -> str | None:
    if value is None:
        return None
    if isinstance(value, str):
        normalized = value.strip()
        if normalized:
            return normalized
        return None
    _log.warning("invalid subagent field type", path=str(path), field=field_name)
    return None


def _coerce_workspace_scope(value: object, path: Path) -> str:
    normalized = _coerce_str(value, "workspace_scope", path)
    if normalized is None:
        return "shared"
    if normalized in {"shared", "task"}:
        return normalized
    _log.warning(
        "invalid subagent workspace_scope; using default",
        path=str(path),
        workspace_scope=normalized,
    )
    return "shared"


def _coerce_permissions(value: object, path: Path) -> tuple[str, ...] | None:
    if value is None:
        return None
    if isinstance(value, str):
        normalized = value.strip()
        return (normalized,) if normalized else ()
    if not isinstance(value, list):
        _log.warning("invalid subagent field type", path=str(path), field="tool_permissions")
        return None

    seen: set[str] = set()
    permissions: list[str] = []
    for entry in value:
        if not isinstance(entry, str):
            continue
        normalized = entry.strip()
        if not normalized or normalized in seen:
            continue
        seen.add(normalized)
        permissions.append(normalized)
    return tuple(permissions)


def load_subagent_profiles(directory: Path | None = None) -> list[SubagentProfile]:
    root = (directory or (OTTO_HOME / "agents")).expanduser()
    if not root.exists() or not root.is_dir():
        return []

    profiles: list[SubagentProfile] = []
    seen: set[str] = set()

    for file_path in sorted(root.glob("*.md")):
        try:
            raw = file_path.read_text(encoding="utf-8")
        except OSError as exc:
            _log.warning("failed reading subagent file", path=str(file_path), error=str(exc))
            continue

        frontmatter, body = _parse_frontmatter(raw)

        name = _coerce_str(frontmatter.get("name"), "name", file_path)
        description = _coerce_str(frontmatter.get("description"), "description", file_path)
        model = _coerce_str(frontmatter.get("model"), "model", file_path)
        fallback = _coerce_str(frontmatter.get("fallback"), "fallback", file_path)
        workspace_scope = _coerce_workspace_scope(frontmatter.get("workspace_scope"), file_path)
        tool_permissions = _coerce_permissions(frontmatter.get("tool_permissions"), file_path)

        if not name or not description or not model:
            _log.warning(
                "skipping invalid subagent profile",
                path=str(file_path),
                has_name=bool(name),
                has_description=bool(description),
                has_model=bool(model),
            )
            continue

        if name in seen:
            _log.warning("duplicate subagent profile name; keeping first", name=name)
            continue

        seen.add(name)
        prompt = body.strip() or None
        profiles.append(
            SubagentProfile(
                name=name,
                description=description,
                model=model,
                fallback=fallback,
                prompt=prompt,
                workspace_scope=workspace_scope,
                tool_permissions=tool_permissions,
            )
        )

    return profiles
