"""Batch manifest loader — parse and validate batch-manifest.yaml.

Schema: version 1 with up to 10 projects, each specifying label, cloud,
state path, optional credentials and frameworks.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Literal

if TYPE_CHECKING:
    from pathlib import Path

import yaml
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator

from complyform.core.errors import (
    BatchDuplicateLabelsError,
    BatchManifestInvalidError,
    BatchManifestNotFoundError,
    BatchSizeLimitError,
)

_MAX_PROJECTS = 10


class BatchProject(BaseModel):
    """Single project entry in a batch manifest."""

    model_config = ConfigDict(strict=True)

    label: str = Field(
        ..., min_length=1, max_length=64, pattern=r"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"
    )
    cloud: Literal["gcp", "aws", "azure"]
    state: str = Field(..., min_length=1)
    credentials: str | None = None
    frameworks: list[str] | None = None

    @field_validator("frameworks")
    @classmethod
    def validate_frameworks(cls, v: list[str] | None) -> list[str] | None:
        """Empty frameworks list is invalid — omit the field for all."""
        if v is not None and len(v) == 0:
            msg = (
                "frameworks list cannot be empty — omit the field to use all configured"
            )
            raise ValueError(msg)
        return v


class BatchManifest(BaseModel):
    """Canonical batch-manifest.yaml schema."""

    model_config = ConfigDict(strict=True)

    version: str = "1"
    projects: list[BatchProject] = Field(..., min_length=1, max_length=_MAX_PROJECTS)

    @model_validator(mode="after")
    def validate_unique_labels(self) -> BatchManifest:
        """Reject duplicate project labels."""
        labels = [p.label for p in self.projects]
        dupes = [lab for lab in labels if labels.count(lab) > 1]
        if dupes:
            raise BatchDuplicateLabelsError(sorted(set(dupes)))
        return self


def load_batch_manifest(path: Path) -> BatchManifest:
    """Load and validate batch-manifest.yaml.

    Raises:
        BatchManifestNotFoundError: File not found.
        BatchManifestInvalidError: YAML parse or Pydantic validation error.
        BatchSizeLimitError: >10 projects.
        BatchDuplicateLabelsError: Duplicate labels.
    """
    if not path.exists():
        raise BatchManifestNotFoundError(str(path))

    try:
        raw = yaml.safe_load(path.read_text(encoding="utf-8"))
    except yaml.YAMLError as exc:
        raise BatchManifestInvalidError(str(exc)) from exc

    if not isinstance(raw, dict):
        raise BatchManifestInvalidError("Expected a YAML mapping at top level.")

    # Explicit size check before Pydantic for clearer error messaging
    projects = raw.get("projects")
    if isinstance(projects, list) and len(projects) > _MAX_PROJECTS:
        raise BatchSizeLimitError(len(projects), _MAX_PROJECTS)

    try:
        return BatchManifest.model_validate(raw)
    except BatchDuplicateLabelsError:
        raise
    except Exception as exc:
        raise BatchManifestInvalidError(str(exc)) from exc
