"""InitConfig Pydantic model — the contract between init and generate.

Load and save complyform.init.yaml with nested YAML structure matching
the §A.1.8.2 schema: project, compliance, services, output, metadata blocks.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Literal

import yaml
from pydantic import BaseModel, ConfigDict

from complyform.core.errors import InitConfigNotFoundError, InitConfigVersionError

if TYPE_CHECKING:
    from pathlib import Path

_SUPPORTED_VERSION = "1"

_YAML_HEADER = (
    "# complyform.init.yaml — Generated by 'complyform init'\n"
    "# Edit this file to adjust settings, then run 'complyform generate'\n\n"
)


class ServiceEntry(BaseModel):
    """Single service selection in init config."""

    model_config = ConfigDict(strict=True, frozen=True)

    id: str
    display_name: str
    category: str


class InitConfig(BaseModel):
    """Parsed complyform.init.yaml — the contract between init and generate."""

    model_config = ConfigDict(strict=True)

    version: str = "1"

    # project block
    cloud: Literal["gcp", "aws", "azure"]
    project_id: str
    org_id: str | None = None
    region: str

    # compliance block
    frameworks: list[str]

    # services block
    services: list[ServiceEntry]

    # output block
    output_directory: str = "./infra"

    # metadata block
    generated_by: str = "complyform"
    generated_at: str  # ISO 8601
    cli_version: str
    tier: str  # informational — tier at generation time


def load_init_config(path: Path) -> InitConfig:
    """Load and validate complyform.init.yaml.

    Raises InitConfigNotFoundError if missing.
    """
    if not path.exists():
        raise InitConfigNotFoundError(str(path))

    raw = yaml.safe_load(path.read_text(encoding="utf-8"))
    if not isinstance(raw, dict):
        raise InitConfigNotFoundError(str(path))

    # Check version
    version = str(raw.get("version", ""))
    if version != _SUPPORTED_VERSION:
        raise InitConfigVersionError(version, _SUPPORTED_VERSION)

    # Flatten nested YAML structure into InitConfig fields
    project = raw.get("project", {})
    compliance = raw.get("compliance", {})
    services_raw = raw.get("services", [])
    output = raw.get("output", {})
    metadata = raw.get("metadata", {})

    services = [ServiceEntry.model_validate(s) for s in services_raw]

    return InitConfig(
        version=version,
        cloud=project.get("cloud", ""),
        project_id=project.get("project_id", ""),
        org_id=project.get("org_id"),
        region=project.get("region", ""),
        frameworks=compliance.get("frameworks", []),
        services=services,
        output_directory=output.get("directory", "./infra"),
        generated_by=metadata.get("generated_by", "complyform"),
        generated_at=metadata.get("generated_at", ""),
        cli_version=metadata.get("cli_version", ""),
        tier=metadata.get("tier", ""),
    )


def save_init_config(config: InitConfig, path: Path) -> None:
    """Write complyform.init.yaml with human-readable comments."""
    data: dict[str, object] = {
        "version": config.version,
        "project": {
            "cloud": config.cloud,
            "project_id": config.project_id,
            "region": config.region,
        },
        "compliance": {
            "frameworks": config.frameworks,
        },
        "services": [s.model_dump(mode="json") for s in config.services],
        "output": {
            "directory": config.output_directory,
        },
        "metadata": {
            "generated_by": config.generated_by,
            "generated_at": config.generated_at,
            "cli_version": config.cli_version,
            "tier": config.tier,
        },
    }

    # Include org_id only when present
    if config.org_id is not None:
        project_block = data["project"]
        assert isinstance(project_block, dict)
        project_block["org_id"] = config.org_id

    path.parent.mkdir(parents=True, exist_ok=True)
    yaml_text = yaml.dump(data, default_flow_style=False, sort_keys=False)
    path.write_text(_YAML_HEADER + yaml_text, encoding="utf-8")
