"""
Configuration validation for Lattice.

This module implements pure validation logic for configuration files.
All functions are Core layer - no I/O, use @pre/@post contracts.

Reference: RFC-002 §3.1 Configuration Schema
"""

from __future__ import annotations

from typing import Any

import deal

from lattice.core.types.config import (
    CaptureConfig,
    CompilerConfig,
    Config,
    EmbeddingConfig,
    SafetyConfig,
    ThresholdsConfig,
)


@deal.pre(lambda raw: len(raw) > 0)
@deal.raises(ValueError)
@deal.post(lambda result: isinstance(result, Config))
def validate_config(raw: dict[str, Any]) -> Config:
    """Validate raw config dict and return typed Config.

    Embedding is optional — omit [embedding] section for FTS5-only search.

    >>> raw = {"compiler": {"model": "ollama/llama3"}}
    >>> config = validate_config(raw)
    >>> config.compiler.model
    'ollama/llama3'
    >>> config.embedding is None
    True
    >>> raw2 = {"compiler": {"model": "ollama/llama3"}, "embedding": {"model": "ollama/nomic"}}
    >>> validate_config(raw2).embedding.model
    'ollama/nomic'
    """
    embedding_raw = raw.get("embedding")
    embedding = _validate_embedding(embedding_raw) if embedding_raw else None

    return Config(
        compiler=_validate_compiler(raw.get("compiler", {})),
        embedding=embedding,
        thresholds=_validate_thresholds(raw.get("thresholds", {})),
        safety=_validate_safety(raw.get("safety", {})),
        capture=_validate_capture(raw.get("capture", {})),
    )


@deal.raises(ValueError)
@deal.post(lambda result: isinstance(result, CompilerConfig))
def _validate_compiler(raw: dict[str, Any]) -> CompilerConfig:
    """Validate compiler configuration section."""
    model = raw.get("model", "")
    if not model:
        raise ValueError(
            "compiler.model is required (litellm format: 'provider/model')"
        )

    return CompilerConfig(
        model=model,
        api_key_env=raw.get("api_key_env", ""),
        batch_cap=raw.get("batch_cap", 30),
        base_url=raw.get("base_url"),
    )


@deal.raises(ValueError)
@deal.post(lambda result: isinstance(result, EmbeddingConfig))
@deal.post(lambda result: result.dimensions > 0)
def _validate_embedding(raw: dict[str, Any]) -> EmbeddingConfig:
    """Validate embedding configuration section."""
    model = raw.get("model", "")
    if not model:
        raise ValueError(
            "embedding.model is required (litellm format: 'provider/model')"
        )

    dimensions = raw.get("dimensions", 768)
    if dimensions <= 0:
        raise ValueError(f"dimensions must be positive: {dimensions}")

    return EmbeddingConfig(
        model=model,
        dimensions=dimensions,
        api_key_env=raw.get("api_key_env", ""),
        base_url=raw.get("base_url"),
    )


@deal.raises(ValueError)
@deal.post(lambda result: isinstance(result, ThresholdsConfig))
def _validate_thresholds(raw: dict[str, Any]) -> ThresholdsConfig:
    """Validate thresholds configuration section."""
    warn = raw.get("warn_tokens", 3000)
    alert = raw.get("alert_tokens", 5000)
    per_session = raw.get("per_session_tokens", 4000)
    if warn >= alert:
        raise ValueError(f"warn_tokens ({warn}) must be < alert_tokens ({alert})")
    if per_session < 1000:
        raise ValueError(f"per_session_tokens ({per_session}) must be >= 1000")
    return ThresholdsConfig(
        warn_tokens=warn, alert_tokens=alert, per_session_tokens=per_session
    )


@deal.raises(ValueError)
@deal.post(lambda result: isinstance(result, SafetyConfig))
def _validate_safety(raw: dict[str, Any]) -> SafetyConfig:
    """Validate safety configuration section."""
    backup = raw.get("backup_keep", 10)
    if backup < 0:
        raise ValueError(f"backup_keep must be non-negative: {backup}")
    return SafetyConfig(
        auto_apply=raw.get("auto_apply", True),
        backup_keep=backup,
        secret_patterns=raw.get("secret_patterns", []),
    )


@deal.post(lambda result: isinstance(result, CaptureConfig))
def _validate_capture(raw: dict[str, Any]) -> CaptureConfig:
    """Validate capture configuration section.

    >>> _validate_capture({})  # uses default
    CaptureConfig(mode='passive')
    >>> _validate_capture({"mode": "active"})
    CaptureConfig(mode='active')
    >>> _validate_capture({"mode": "invalid"})  # falls back to passive
    CaptureConfig(mode='passive')
    """
    mode = raw.get("mode", "passive")
    # Validate mode is one of the allowed values
    valid_modes = ("passive", "active")
    if mode not in valid_modes:
        mode = "passive"  # Default to passive for invalid values
    return CaptureConfig(mode=mode)


@deal.post(lambda result: isinstance(result, Config))
def get_default_config() -> Config:
    """Get default configuration.

    Default has no embedding (FTS5-only search).

    >>> config = get_default_config()
    >>> config.compiler.model
    ''
    >>> config.embedding is None
    True
    >>> config.capture.mode
    'passive'
    """
    return Config(
        compiler=CompilerConfig(model="", api_key_env=""),
        thresholds=ThresholdsConfig(),
        safety=SafetyConfig(),
        capture=CaptureConfig(),
    )


@deal.pre(
    lambda content, fallback: isinstance(content, str) and isinstance(fallback, str)
)
def parse_rule_title(content: str, fallback: str) -> str:
    """Extract title from markdown content.

    >>> parse_rule_title("# My Title\\nContent", "fallback")
    'My Title'
    >>> parse_rule_title("No heading", "fallback")
    'fallback'
    """
    for line in content.split("\n"):
        stripped = line.strip()
        if stripped.startswith("# ") and len(stripped) > 2:
            return stripped[2:].strip()
    return fallback


@deal.pre(lambda raw: isinstance(raw, dict))
def parse_project(raw: dict[str, Any]) -> dict[str, Any] | None:
    """Parse project registration dict, return None if invalid.

    >>> parse_project({"path": "/a", "name": "a", "registered_at": "2026-01-01"})
    {'path': '/a', 'name': 'a', 'registered_at': '2026-01-01', 'exclude_from_global': False}
    >>> parse_project({"path": "/a"})  # missing required fields

    """
    required = ("path", "name", "registered_at")
    if not all(k in raw for k in required):
        return None
    return {
        "path": raw["path"],
        "name": raw["name"],
        "registered_at": raw["registered_at"],
        "exclude_from_global": raw.get("exclude_from_global", False),
    }


@deal.pre(lambda raw: isinstance(raw, dict))
def parse_excluded(raw: dict[str, Any]) -> dict[str, Any] | None:
    """Parse excluded project dict, return None if invalid.

    >>> parse_excluded({"path": "/a", "excluded_at": "2026-01-01T00:00:00Z"})
    {'path': '/a', 'excluded_at': '2026-01-01T00:00:00Z', 'reason': ''}
    >>> parse_excluded({"path": "/a", "excluded_at": "2026-01-01", "reason": "test"})
    {'path': '/a', 'excluded_at': '2026-01-01', 'reason': 'test'}
    >>> parse_excluded({"path": "/a"})  # missing required fields

    """
    required = ("path", "excluded_at")
    if not all(k in raw for k in required):
        return None
    return {
        "path": raw["path"],
        "excluded_at": raw["excluded_at"],
        "reason": raw.get("reason", ""),
    }
