"""Policy configuration: Load capability policies from files (YAML/JSON).

Supports:
- YAML and JSON formats
- Predefined policy templates
- Custom capability definitions
- Policy inheritance and composition
- Environment variable expansion
"""

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

from rpipe.capability import (
    Capability,
    CapabilitySet,
    OperationType,
    DEFAULT_READ_ONLY_CAPABILITIES,
    DEFAULT_WRITE_CAPABLE_CAPABILITIES,
    DEFAULT_RESTRICTIVE_CAPABILITIES,
)


class PolicyConfigError(Exception):
    """Error loading or parsing policy configuration."""

    pass


class PolicyConfigLoader:
    """Load capability policies from configuration files."""

    # Built-in policy templates
    BUILTIN_POLICIES = {
        "read_only": DEFAULT_READ_ONLY_CAPABILITIES,
        "write_capable": DEFAULT_WRITE_CAPABLE_CAPABILITIES,
        "restrictive": DEFAULT_RESTRICTIVE_CAPABILITIES,
    }

    @staticmethod
    def load_from_file(file_path: str) -> Dict[str, CapabilitySet]:
        """
        Load policies from a YAML or JSON file.

        File format (YAML):
            policies:
              custom_policy:
                capabilities:
                  - operation: read_file
                    resource: /var/log/**/*
                  - operation: execute_binary
                    resource: cat|grep|ls
                  - operation: write_stdout
                    resource: null

        File format (JSON):
            {
              "policies": {
                "custom_policy": {
                  "capabilities": [
                    {"operation": "read_file", "resource": "/var/log/**/*"},
                    ...
                  ]
                }
              }
            }

        Args:
            file_path: Path to YAML or JSON file

        Returns:
            Dict mapping policy names to CapabilitySet objects

        Raises:
            PolicyConfigError: If file not found or parsing fails
        """
        # Expand environment variables in path FIRST
        file_path = Path(os.path.expandvars(str(file_path)))

        if not file_path.exists():
            raise PolicyConfigError(f"Policy file not found: {file_path}")

        if file_path.suffix.lower() in [".yaml", ".yml"]:
            try:
                import yaml

                with open(file_path, "r") as f:
                    data = yaml.safe_load(f)
            except ImportError:
                raise PolicyConfigError(
                    "YAML support requires 'pyyaml' package. Install with: pip install pyyaml"
                )
            except Exception as e:
                raise PolicyConfigError(f"Failed to parse YAML file {file_path}: {e}") from e

        elif file_path.suffix.lower() == ".json":
            try:
                with open(file_path, "r") as f:
                    data = json.load(f)
            except Exception as e:
                raise PolicyConfigError(f"Failed to parse JSON file {file_path}: {e}") from e

        else:
            raise PolicyConfigError(
                f"Unsupported file format: {file_path.suffix} (expected .yaml or .json)"
            )

        return PolicyConfigLoader._parse_policies(data)

    @staticmethod
    def _parse_policies(data: Dict[str, Any]) -> Dict[str, CapabilitySet]:
        """
        Parse policies from loaded configuration data.

        Args:
            data: Loaded configuration dictionary

        Returns:
            Dict mapping policy names to CapabilitySet objects

        Raises:
            PolicyConfigError: If configuration is invalid
        """
        if not isinstance(data, dict):
            raise PolicyConfigError("Configuration must be a dictionary")

        policies_dict = data.get("policies")
        if not isinstance(policies_dict, dict):
            raise PolicyConfigError("'policies' key must contain a dictionary")

        result: Dict[str, CapabilitySet] = {}

        for policy_name, policy_def in policies_dict.items():
            if not isinstance(policy_name, str):
                raise PolicyConfigError(f"Policy name must be string, got {type(policy_name)}")

            # Check if it's a reference to built-in policy
            if isinstance(policy_def, str):
                if policy_def in PolicyConfigLoader.BUILTIN_POLICIES:
                    result[policy_name] = CapabilitySet(
                        PolicyConfigLoader.BUILTIN_POLICIES[policy_def]
                    )
                    continue
                else:
                    raise PolicyConfigError(f"Unknown built-in policy: {policy_def}")

            # Parse custom policy definition
            if not isinstance(policy_def, dict):
                raise PolicyConfigError(f"Policy definition must be dict or string, got {type(policy_def)}")

            capabilities_list = policy_def.get("capabilities", [])
            if not isinstance(capabilities_list, list):
                raise PolicyConfigError(f"'capabilities' must be a list, got {type(capabilities_list)}")

            capabilities = []
            for cap_def in capabilities_list:
                if not isinstance(cap_def, dict):
                    raise PolicyConfigError(f"Capability definition must be dict, got {type(cap_def)}")

                try:
                    operation_str = cap_def.get("operation")
                    resource = cap_def.get("resource")

                    if not operation_str:
                        raise PolicyConfigError("Capability missing 'operation'")

                    # Convert operation string to OperationType
                    try:
                        operation = OperationType[operation_str.upper()]
                    except KeyError:
                        valid_ops = [op.name.lower() for op in OperationType]
                        raise PolicyConfigError(
                            f"Invalid operation '{operation_str}'. Valid: {', '.join(valid_ops)}"
                        )

                    cap = Capability(operation=operation, resource_pattern=resource)
                    capabilities.append(cap)

                except PolicyConfigError:
                    raise
                except Exception as e:
                    raise PolicyConfigError(f"Failed to parse capability: {e}") from e

            if not capabilities:
                raise PolicyConfigError(f"Policy '{policy_name}' has no capabilities")

            result[policy_name] = CapabilitySet(capabilities)

        return result

    @staticmethod
    def load_policy(name: str, config_file: Optional[str] = None) -> CapabilitySet:
        """
        Load a specific policy by name.

        Searches in this order:
        1. Loaded configuration file (if provided)
        2. Built-in policies

        Args:
            name: Policy name
            config_file: Optional path to configuration file

        Returns:
            CapabilitySet for the policy

        Raises:
            PolicyConfigError: If policy not found
        """
        # Try built-in policies first
        if name in PolicyConfigLoader.BUILTIN_POLICIES:
            return CapabilitySet(PolicyConfigLoader.BUILTIN_POLICIES[name])

        # Try loaded configuration
        if config_file:
            policies = PolicyConfigLoader.load_from_file(config_file)
            if name in policies:
                return policies[name]

        valid_names = list(PolicyConfigLoader.BUILTIN_POLICIES.keys())
        if config_file:
            try:
                custom_policies = PolicyConfigLoader.load_from_file(config_file)
                valid_names.extend(custom_policies.keys())
            except:
                pass

        raise PolicyConfigError(
            f"Policy '{name}' not found. Valid policies: {', '.join(valid_names)}"
        )
