"""
Secret access control and distribution system.
Provides secure ways for applications to consume rotated secrets.
"""

import json
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any
from pathlib import Path
from secret_rotator.utils.logger import logger
from secret_rotator.encryption_manager import EncryptionManager, SecretMasker


class SecretAccessPolicy:
    """Define who/what can access which secrets"""

    def __init__(self):
        self.policies: Dict[str, Dict[str, Any]] = {}

    def add_policy(
        self,
        secret_id: str,
        allowed_services: List[str],
        allowed_ips: Optional[List[str]] = None,
        expiry_hours: Optional[int] = None,
    ):
        """Add an access policy for a secret"""
        self.policies[secret_id] = {
            "allowed_services": allowed_services,
            "allowed_ips": allowed_ips or [],
            "expiry_hours": expiry_hours,
            "created_at": datetime.now().isoformat(),
        }

    def can_access(
        self, secret_id: str, service_name: str, ip_address: Optional[str] = None
    ) -> bool:
        """Check if a service can access a secret"""
        policy = self.policies.get(secret_id)
        if not policy:
            return False

        # Check service name
        if service_name not in policy["allowed_services"]:
            return False

        # Check IP if specified
        if policy["allowed_ips"] and ip_address:
            if ip_address not in policy["allowed_ips"]:
                return False

        # Check expiry
        if policy["expiry_hours"]:
            created = datetime.fromisoformat(policy["created_at"])
            expiry = created + timedelta(hours=policy["expiry_hours"])
            if datetime.now() > expiry:
                return False

        return True


class SecretDistributionMethod:
    """Base class for different secret distribution methods"""

    def distribute(self, secret_id: str, secret_value: str, metadata: Dict):
        """Distribute the secret to the target system"""
        raise NotImplementedError


class EnvironmentVariableDistributor(SecretDistributionMethod):
    """
    Distribute secrets as environment variables.
    Creates a .env file that applications can source.
    """

    def __init__(self, env_file: str = ".env.secrets"):
        self.env_file = Path(env_file)

    def distribute(self, secret_id: str, secret_value: str, metadata: Dict):
        """Write secret to .env file"""
        env_name = metadata.get("env_var_name", secret_id.upper())

        # Read existing content
        content = {}
        if self.env_file.exists():
            with open(self.env_file, "r") as f:
                for line in f:
                    if "=" in line and not line.startswith("#"):
                        key, value = line.strip().split("=", 1)
                        content[key] = value

        # Update secret
        content[env_name] = secret_value

        # Write back
        with open(self.env_file, "w") as f:
            f.write("# Auto-generated by Secret Rotation System\n")
            f.write(f"# Last updated: {datetime.now().isoformat()}\n\n")
            for key, value in content.items():
                f.write(f"{key}={value}\n")

        # Set restricted permissions
        import os

        os.chmod(self.env_file, 0o600)

        logger.info(f"Distributed secret {secret_id} to {self.env_file}")


class ConfigFileDistributor(SecretDistributionMethod):
    """
    Distribute secrets to application config files.
    Supports JSON, YAML, TOML formats.
    """

    def __init__(self, config_file: str, file_format: str = "json"):
        self.config_file = Path(config_file)
        self.file_format = file_format.lower()

    def distribute(self, secret_id: str, secret_value: str, metadata: Dict):
        """Update secret in config file"""
        config_path = metadata.get("config_path", secret_id)

        # Read existing config
        if self.config_file.exists():
            config = self._read_config()
        else:
            config = {}

        # Update secret using dot notation path
        self._set_nested_value(config, config_path, secret_value)

        # Write back
        self._write_config(config)

        logger.info(f"Distributed secret {secret_id} to {self.config_file}")

    def _read_config(self) -> Dict:
        """Read config file based on format"""
        with open(self.config_file, "r") as f:
            if self.file_format == "json":
                return json.load(f)
            elif self.file_format == "yaml":
                import yaml

                return yaml.safe_load(f)
            elif self.file_format == "toml":
                import toml

                return toml.load(f)
        return {}

    def _write_config(self, config: Dict):
        """Write config file based on format"""
        with open(self.config_file, "w") as f:
            if self.file_format == "json":
                json.dump(config, f, indent=2)
            elif self.file_format == "yaml":
                import yaml

                yaml.dump(config, f, default_flow_style=False)
            elif self.file_format == "toml":
                import toml

                toml.dump(config, f)

    def _set_nested_value(self, d: Dict, path: str, value: Any):
        """Set a nested dictionary value using dot notation"""
        keys = path.split(".")
        for key in keys[:-1]:
            d = d.setdefault(key, {})
        d[keys[-1]] = value


class SecretAPIDistributor(SecretDistributionMethod):
    """
    Distribute secrets via HTTP API endpoint.
    Applications can pull secrets from this endpoint.
    """

    def __init__(self, api_url: str, auth_token: str):
        self.api_url = api_url
        self.auth_token = auth_token

    def distribute(self, secret_id: str, secret_value: str, metadata: Dict):
        """Push secret to API endpoint"""
        import requests

        headers = {"Authorization": f"Bearer {self.auth_token}", "Content-Type": "application/json"}

        payload = {
            "secret_id": secret_id,
            "value": secret_value,
            "rotated_at": datetime.now().isoformat(),
            "metadata": metadata,
        }

        try:
            response = requests.post(
                f"{self.api_url}/secrets/{secret_id}", json=payload, headers=headers, timeout=10
            )
            response.raise_for_status()
            logger.info(f"Distributed secret {secret_id} to API endpoint")
        except Exception as e:
            logger.error(f"Failed to distribute secret via API: {e}")
            raise


