"""Configuration loading for Sanicode.

Searches for config in this order:
  1. Path provided by the caller (CLI --config flag)
  2. sanicode.toml in the current working directory
  3. ~/.config/sanicode/config.toml
"""

from __future__ import annotations

import sys
from dataclasses import dataclass, field
from pathlib import Path

if sys.version_info >= (3, 11):
    import tomllib
else:
    try:
        import tomllib  # type: ignore[no-redef]
    except ImportError:
        import tomli as tomllib  # type: ignore[no-redef]

# Default search paths, in priority order (index 0 = highest priority).
_DEFAULT_SEARCH_PATHS = [
    Path("sanicode.toml"),
    Path.home() / ".config" / "sanicode" / "config.toml",
]


@dataclass
class LLMTierConfig:
    """Configuration for a single LLM tier (fast / analysis / reasoning)."""

    endpoint: str = ""
    model: str = ""
    api_key: str | None = None
    timeout_seconds: int = 30


@dataclass
class LLMConfig:
    """Tiered LLM endpoint configuration."""

    fast: LLMTierConfig = field(default_factory=LLMTierConfig)
    analysis: LLMTierConfig = field(default_factory=LLMTierConfig)
    reasoning: LLMTierConfig = field(default_factory=LLMTierConfig)


@dataclass
class ScanConfig:
    """Scan behavior configuration."""

    exclude_patterns: list[str] = field(default_factory=list)
    include_extensions: list[str] = field(default_factory=lambda: [".py"])
    max_file_size_kb: int = 512


@dataclass
class OutputConfig:
    """Report output preferences."""

    formats: list[str] = field(default_factory=lambda: ["markdown", "sarif"])
    output_dir: str = "sanicode-reports"


@dataclass
class ComplianceConfig:
    """Compliance profile selection."""

    # Supported profiles: owasp-asvs, nist-800-53, asd-stig, pci-dss
    profiles: list[str] = field(
        default_factory=lambda: ["owasp-asvs", "nist-800-53", "asd-stig"]
    )


@dataclass
class SanicodeConfig:
    """Root configuration object for Sanicode."""

    llm: LLMConfig = field(default_factory=LLMConfig)
    scan: ScanConfig = field(default_factory=ScanConfig)
    output: OutputConfig = field(default_factory=OutputConfig)
    compliance: ComplianceConfig = field(default_factory=ComplianceConfig)

    # Path this config was loaded from; None if using defaults.
    _source_path: Path | None = field(default=None, compare=False, repr=False)


def _parse_toml(path: Path) -> SanicodeConfig:
    """Parse a TOML file into a SanicodeConfig."""
    with open(path, "rb") as fh:
        raw = tomllib.load(fh)

    cfg = SanicodeConfig(_source_path=path)

    if llm_raw := raw.get("llm", {}):
        for tier_name in ("fast", "analysis", "reasoning"):
            if tier_raw := llm_raw.get(tier_name, {}):
                tier = LLMTierConfig(
                    endpoint=tier_raw.get("endpoint", ""),
                    model=tier_raw.get("model", ""),
                    api_key=tier_raw.get("api_key"),
                    timeout_seconds=tier_raw.get("timeout_seconds", 30),
                )
                setattr(cfg.llm, tier_name, tier)

    if scan_raw := raw.get("scan", {}):
        cfg.scan = ScanConfig(
            exclude_patterns=scan_raw.get("exclude_patterns", []),
            include_extensions=scan_raw.get("include_extensions", [".py"]),
            max_file_size_kb=scan_raw.get("max_file_size_kb", 512),
        )

    if output_raw := raw.get("output", {}):
        cfg.output = OutputConfig(
            formats=output_raw.get("formats", ["markdown", "sarif"]),
            output_dir=output_raw.get("output_dir", "sanicode-reports"),
        )

    if compliance_raw := raw.get("compliance", {}):
        cfg.compliance = ComplianceConfig(
            profiles=compliance_raw.get(
                "profiles", ["owasp-asvs", "nist-800-53", "asd-stig"]
            )
        )

    return cfg


DEFAULT_CONFIG_TEMPLATE = """\
# sanicode.toml — generated by `sanicode config --init`
#
# Config search order:
#   1. Path passed via --config flag
#   2. sanicode.toml in the current directory
#   3. ~/.config/sanicode/config.toml

# ---------------------------------------------------------------------------
# LLM endpoints — tiered by task complexity.
# All tiers are optional. Without any LLM configured, Sanicode runs in
# degraded mode: AST patterns, graph construction, and compliance lookups
# still work; LLM-assisted reasoning is skipped.
# ---------------------------------------------------------------------------

[llm.fast]
# Quick classification tasks (true-positive filtering, severity estimation).
# Recommended: Granite Nano, Mistral 7B, or any small instruction-tuned model.
# endpoint = "http://localhost:11434/v1"
# model    = "granite3-dense:2b"
# timeout_seconds = 15

[llm.analysis]
# Data flow and sanitization chain reasoning.
# Recommended: Granite Code 8B, DeepSeek Coder 7B, or similar code-focused model.
# endpoint = "http://granite-code.openshift-ai.svc.cluster.local/v1"
# model    = "granite-code:8b"
# timeout_seconds = 30

[llm.reasoning]
# Compliance mapping and report generation — highest quality output.
# Recommended: Llama 3.1 70B, Mixtral 8x22B, or GPT-4o (non-sensitive use only).
# endpoint = "http://llama-70b.openshift-ai.svc.cluster.local/v1"
# model    = "llama3.1:70b"
# timeout_seconds = 60

# ---------------------------------------------------------------------------
# Scan settings
# ---------------------------------------------------------------------------

[scan]
# Glob patterns to exclude from scanning.
exclude_patterns = [
    "**/.venv/**",
    "**/node_modules/**",
    "**/dist/**",
    "**/build/**",
    "**/__pycache__/**",
    "**/migrations/**",
    "**/*.min.js",
]

# File extensions to include (multi-language support is Phase 2+).
include_extensions = [".py"]

# Skip files larger than this.
max_file_size_kb = 512

# ---------------------------------------------------------------------------
# Output settings
# ---------------------------------------------------------------------------

[output]
formats    = ["markdown", "sarif"]
output_dir = "sanicode-reports"

# ---------------------------------------------------------------------------
# Compliance profile selection
# ---------------------------------------------------------------------------

[compliance]
profiles = ["owasp-asvs", "nist-800-53", "asd-stig"]
"""


def load_config(explicit_path: Path | None = None) -> SanicodeConfig:
    """Load configuration from the first available source.

    Args:
        explicit_path: If provided, use this path and raise if it doesn't exist.

    Returns:
        A populated SanicodeConfig (falls back to defaults if no file found).

    Raises:
        FileNotFoundError: If explicit_path is given but does not exist.
        ValueError: If the config file cannot be parsed.
    """
    if explicit_path is not None:
        if not explicit_path.exists():
            raise FileNotFoundError(
                f"Config file not found: {explicit_path}"
            )
        return _parse_toml(explicit_path)

    for candidate in _DEFAULT_SEARCH_PATHS:
        if candidate.exists():
            return _parse_toml(candidate)

    # No file found — return a default config.
    return SanicodeConfig()
