"""
StackSage Configuration Module

Handles loading and validation of stacksage.yml configuration file.
Provides defaults and allows customer customization of:
- Resource exclusions (by ID, tag, region)
- Detector-specific thresholds
- Reporting filters
- Tag governance rules
"""

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

import yaml


class StackSageConfig:
    """
    Configuration manager for StackSage audit customization.

    Loads stacksage.yml from workspace root and merges with sensible defaults.
    Config file is optional - if not present, uses defaults.
    """

    # Default configuration values
    DEFAULTS = {
        "exclusions": {
            "resources": [],
            "tags": [],
            "regions": [],
            "detectors": [],
        },
        "thresholds": {
            "nat_gateway": {
                "idle_threshold_gb_per_day": 1.0,
            },
            "ebs": {
                "unattached_days": 7,
                "performance_utilization": 0.20,
            },
            "lambda": {
                "low_invocation_threshold": 10,
            },
            "rds": {
                "low_connection_count": 10,
            },
            "elb": {
                "idle_request_threshold_per_day": 10.0,
                "min_days_old": 7,
            },
            "lb": {
                "min_days_old": 7,
            },
            "ec2": {
                "stopped_days": 14,
                "idle_cpu_threshold_pct": 5.0,
            },
            "snapshot": {
                "max_age_days": 180,
            },
            "dynamodb": {
                "idle_lookback_days": 30,
            },
            "elasticache": {
                "idle_hit_rate_threshold_pct": 5.0,
                "idle_connections_threshold": 2.0,
            },
            "cloudfront": {
                "idle_requests_threshold": 100.0,
                "idle_lookback_days": 30,
            },
            "route53": {
                "idle_queries_threshold": 100.0,
                "idle_lookback_days": 30,
            },
            "severity": {
                "high_cost_threshold": 50,
                "medium_cost_threshold": 20,
            },
        },
        "reporting": {
            "min_savings_usd": 0,
            "severity_filter": [],
            "exclude_finding_types": [],
        },
        "tag_governance": {
            "required_tags": [],
            "cost_grouping": [],
            "audit_filter": {},
        },
    }

    def __init__(self, config_path: Optional[str] = None):
        """
        Initialize configuration.

        Args:
            config_path: Path to stacksage.yml. If None, searches current directory.
        """
        self.config_path = config_path
        self.config = self._load_config()
        self._validate_config()

    def _load_config(self) -> Dict[str, Any]:
        """Load config from file or use defaults."""
        # Start with defaults
        config = self._deep_copy(self.DEFAULTS)

        # Find config file
        config_file = self._find_config_file()
        if not config_file:
            return config

        # Load and merge user config
        try:
            with open(config_file, "r") as f:
                user_config = yaml.safe_load(f) or {}

            # Deep merge user config over defaults
            config = self._deep_merge(config, user_config)
        except Exception as e:
            # Log warning but continue with defaults
            print(f"Warning: Could not load {config_file}: {e}")
            print("Using default configuration.")

        return config

    def _find_config_file(self) -> Optional[Path]:
        """Find stacksage.yml in current directory or specified path."""
        if self.config_path:
            path = Path(self.config_path)
            if path.exists():
                return path
            return None

        # Look in current directory
        for filename in ["stacksage.yml", "stacksage.yaml", ".stacksage.yml"]:
            path = Path(filename)
            if path.exists():
                return path

        return None

    def _deep_copy(self, obj: Any) -> Any:
        """Deep copy a dict/list structure."""
        if isinstance(obj, dict):
            return {k: self._deep_copy(v) for k, v in obj.items()}
        elif isinstance(obj, list):
            return [self._deep_copy(item) for item in obj]
        else:
            return obj

    def _deep_merge(self, base: Dict, override: Dict) -> Dict:
        """Deep merge override into base dict."""
        result = base.copy()
        for key, value in override.items():
            if (
                key in result
                and isinstance(result[key], dict)
                and isinstance(value, dict)
            ):
                result[key] = self._deep_merge(result[key], value)
            else:
                result[key] = value
        return result

    def _validate_config(self):
        """Validate configuration structure and values."""
        # Validate thresholds are positive numbers
        thresholds = self.config.get("thresholds", {})
        for resource_type, settings in thresholds.items():
            if isinstance(settings, dict):
                for key, value in settings.items():
                    if isinstance(value, (int, float)) and value < 0:
                        raise ValueError(
                            f"Threshold {resource_type}.{key} must be positive, got {value}"
                        )

        # Validate reporting min_savings is non-negative
        min_savings = self.config.get("reporting", {}).get("min_savings_usd", 0)
        if min_savings < 0:
            raise ValueError(
                f"reporting.min_savings_usd must be non-negative, got {min_savings}"
            )

    # Exclusion checks
    def is_resource_excluded(self, resource_id: str) -> bool:
        """Check if a resource ID is excluded."""
        excluded_ids = self.config["exclusions"]["resources"]
        return resource_id in excluded_ids

    def is_region_excluded(self, region: str) -> bool:
        """Check if a region is excluded."""
        excluded_regions = self.config["exclusions"]["regions"]
        return region in excluded_regions

    def is_detector_excluded(self, detector_type: str) -> bool:
        """Check if a detector type is excluded."""
        excluded_detectors = self.config["exclusions"]["detectors"]
        return detector_type in excluded_detectors

    def is_excluded_by_tags(self, tags: List[Dict[str, str]]) -> bool:
        """
        Check if resource should be excluded based on tags.

        Args:
            tags: List of tag dicts with 'Key' and 'Value' fields (AWS format)

        Returns:
            True if resource matches any exclusion tag rule
        """
        exclusion_rules = self.config["exclusions"]["tags"]
        if not exclusion_rules:
            return False

        # Convert AWS tags to dict for easier lookup
        tag_dict = {}
        for tag in tags or []:
            if isinstance(tag, dict) and "Key" in tag:
                tag_dict[tag["Key"].lower()] = str(tag.get("Value", "")).lower()

        # Check each exclusion rule
        for rule in exclusion_rules:
            if isinstance(rule, dict):
                key = rule.get("key", "").lower()
                value = rule.get("value", "").lower()
                if key in tag_dict and (not value or tag_dict[key] == value):
                    return True

        return False

    # Threshold getters
    def get_threshold(self, resource_type: str, threshold_name: str) -> Any:
        """
        Get threshold value for a resource type.

        Args:
            resource_type: e.g., 'nat_gateway', 'ebs', 'lambda'
            threshold_name: e.g., 'idle_threshold_gb_per_day', 'unattached_days'

        Returns:
            Threshold value or default if not found
        """
        thresholds = self.config.get("thresholds", {})
        resource_thresholds = thresholds.get(resource_type, {})
        default_resource = self.DEFAULTS["thresholds"].get(resource_type, {})

        return resource_thresholds.get(
            threshold_name, default_resource.get(threshold_name)
        )

    def get_severity_thresholds(self) -> Dict[str, float]:
        """Get cost-based severity thresholds."""
        return self.config["thresholds"]["severity"]

    # Reporting filters
    def should_include_finding(self, finding: Dict[str, Any]) -> bool:
        """
        Check if finding should be included in report based on filters.

        Args:
            finding: Finding dictionary with type, severity, savings

        Returns:
            True if finding passes all filters
        """
        reporting = self.config["reporting"]

        # Check minimum savings threshold
        savings = finding.get("estimated_monthly_savings_usd", 0)
        if savings < reporting["min_savings_usd"]:
            return False

        # Check severity filter (if specified)
        severity_filter = reporting["severity_filter"]
        if severity_filter:
            finding_severity = finding.get("severity", "").lower()
            if finding_severity not in [s.lower() for s in severity_filter]:
                return False

        # Check finding type exclusions
        finding_type = finding.get("type", "")
        if finding_type in reporting["exclude_finding_types"]:
            return False

        return True

    # Tag governance
    def get_required_tags(self) -> List[str]:
        """Get list of required tag keys."""
        return self.config["tag_governance"]["required_tags"]

    def get_cost_grouping_tags(self) -> List[str]:
        """Get list of tag keys for cost grouping."""
        return self.config["tag_governance"]["cost_grouping"]

    def get_audit_filter(self) -> Dict[str, str]:
        """Get tag-based audit filter (e.g., only audit specific team)."""
        return self.config["tag_governance"]["audit_filter"]


def load_config(config_path: Optional[str] = None) -> StackSageConfig:
    """
    Convenience function to load configuration.

    Args:
        config_path: Optional path to config file

    Returns:
        StackSageConfig instance
    """
    return StackSageConfig(config_path)
