"""Pydantic v2 configuration schema for sentinel.yml."""

from __future__ import annotations

from pathlib import Path
from typing import Literal

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

_DEFAULT_MODULES: list[Literal["cicd", "scm", "secrets", "deps"]] = [
    "cicd", "scm", "secrets", "deps"
]
_DEFAULT_FORMATS: list[Literal["json", "pdf", "html", "markdown"]] = ["json"]


class ClientConfig(BaseModel):
    name: str = "Unknown Client"
    engagement_date: str = ""


class ScanConfig(BaseModel):
    path: str = "."
    modules: list[Literal["cicd", "scm", "secrets", "deps"]] = Field(
        default_factory=lambda: list(_DEFAULT_MODULES)
    )


class CicdConfig(BaseModel):
    enabled: bool = True
    deep_include: bool = False
    required_stages: list[str] = Field(
        default_factory=lambda: [
            "sast",
            "secret_detection",
            "dependency_scanning",
            "dast",
            "container_scanning",
        ]
    )
    # Domains allowed in include: remote: (others flagged as CICD-SEC-8)
    trusted_remote_domains: list[str] = Field(
        default_factory=lambda: ["gitlab.com"]
    )


class DepsConfig(BaseModel):
    enabled: bool = True
    ecosystems: list[str] = Field(
        default_factory=lambda: ["npm", "pip", "go", "cargo", "ruby"]
    )
    max_severity: Literal["critical", "high", "medium", "low"] = "critical"
    ignore_cves: list[str] = Field(default_factory=list)
    nvd_api_key_env: str = "NVD_API_KEY"  # optional — env var name


class SecretsConfig(BaseModel):
    enabled: bool = True
    scan_history: bool = False
    history_depth: int = 100
    allowlist: list[str] = Field(
        default_factory=lambda: ["tests/fixtures/*", "test/fixtures/*"]
    )
    entropy_threshold: float = 4.5
    entropy_min_length: int = 20


class BrandingConfig(BaseModel):
    company: str = "Cephalon Labs"
    logo_path: str = "./assets/logo.png"


class ReportingConfig(BaseModel):
    formats: list[Literal["json", "pdf", "html", "markdown"]] = Field(
        default_factory=lambda: list(_DEFAULT_FORMATS)
    )
    output_dir: str = "./sentinel-reports"
    branding: BrandingConfig = Field(default_factory=BrandingConfig)

    @field_validator("formats")
    @classmethod
    def at_least_one_format(cls, v: list[str]) -> list[str]:
        if not v:
            return ["json"]
        return v


class SentinelConfig(BaseModel):
    version: str = "1.0"
    client: ClientConfig = Field(default_factory=ClientConfig)
    scan: ScanConfig = Field(default_factory=ScanConfig)
    cicd: CicdConfig = Field(default_factory=CicdConfig)
    deps: DepsConfig = Field(default_factory=DepsConfig)
    secrets: SecretsConfig = Field(default_factory=SecretsConfig)
    reporting: ReportingConfig = Field(default_factory=ReportingConfig)

    @model_validator(mode="after")
    def sync_module_flags(self) -> SentinelConfig:
        """Ensure sub-config enabled flags match scan.modules list."""
        modules = self.scan.modules
        self.cicd.enabled = "cicd" in modules
        self.deps.enabled = "deps" in modules
        self.secrets.enabled = "secrets" in modules
        return self

    @classmethod
    def from_file(cls, path: Path) -> SentinelConfig:
        with open(path) as fh:
            raw = yaml.safe_load(fh) or {}
        return cls.model_validate(raw)

    @classmethod
    def defaults(cls) -> SentinelConfig:
        return cls()
