"""Pydantic models for team configuration."""

from __future__ import annotations

from pathlib import Path
from typing import Any, Literal

import yaml
from pydantic import BaseModel, ConfigDict, ValidationError, field_validator


class RoleSpec(BaseModel):
    """A role specification within a preset."""

    model_config = ConfigDict(frozen=True)

    role: str
    level: str
    count: int = 1
    required: bool = True


class PresetConfig(BaseModel):
    """A project preset defining team shape and defaults."""

    model_config = ConfigDict(frozen=True)

    name: str
    description: str
    default_team_size: int
    roles: list[RoleSpec]
    skills: list[str]
    default_ci: str = "github-actions"


class TeamMember(BaseModel):
    """A generated team member."""

    name: str
    agent_name: str = ""
    role: str
    level: str
    email: str
    reports_to: str = ""
    personality: str = ""


class TeamConfig(BaseModel):
    """Full team configuration for bootstrap."""

    project_name: str
    preset: str
    team_members: list[TeamMember]
    skills: list[str]
    git_email_domain: str = "gmail.com"
    git_email_prefix: str = ""


# ---------------------------------------------------------------------------
# YAML Config Schema (used by ``--config`` flag)
# ---------------------------------------------------------------------------


class MemberOverride(BaseModel):
    """Per-member override in a YAML config file."""

    name: str | None = None
    role: str | None = None
    level: str | None = None
    personality: str | None = None


class YamlConfig(BaseModel):
    """Schema for a YAML configuration file used with ``2real-team init --config``."""

    preset: str
    project_name: str | None = None
    team_size: int | None = None
    git_email_prefix: str = ""
    target: str = "."
    skills: list[str] | None = None
    members: list[MemberOverride] | None = None

    @field_validator("preset")
    @classmethod
    def preset_not_empty(cls, v: str) -> str:
        if not v.strip():
            raise ValueError("preset must not be empty")
        return v

    @classmethod
    def from_yaml(cls, path: str | Path) -> YamlConfig:
        """Load and validate a YAML config file.

        Raises ``ValueError`` with a human-readable message on validation
        failure, and ``FileNotFoundError`` when the file does not exist.
        """
        raw = _read_yaml_mapping(path)
        try:
            return cls(**raw)
        except ValidationError as exc:
            raise ValueError(_format_validation_error(path, exc)) from exc


# ---------------------------------------------------------------------------
# Unified install-config schema (install.config.yaml — shared with
# framework/install/bootstrap.py; see framework/config/install.config.default.yaml)
# ---------------------------------------------------------------------------

#: Top-level keys that mark a YAML file as a UNIFIED install config rather than
#: the legacy flat team config (which has a required top-level ``preset``).
UNIFIED_CONFIG_MARKERS = frozenset(
    {"version", "repo", "scm", "ci", "ticketing", "pre_push", "ontology", "children"}
)


class RepoSection(BaseModel):
    model_config = ConfigDict(extra="allow")

    expect: Literal["fresh", "existing", "any"] = "fresh"


class ProjectSection(BaseModel):
    model_config = ConfigDict(extra="allow")

    name: str | None = None
    model: Literal["standalone", "meta", "child"] = "standalone"


class ScmSection(BaseModel):
    model_config = ConfigDict(extra="allow")

    provider: Literal["github"] = "github"
    owner: str | None = None


class CiSection(BaseModel):
    model_config = ConfigDict(extra="allow")

    provider: Literal["github-actions"] = "github-actions"


class TicketingSection(BaseModel):
    model_config = ConfigDict(extra="allow")

    provider: Literal["github-issues"] = "github-issues"


class PrePushSection(BaseModel):
    model_config = ConfigDict(extra="allow")

    mode: Literal["noop", "enforce", "none"] = "noop"


class OntologySection(BaseModel):
    model_config = ConfigDict(extra="allow")

    enabled: bool = True


class TeamSection(BaseModel):
    model_config = ConfigDict(extra="allow")

    enabled: bool = True
    preset: str | None = None
    size: int | None = None


class ChildSpec(BaseModel):
    model_config = ConfigDict(extra="allow")

    path: str
    flavor: Literal["product", "infra"] = "product"


class InstallConfig(BaseModel):
    """The unified YAML install config (see the framework key reference).

    Covers every interactive decision point of both installers. Extra keys are
    allowed (forward compatibility) and carried through to the framework
    bootstrapper, which records the resolved config in the target repo.
    """

    model_config = ConfigDict(extra="allow")

    version: Literal[1] = 1
    repo: RepoSection = RepoSection()
    project: ProjectSection = ProjectSection()
    scm: ScmSection = ScmSection()
    ci: CiSection = CiSection()
    ticketing: TicketingSection = TicketingSection()
    pre_push: PrePushSection = PrePushSection()
    ontology: OntologySection = OntologySection()
    team: TeamSection = TeamSection()
    children: list[ChildSpec] = []

    @classmethod
    def from_yaml(cls, path: str | Path) -> InstallConfig:
        raw = _read_yaml_mapping(path)
        try:
            return cls(**raw)
        except ValidationError as exc:
            raise ValueError(_format_validation_error(path, exc)) from exc


def is_unified_config(raw: dict) -> bool:
    """True when the mapping uses the unified install-config schema."""
    if UNIFIED_CONFIG_MARKERS & set(raw):
        return True
    return isinstance(raw.get("team"), dict) or isinstance(raw.get("project"), dict)


def _read_yaml_mapping(path: str | Path) -> dict:
    p = Path(path)
    if not p.exists():
        raise FileNotFoundError(f"Config file not found: {path}")
    with open(p) as f:
        try:
            raw: Any = yaml.safe_load(f)
        except yaml.YAMLError as exc:
            raise ValueError(f"Invalid YAML in config file ({path}): {exc}") from exc
    if not isinstance(raw, dict):
        raise ValueError(f"Config file must be a YAML mapping, got {type(raw).__name__}")
    return raw


def _format_validation_error(path: str | Path, exc: ValidationError) -> str:
    lines = [f"Invalid config file ({path}):"]
    for err in exc.errors():
        loc = " -> ".join(str(part) for part in err["loc"])
        lines.append(f"  {loc}: {err['msg']}")
    return "\n".join(lines)
