"""Project-level configuration and rule suppression.

Supports:
- .oncecheckignore  — list of rule IDs to suppress (one per line)
- .oncecheckrc  — YAML config for severity overrides, disabled rules, etc.
"""

from __future__ import annotations

import logging
from pathlib import Path
from typing import Any, Dict, List, Optional, Set

import yaml

logger = logging.getLogger("oncecheck")

_VALID_SEVERITIES = {"FAIL", "WARN", "INFO"}


def _load_ignore_file(project_root: Path) -> Set[str]:
    """Load rule IDs from .oncecheckignore file."""
    ignore_path = project_root / ".oncecheckignore"
    if not ignore_path.exists():
        return set()

    ignored: Set[str] = set()
    try:
        for line in ignore_path.read_text().splitlines():
            line = line.strip()
            # Skip empty lines and comments
            if not line or line.startswith("#"):
                continue
            ignored.add(line)
    except OSError:
        pass

    if ignored:
        logger.info("Loaded %d suppressed rules from .oncecheckignore", len(ignored))

    return ignored


def _load_rc_file(project_root: Path) -> Dict[str, Any]:
    """Load .oncecheckrc YAML config."""
    for name in (".oncecheckrc", ".oncecheckrc.yaml", ".oncecheckrc.yml"):
        rc_path = project_root / name
        if rc_path.exists():
            try:
                data = yaml.safe_load(rc_path.read_text()) or {}
                logger.info("Loaded config from %s", name)
                return data
            except (yaml.YAMLError, OSError) as exc:
                logger.warning("Failed to parse %s: %s", name, exc)
                return {}
    return {}


class ScanConfig:
    """Resolved scan configuration for a project."""

    def __init__(self, project_root: Path) -> None:
        self.project_root = project_root
        self._rc = _load_rc_file(project_root)
        self._ignored = _load_ignore_file(project_root)

        # Merge disabled_rules from rc into ignored set
        rc_disabled = self._rc.get("disabled_rules", [])
        if isinstance(rc_disabled, list):
            self._ignored.update(rc_disabled)

        # Severity overrides: { "IOS-SEC-001": "WARN" }
        self._severity_overrides: Dict[str, str] = {}
        rc_overrides = self._rc.get("severity_overrides", {})
        if isinstance(rc_overrides, dict):
            for rule_id, severity in rc_overrides.items():
                if severity in _VALID_SEVERITIES:
                    self._severity_overrides[rule_id] = severity
                else:
                    logger.warning(
                        "Invalid severity override for %s: %s (must be FAIL/WARN/INFO)",
                        rule_id, severity,
                    )

        # Fail-on threshold from config (CLI flag takes precedence)
        self.fail_on: Optional[str] = self._rc.get("fail_on")
        if self.fail_on and self.fail_on not in ("FAIL", "WARN"):
            logger.warning("Invalid fail_on in config: %s", self.fail_on)
            self.fail_on = None

    @property
    def ignored_rules(self) -> Set[str]:
        return self._ignored

    @property
    def severity_overrides(self) -> Dict[str, str]:
        return self._severity_overrides

    def is_suppressed(self, rule_id: str) -> bool:
        """Check if a rule ID is suppressed."""
        return rule_id in self._ignored

    def get_severity(self, rule_id: str, default: str) -> str:
        """Get the effective severity for a rule (with overrides applied)."""
        return self._severity_overrides.get(rule_id, default)

    def filter_findings(self, findings: List[Any]) -> List[Any]:
        """Filter out suppressed findings and apply severity overrides."""
        filtered = []
        suppressed_count = 0
        for finding in findings:
            if self.is_suppressed(finding.rule_id):
                suppressed_count += 1
                continue
            # Apply severity override
            if finding.rule_id in self._severity_overrides:
                finding.severity = self._severity_overrides[finding.rule_id]
            filtered.append(finding)

        if suppressed_count:
            logger.info("Suppressed %d findings via ignore rules", suppressed_count)

        return filtered
