"""Audit agent-level configurations for security issues.

Detects config shadows, plaintext keys in models.json, and raw key usage.
"""

import json
from pathlib import Path

from clawguard.models import Finding, Severity
from clawguard.patterns import ENV_VAR_PATTERN, PROVIDER_ENV_MAP
from clawguard.utils import parse_config, scan_file_for_keys


def check_agents(openclaw_path: Path) -> list[Finding]:
    """Check all agent configs for security issues."""
    findings = []

    agents_dir = openclaw_path / "agents"
    if not agents_dir.exists() or not agents_dir.is_dir():
        return findings

    # Read global config model for shadow detection
    global_config = parse_config(openclaw_path / "openclaw.json")
    global_model = _get_global_model(global_config)

    agent_count = 0
    issue_count = 0

    for agent_dir in agents_dir.iterdir():
        if not agent_dir.is_dir():
            continue

        models_json = agent_dir / "agent" / "models.json"
        if not models_json.exists():
            continue

        agent_count += 1
        agent_name = agent_dir.name

        # Check 1: Plaintext API keys in models.json
        # models.json is auto-generated by OpenClaw from openclaw.json on every gateway
        # startup, resolving env var refs like ${OPENAI_API_KEY} into actual keys.
        # If openclaw.json uses env var references, this is expected behavior (INFO).
        key_hits = scan_file_for_keys(models_json)
        openclaw_uses_env_refs = (
            _check_openclaw_models_use_env_refs(openclaw_path) if key_hits else False
        )
        if key_hits:
            if openclaw_uses_env_refs:
                # openclaw.json correctly uses env var refs; models.json is auto-generated
                findings.append(Finding(
                    severity=Severity.INFO,
                    title=f"Agent '{agent_name}': models.json contains resolved keys (auto-generated)",
                    details=[
                        "models.json contains resolved API keys, but openclaw.json uses ${ENV_VAR} references",
                        "OpenClaw regenerates models.json from openclaw.json on every gateway startup",
                        "This is expected behavior — no action needed",
                    ],
                    category="agents",
                ))
            else:
                issue_count += 1
                details = [f"Line {ln}: {name} ({masked})" for name, masked, ln in key_hits[:5]]
                findings.append(Finding(
                    severity=Severity.CRITICAL,
                    title=f"Agent '{agent_name}' has plaintext API keys in models.json",
                    details=details,
                    fix=f'Replace raw keys with env var refs: "apiKey": "${{{PROVIDER_ENV_MAP["anthropic"]}}}"',
                    category="agents",
                ))

        # Check 2: HIGH - models.json uses raw keys instead of ${ENV_VAR} references
        # Skip this check if openclaw.json already uses env var refs (models.json is auto-generated)
        if not openclaw_uses_env_refs:
            try:
                content = models_json.read_text(errors="ignore")
                has_env_refs = bool(ENV_VAR_PATTERN.search(content))
                if key_hits and not has_env_refs:
                    findings.append(Finding(
                        severity=Severity.HIGH,
                        title=f"Agent '{agent_name}' uses raw keys instead of env var references",
                        details=[
                            "models.json contains no ${ENV_VAR} references",
                            "All API keys should use environment variable substitution",
                        ],
                        fix='Run "clawguard migrate-env" to auto-migrate keys to .env',
                        category="agents",
                    ))
                    issue_count += 1
            except (PermissionError, FileNotFoundError):
                pass

        # Check 3: MEDIUM - Config shadow detection
        if global_model:
            agent_model = _get_agent_model(models_json)
            if agent_model and agent_model != global_model:
                findings.append(Finding(
                    severity=Severity.MEDIUM,
                    title=f"Agent '{agent_name}' model differs from global config",
                    details=[
                        f"Global model: {global_model}",
                        f"Agent model: {agent_model}",
                        "This agent overrides the global model setting (config shadow)",
                    ],
                    fix="Verify this is intentional. Remove agent-level model to use global setting.",
                    category="agents",
                ))
                issue_count += 1

    if agent_count > 0 and issue_count == 0:
        findings.append(Finding(
            severity=Severity.INFO,
            title=f"{agent_count} agent config(s) scanned - no issues found",
            category="agents",
        ))

    return findings


def _check_openclaw_models_use_env_refs(openclaw_path: Path) -> bool:
    """Check if openclaw.json's models.providers section uses ${ENV_VAR} references.

    Returns True if at least one provider uses env var substitution for its API key.
    """
    try:
        config_path = openclaw_path / "openclaw.json"
        data = json.loads(config_path.read_text(errors="ignore"))
        models = data.get("models", {})
        if not models:
            return False
        providers = models.get("providers", {})
        if not providers:
            return False

        # Check if any provider's apiKey (or key) uses ${ENV_VAR} syntax
        for provider in providers.values():
            if not isinstance(provider, dict):
                continue
            api_key = str(provider.get("apiKey") or provider.get("key") or "")
            if ENV_VAR_PATTERN.search(api_key):
                return True
    except (json.JSONDecodeError, PermissionError, FileNotFoundError, TypeError, AttributeError):
        pass
    return False


def _get_global_model(config: dict | None) -> str | None:
    """Extract the default model from global config."""
    if not config:
        return None
    try:
        models = config.get("models", {})
        if not models:
            return None
        default_model = models.get("default")
        if default_model:
            return str(default_model)
        providers = models.get("providers", {})
        for provider in providers.values():
            if isinstance(provider, dict) and provider.get("model"):
                return str(provider["model"])
    except (AttributeError, TypeError):
        pass
    return None


def _get_agent_model(models_json: Path) -> str | None:
    """Extract the model from an agent's models.json."""
    try:
        data = json.loads(models_json.read_text(errors="ignore"))
        if data.get("model"):
            return str(data["model"])
        if data.get("default"):
            return str(data["default"])
        providers = data.get("providers", {})
        for provider in providers.values():
            if isinstance(provider, dict) and provider.get("model"):
                return str(provider["model"])
    except (json.JSONDecodeError, PermissionError, FileNotFoundError):
        pass
    return None
