"""Config validation — standalone functions that check CortexConfig integrity."""
from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING, List

from cortex.types import CortexTier

if TYPE_CHECKING:
    from cortex.config.model import CortexConfig


def validate_config(config: "CortexConfig") -> List[str]:
    """
    Validate a CortexConfig instance and return a list of warning/error strings.

    Rules (#73):
    - Required fields must exist (they always do given dataclass defaults, but
      we check that they have non-sentinel values here).
    - ``extraction_model`` and ``reconciliation_model`` must be non-empty strings.
    - ``embedding_dim`` must be > 0.
    - The directory component of ``storage_db_path`` must exist (warns if not).
    - ``rate_limit_rpm`` must be >= 0; ``rate_limit_burst`` must be >= 0.

    Returns:
        List of warning strings.  Empty list = config is valid.
        Callers should log these as warnings; they do NOT raise exceptions so
        that graceful degradation is preserved.
    """
    issues: List[str] = []

    try:
        # --- Model name validation ---
        if not config.extraction_model or not config.extraction_model.strip():
            issues.append(
                "extraction_model is empty — LLM extraction will fail at runtime"
            )
        if not config.reconciliation_model or not config.reconciliation_model.strip():
            issues.append(
                "reconciliation_model is empty — reconciliation will fail at runtime"
            )

        # --- Embedding dimension ---
        if config.embedding_dim <= 0:
            issues.append(
                f"embedding_dim must be > 0, got {config.embedding_dim}"
            )

        # --- DB path directory existence ---
        db_path = Path(config.storage_db_path)
        parent = db_path.parent if not db_path.is_absolute() else db_path.parent
        # For relative paths resolve against cwd for the warning check
        resolved_parent = (Path.cwd() / db_path).parent if not db_path.is_absolute() else parent
        if not resolved_parent.exists():
            issues.append(
                f"db_path directory does not exist: '{resolved_parent}' "
                f"(from storage_db_path='{config.storage_db_path}'). "
                "Database cannot be created until this directory exists."
            )

        # --- Rate limit sanity ---
        if config.rate_limit_rpm < 0:
            issues.append(
                f"rate_limit_rpm must be >= 0 (0 = disabled), got {config.rate_limit_rpm}"
            )
        if config.rate_limit_burst < 0:
            issues.append(
                f"rate_limit_burst must be >= 0, got {config.rate_limit_burst}"
            )

        # --- Extraction guardrails sanity ---
        if config.max_claims_per_extraction < 0:
            issues.append(
                f"max_claims_per_extraction must be >= 0 (0 = disabled), got {config.max_claims_per_extraction}"
            )
        if config.max_entities_per_extraction < 0:
            issues.append(
                f"max_entities_per_extraction must be >= 0 (0 = disabled), got {config.max_entities_per_extraction}"
            )
        if config.max_concurrent_extractions < 1:
            issues.append(
                f"max_concurrent_extractions must be >= 1, got {config.max_concurrent_extractions}"
            )

        # --- Token budget consistency ---
        if config.total_memory_token_budget < (
            config.cornerstone_token_budget + config.retrieval_token_budget
        ):
            issues.append(
                "total_memory_token_budget is less than cornerstone_token_budget + "
                "retrieval_token_budget — memory context will be silently truncated"
            )

        # --- Working memory validation (audit fix) ---
        if config.working_memory_max_items < 1:
            issues.append(
                f"working_memory_max_items must be >= 1, got {config.working_memory_max_items}"
            )
        if config.working_memory_default_ttl_seconds < 0:
            issues.append(
                f"working_memory_default_ttl_seconds must be >= 0, got {config.working_memory_default_ttl_seconds}"
            )

        # --- Token budget fraction validation (audit fix) ---
        for frac_name in (
            "token_budget_memory_fraction",
            "token_budget_user_fraction",
            "token_budget_reserve_fraction",
        ):
            frac_val = getattr(config, frac_name, None)
            if frac_val is not None and (frac_val < 0.0 or frac_val > 1.0):
                issues.append(
                    f"{frac_name} must be in [0.0, 1.0], got {frac_val}"
                )

        # --- Promotion scoring validation (audit fix C-HIGH-3) ---
        promotion_weight_sum = (
            config.promotion_weight_evidence_strength
            + config.promotion_weight_cross_session_frequency
            + config.promotion_weight_user_explicitness
            + config.promotion_weight_retrieval_utility
            + config.promotion_weight_temporal_stability
            + config.promotion_weight_contradiction_absence
        )
        if abs(promotion_weight_sum - 1.0) > 0.01:
            issues.append(
                f"promotion weights must sum to 1.0 (±0.01), got {promotion_weight_sum:.4f}"
            )
        if not (0.0 <= config.promotion_threshold <= 1.0):
            issues.append(
                f"promotion_threshold must be in [0.0, 1.0], got {config.promotion_threshold}"
            )
        if config.promotion_min_sessions < 1:
            issues.append(
                f"promotion_min_sessions must be >= 1, got {config.promotion_min_sessions}"
            )
    except (TypeError, AttributeError):
        # Config is not a real CortexConfig (e.g. MagicMock or _FakeConfig in tests).
        # validate_config is a best-effort guard — never block startup.
        pass

    return issues


def is_full_tier(config: "CortexConfig") -> bool:
    """Return True if the config specifies the FULL deployment tier.

    Use this as the canonical feature gate throughout the codebase instead of
    comparing config.tier directly — it reads more clearly at call sites.

    Args:
        config: A CortexConfig instance.

    Returns:
        True when config.tier == CortexTier.FULL, False otherwise.
    """
    return config.tier == CortexTier.FULL
