"""Secure configuration management for trax project.

This module provides encrypted storage for API keys and other sensitive data,
along with validation functions for file paths and URLs.
"""

import json
import os
import re
from pathlib import Path
from typing import Optional

from cryptography.fernet import Fernet

from src.logging import get_logger

logger = get_logger(__name__)


class SecureConfig:
    """Secure configuration management with encrypted storage."""

    def __init__(self, config_path: Path = Path("~/.trax/config.json").expanduser()):
        """Initialize secure configuration.

        Args:
            config_path: Path to the encrypted configuration file
        """
        self.config_path = config_path
        self.config_dir = config_path.parent
        self.key_path = self.config_dir / "key.bin"
        self.fernet = None

        # Ensure config directory exists
        self.config_dir.mkdir(parents=True, exist_ok=True)

        # Initialize encryption key
        self._init_encryption()

    def _init_encryption(self) -> None:
        """Initialize or load encryption key."""
        if not self.key_path.exists():
            # Generate new key
            key = Fernet.generate_key()
            with open(self.key_path, "wb") as f:
                f.write(key)
            # Set permissions to owner-only
            os.chmod(self.key_path, 0o600)
            logger.info(f"Generated new encryption key: {self.key_path}")

        # Load key
        with open(self.key_path, "rb") as f:
            key = f.read()
        self.fernet = Fernet(key)
        logger.debug("Encryption key loaded successfully")

    def get_api_key(self, service: str) -> Optional[str]:
        """Get API key for specified service.

        Args:
            service: Name of the service (e.g., 'whisper', 'deepseek')

        Returns:
            API key if found, None otherwise
        """
        if not self.config_path.exists():
            return None

        try:
            with open(self.config_path, "rb") as f:
                encrypted_data = f.read()

            data = json.loads(self.fernet.decrypt(encrypted_data).decode())
            return data.get("api_keys", {}).get(service)
        except Exception as e:
            logger.error(f"Error reading API key for {service}: {str(e)}")
            return None

    def set_api_key(self, service: str, key: str) -> bool:
        """Set API key for specified service.

        Args:
            service: Name of the service
            key: API key value

        Returns:
            True if successful, False otherwise
        """
        try:
            # Load existing config or create new one
            if self.config_path.exists():
                with open(self.config_path, "rb") as f:
                    encrypted_data = f.read()
                data = json.loads(self.fernet.decrypt(encrypted_data).decode())
            else:
                data = {}

            # Update API key
            if "api_keys" not in data:
                data["api_keys"] = {}
            data["api_keys"][service] = key

            # Encrypt and save
            encrypted_data = self.fernet.encrypt(json.dumps(data).encode())
            with open(self.config_path, "wb") as f:
                f.write(encrypted_data)

            # Set permissions to owner-only
            os.chmod(self.config_path, 0o600)

            logger.info(f"API key for {service} stored securely")
            return True
        except Exception as e:
            logger.error(f"Error setting API key for {service}: {str(e)}")
            return False

    def list_services(self) -> list[str]:
        """List all services with stored API keys.

        Returns:
            List of service names
        """
        if not self.config_path.exists():
            return []

        try:
            with open(self.config_path, "rb") as f:
                encrypted_data = f.read()

            data = json.loads(self.fernet.decrypt(encrypted_data).decode())
            return list(data.get("api_keys", {}).keys())
        except Exception as e:
            logger.error(f"Error listing services: {str(e)}")
            return []

    def remove_api_key(self, service: str) -> bool:
        """Remove API key for specified service.

        Args:
            service: Name of the service

        Returns:
            True if successful, False otherwise
        """
        try:
            if not self.config_path.exists():
                return True

            with open(self.config_path, "rb") as f:
                encrypted_data = f.read()
            data = json.loads(self.fernet.decrypt(encrypted_data).decode())

            # Remove API key
            if "api_keys" in data and service in data["api_keys"]:
                del data["api_keys"][service]

            # Encrypt and save
            encrypted_data = self.fernet.encrypt(json.dumps(data).encode())
            with open(self.config_path, "wb") as f:
                f.write(encrypted_data)

            logger.info(f"API key for {service} removed")
            return True
        except Exception as e:
            logger.error(f"Error removing API key for {service}: {str(e)}")
            return False


def validate_path(path: str) -> bool:
    """Validate file path to prevent directory traversal.

    Args:
        path: File path to validate

    Returns:
        True if path is safe, False otherwise
    """
    if not path or not path.strip():
        return False

    # Handle special cases
    if path in [".", ".."]:
        return False

    # Expand user path and convert to absolute path
    expanded_path = os.path.expanduser(path)
    abs_path = os.path.abspath(expanded_path)

    # Check for suspicious patterns
    if re.search(r'\.\.|/tmp|/etc|/var|/root|/home', abs_path):
        return False

    # Ensure path is within allowed directories
    allowed_dirs = [
        os.path.expanduser("~/Documents"),
        os.path.expanduser("~/Downloads"),
        os.path.expanduser("~/.trax"),
        os.path.expanduser("~/Desktop"),
        os.path.expanduser("~/Music"),
        os.path.expanduser("~/Videos"),
    ]

    for allowed_dir in allowed_dirs:
        if abs_path.startswith(allowed_dir):
            return True

    # Allow current working directory for relative paths
    cwd = os.getcwd()
    if abs_path.startswith(cwd):
        return True

    return False


def validate_youtube_url(url: str) -> bool:
    """Validate YouTube URL to prevent malicious URLs.

    Args:
        url: URL to validate

    Returns:
        True if URL is a valid YouTube URL, False otherwise
    """
    if not url:
        return False

    # YouTube URL patterns - allow subdomains
    youtube_regex = r'^(https?://)?([a-zA-Z0-9-]+\.)?(youtube\.com|youtu\.be)/.+$'
    return bool(re.match(youtube_regex, url))


def sanitize_filename(filename: str) -> str:
    """Sanitize filename to prevent path traversal and other attacks.

    Args:
        filename: Original filename

    Returns:
        Sanitized filename
    """
    if not filename or not filename.strip():
        return "unnamed_file"

    # Remove path separators and other dangerous characters
    sanitized = re.sub(r'[<>:"/\\|?*]', '_', filename)
    
    # Remove leading/trailing dots and spaces
    sanitized = sanitized.strip('. ')
    
    # Limit length while preserving extension
    if len(sanitized) > 255:
        # Try to preserve extension
        name, ext = os.path.splitext(sanitized)
        if ext:
            max_name_length = 255 - len(ext)
            sanitized = name[:max_name_length] + ext
        else:
            sanitized = sanitized[:255]
    
    # Ensure it's not empty
    if not sanitized:
        sanitized = "unnamed_file"
    
    return sanitized