class SecretAccessAudit:
    """Audit log for secret access events"""

    def __init__(self, audit_file: str = "logs/secret_access.log"):
        self.audit_file = Path(audit_file)
        self.audit_file.parent.mkdir(parents=True, exist_ok=True)

    def log_access(
        self,
        secret_id: str,
        service_name: str,
        action: str,
        success: bool,
        ip_address: Optional[str] = None,
    ):
        """Log a secret access event"""
        event = {
            "timestamp": datetime.now().isoformat(),
            "secret_id": secret_id,
            "service": service_name,
            "action": action,
            "success": success,
            "ip_address": ip_address,
        }

        with open(self.audit_file, "a") as f:
            f.write(json.dumps(event) + "\n")

    def get_access_history(self, secret_id: Optional[str] = None, hours: int = 24) -> List[Dict]:
        """Get access history for a secret"""
        if not self.audit_file.exists():
            return []

        cutoff = datetime.now() - timedelta(hours=hours)
        events = []

        with open(self.audit_file, "r") as f:
            for line in f:
                try:
                    event = json.loads(line)
                    event_time = datetime.fromisoformat(event["timestamp"])

                    if event_time > cutoff:
                        if secret_id is None or event["secret_id"] == secret_id:
                            events.append(event)
                except BaseException:
                    continue

        return events


class SecretAccessManager:
    """
    Central manager for secret access and distribution.
    Handles encryption, access control, and distribution.
    """

    def __init__(self, encryption_manager: EncryptionManager):
        self.encryption = encryption_manager
        self.access_policy = SecretAccessPolicy()
        self.audit = SecretAccessAudit()
        self.distributors: Dict[str, SecretDistributionMethod] = {}

    def register_distributor(self, name: str, distributor: SecretDistributionMethod):
        """Register a secret distribution method"""
        self.distributors[name] = distributor
        logger.info(f"Registered distributor: {name}")

    def get_secret(
        self,
        secret_id: str,
        service_name: str,
        ip_address: Optional[str] = None,
        decrypt: bool = True,
    ) -> Optional[str]:
        """
        Get a secret with access control.
        This is the secure way for applications to retrieve secrets.
        """
        # Check access policy
        if not self.access_policy.can_access(secret_id, service_name, ip_address):
            self.audit.log_access(secret_id, service_name, "get", False, ip_address)
            logger.warning(f"Access denied for {service_name} to secret {secret_id}")
            return None

        try:
            # Retrieve encrypted secret from provider
            # (This would integrate with your provider system)
            encrypted_value = self._retrieve_from_provider(secret_id)

            if decrypt:
                secret_value = self.encryption.decrypt(encrypted_value)
            else:
                secret_value = encrypted_value

            self.audit.log_access(secret_id, service_name, "get", True, ip_address)
            return secret_value

        except Exception as e:
            self.audit.log_access(secret_id, service_name, "get", False, ip_address)
            logger.error(f"Failed to retrieve secret {secret_id}: {e}")
            return None

    def distribute_secret(
        self,
        secret_id: str,
        secret_value: str,
        distribution_methods: List[str],
        metadata: Optional[Dict] = None,
    ):
        """
        Distribute a secret using configured methods.
        Called after rotation to push secrets to applications.
        """
        metadata = metadata or {}

        for method_name in distribution_methods:
            distributor = self.distributors.get(method_name)
            if distributor:
                try:
                    distributor.distribute(secret_id, secret_value, metadata)
                    logger.info(f"Distributed {secret_id} via {method_name}")
                except Exception as e:
                    logger.error(f"Distribution via {method_name} failed: {e}")
            else:
                logger.warning(f"Unknown distribution method: {method_name}")

    def get_masked_secret(self, secret_id: str, service_name: str) -> Optional[str]:
        """Get a masked version of the secret for display purposes"""
        secret = self.get_secret(secret_id, service_name)
        if secret:
            return SecretMasker.mask_secret(secret)
        return None

    def _retrieve_from_provider(self, secret_id: str) -> str:
        """
        Retrieve encrypted secret from storage provider.
        This should integrate with your existing provider system.
        """
        # Placeholder - integrate with your provider system
        pass


class SecretRotationHook:
    """
    Hooks that execute before/after rotation.
    Useful for custom validation, notifications, or distribution.
    """

    def __init__(self):
        self.pre_rotation_hooks = []
        self.post_rotation_hooks = []

    def register_pre_rotation(self, func):
        """Register a hook to run before rotation"""
        self.pre_rotation_hooks.append(func)

    def register_post_rotation(self, func):
        """Register a hook to run after rotation"""
        self.post_rotation_hooks.append(func)

    def execute_pre_rotation(self, secret_id: str, old_value: str) -> bool:
        """Execute all pre-rotation hooks"""
        for hook in self.pre_rotation_hooks:
            try:
                result = hook(secret_id, old_value)
                if result is False:
                    logger.warning(f"Pre-rotation hook failed for {secret_id}")
                    return False
            except Exception as e:
                logger.error(f"Pre-rotation hook error: {e}")
                return False
        return True

    def execute_post_rotation(self, secret_id: str, new_value: str):
        """Execute all post-rotation hooks"""
        for hook in self.post_rotation_hooks:
            try:
                hook(secret_id, new_value)
            except Exception as e:
                logger.error(f"Post-rotation hook error: {e}")
