"""
VexaAuth Client
Main client class for interacting with VexaAuth API
"""

import requests
from typing import Dict, Optional, Any, List, Union
from .exceptions import (
    VexaAuthError,
    AuthenticationError,
    ValidationError,
    NetworkError,
    RateLimitError,
)


class VexaAuthClient:
    """
    VexaAuth API Client
    
    A modern, type-safe client for VexaAuth authentication system.
    
    Args:
        api_key (str): Your VexaAuth API key
        base_url (str, optional): Base URL for the API. Defaults to production.
        timeout (int, optional): Request timeout in seconds. Defaults to 30.
    
    Example:
        >>> client = VexaAuthClient(api_key="your_api_key")
        >>> result = client.verify_license("LICENSE-KEY-HERE")
        >>> print(result['valid'])
    """

    def __init__(
        self,
        api_key: str,
        base_url: str = "https://your-domain.com/api/v1",
        timeout: int = 30,
    ):
        """Initialize the VexaAuth client"""
        if not api_key:
            raise ValueError("API key is required")

        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self.session = requests.Session()
        self.session.headers.update(
            {
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
                "User-Agent": "VexaAuth-Python/1.0.0",
            }
        )

    def _request(
        self, method: str, endpoint: str, data: Optional[Dict] = None, params: Optional[Dict] = None
    ) -> Dict[str, Any]:
        """Make an HTTP request to the API"""
        url = f"{self.base_url}/{endpoint.lstrip('/')}"
        
        # Handle file uploads
        if data and 'files' in data:
            files = data.pop('files')
            response = self.session.request(
                method=method, url=url, data=data, files=files, params=params, timeout=self.timeout
            )
        else:
            response = self.session.request(
                method=method, url=url, json=data, params=params, timeout=self.timeout
            )

        # Handle rate limiting
        if response.status_code == 429:
            raise RateLimitError("Rate limit exceeded. Please try again later.")

        # Handle authentication errors
        if response.status_code == 401:
            raise AuthenticationError("Invalid API key or unauthorized access")

        # Handle validation errors
        if response.status_code == 400:
            error_msg = response.json().get("error", "Validation failed")
            raise ValidationError(error_msg)

        # Handle other errors
        if not response.ok:
            raise VexaAuthError(
                f"API request failed: {response.status_code} - {response.text}"
            )

        return response.json()

    def login(self, username: str, password: str, hwid: Optional[str] = None) -> Dict[str, Any]:
        """
        Authenticate a user with username and password
        
        Args:
            username (str): User's username or email
            password (str): User's password
            hwid (str, optional): Hardware ID for device binding
        
        Returns:
            Dict containing user information and session token
        
        Raises:
            AuthenticationError: If credentials are invalid
            ValidationError: If input validation fails
        
        Example:
            >>> result = client.login("user@example.com", "password123")
            >>> print(result['token'])
        """
        data = {"username": username, "password": password}
        if hwid:
            data["hwid"] = hwid

        return self._request("POST", "/user/login", data)

    def verify_license(
        self, license_key: str, hwid: Optional[str] = None, session_id: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Verify a license key
        
        Args:
            license_key (str): The license key to verify
            hwid (str, optional): Hardware ID for device binding
            session_id (str, optional): Session ID for session-based validation
        
        Returns:
            Dict containing license validation result
        
        Example:
            >>> result = client.verify_license("XXXX-XXXX-XXXX-XXXX")
            >>> if result['valid']:
            ...     print("License is valid!")
        """
        data = {"license_key": license_key}
        if hwid:
            data["hwid"] = hwid
        if session_id:
            data["session_id"] = session_id

        return self._request("POST", "/license/validate", data)

    def activate_license(
        self, license_key: str, app_id: str, app_secret: str, hwid: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Activate a license
        
        Args:
            license_key (str): The license key to activate
            app_id (str): Application ID
            app_secret (str): Application secret
            hwid (str, optional): Hardware ID for device binding
        
        Returns:
            Dict containing license activation result
        
        Example:
            >>> result = client.activate_license("XXXX-XXXX-XXXX-XXXX", "app_123", "secret_123")
            >>> print(result['data']['session_id'])
        """
        data = {"license_key": license_key, "app_id": app_id, "app_secret": app_secret}
        if hwid:
            data["hwid"] = hwid

        return self._request("POST", "/license/activate", data)

    def deactivate_license(
        self, license_key: str, app_id: str, app_secret: str, 
        session_id: Optional[str] = None, hwid: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Deactivate a license
        
        Args:
            license_key (str): The license key to deactivate
            app_id (str): Application ID
            app_secret (str): Application secret
            session_id (str, optional): Session ID to deactivate
            hwid (str, optional): Hardware ID to deactivate
        
        Returns:
            Dict containing license deactivation result
        
        Example:
            >>> result = client.deactivate_license("XXXX-XXXX-XXXX-XXXX", "app_123", "secret_123")
            >>> print(result['message'])
        """
        data = {"license_key": license_key, "app_id": app_id, "app_secret": app_secret}
        if session_id:
            data["session_id"] = session_id
        if hwid:
            data["hwid"] = hwid

        return self._request("POST", "/license/deactivate", data)

    def validate_session(
        self, license_key: str, app_id: str, app_secret: str,
        session_id: Optional[str] = None, hwid: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Validate a session
        
        Args:
            license_key (str): The license key
            app_id (str): Application ID
            app_secret (str): Application secret
            session_id (str, optional): Session ID to validate
            hwid (str, optional): Hardware ID to validate
        
        Returns:
            Dict containing session validation result
        
        Example:
            >>> result = client.validate_session("XXXX-XXXX-XXXX-XXXX", "app_123", "secret_123", "session_123")
            >>> print(result['message'])
        """
        data = {"license_key": license_key, "app_id": app_id, "app_secret": app_secret}
        if session_id:
            data["session_id"] = session_id
        if hwid:
            data["hwid"] = hwid

        return self._request("POST", "/license/session", data)

    def get_user(self, user_id: str) -> Dict[str, Any]:
        """
        Get user information by ID
        
        Args:
            user_id (str): The user's unique identifier
        
        Returns:
            Dict containing user information
        
        Example:
            >>> user = client.get_user("user_123")
            >>> print(user['email'])
        """
        return self._request("GET", f"/user/{user_id}")

    def get_profile(self) -> Dict[str, Any]:
        """
        Get the current authenticated user's profile
        
        Returns:
            Dict containing profile information
        
        Example:
            >>> profile = client.get_profile()
            >>> print(profile['email'])
        """
        return self._request("GET", "/user/profile")

    def set_user_variable(self, key: str, value: str) -> Dict[str, Any]:
        """
        Set a user variable
        
        Args:
            key (str): Variable key
            value (str): Variable value
        
        Returns:
            Dict containing result of setting variable
        
        Example:
            >>> result = client.set_user_variable("theme", "dark")
            >>> print(result['message'])
        """
        data = {"key": key, "value": value}
        return self._request("POST", "/user/variables", data)

    def get_user_variable(self, key: str) -> Dict[str, Any]:
        """
        Get a user variable
        
        Args:
            key (str): Variable key
        
        Returns:
            Dict containing variable value
        
        Example:
            >>> result = client.get_user_variable("theme")
            >>> print(result['data']['value'])
        """
        params = {"key": key}
        return self._request("GET", "/user/variables", params=params)

    def get_all_user_variables(self) -> Dict[str, Any]:
        """
        Get all user variables
        
        Returns:
            Dict containing all user variables
        
        Example:
            >>> result = client.get_all_user_variables()
            >>> print(result['data']['variables'])
        """
        return self._request("GET", "/user/variables")

    def delete_user_variable(self, key: str) -> Dict[str, Any]:
        """
        Delete a user variable
        
        Args:
            key (str): Variable key
        
        Returns:
            Dict containing result of deleting variable
        
        Example:
            >>> result = client.delete_user_variable("theme")
            >>> print(result['message'])
        """
        data = {"key": key}
        return self._request("DELETE", "/user/variables", data)

    def ban_user(self, username: str, app_id: str, app_secret: str, reason: Optional[str] = None) -> Dict[str, Any]:
        """
        Ban a user
        
        Args:
            username (str): Username to ban
            app_id (str): Application ID
            app_secret (str): Application secret
            reason (str, optional): Reason for ban
        
        Returns:
            Dict containing ban result
        
        Example:
            >>> result = client.ban_user("baduser", "app_123", "secret_123", "Violated terms")
            >>> print(result['message'])
        """
        data = {"username": username, "app_id": app_id, "app_secret": app_secret, "action": "ban"}
        if reason:
            data["reason"] = reason

        return self._request("PUT", "/management/users", data)

    def unban_user(self, username: str, app_id: str, app_secret: str) -> Dict[str, Any]:
        """
        Unban a user
        
        Args:
            username (str): Username to unban
            app_id (str): Application ID
            app_secret (str): Application secret
        
        Returns:
            Dict containing unban result
        
        Example:
            >>> result = client.unban_user("baduser", "app_123", "secret_123")
            >>> print(result['message'])
        """
        data = {"username": username, "app_id": app_id, "app_secret": app_secret, "action": "unban"}
        return self._request("PUT", "/management/users", data)

    def enable_2fa(self) -> Dict[str, Any]:
        """
        Enable 2FA for current user
        
        Returns:
            Dict containing 2FA setup information
        
        Example:
            >>> result = client.enable_2fa()
            >>> print(result['data']['qr_code'])
        """
        data = {"action": "enable"}
        return self._request("POST", "/user/2fa", data)

    def verify_2fa(self, token: str) -> Dict[str, Any]:
        """
        Verify and complete 2FA setup
        
        Args:
            token (str): 2FA token from authenticator app
        
        Returns:
            Dict containing 2FA verification result
        
        Example:
            >>> result = client.verify_2fa("123456")
            >>> print(result['data']['recovery_codes'])
        """
        data = {"action": "verify", "token": token}
        return self._request("POST", "/user/2fa", data)

    def disable_2fa(self, token: str) -> Dict[str, Any]:
        """
        Disable 2FA for current user
        
        Args:
            token (str): 2FA token from authenticator app
        
        Returns:
            Dict containing 2FA disable result
        
        Example:
            >>> result = client.disable_2fa("123456")
            >>> print(result['message'])
        """
        data = {"action": "disable", "token": token}
        return self._request("POST", "/user/2fa", data)

    def use_recovery_code(self, code: str) -> Dict[str, Any]:
        """
        Use recovery code for 2FA
        
        Args:
            code (str): Recovery code
        
        Returns:
            Dict containing recovery code result
        
        Example:
            >>> result = client.use_recovery_code("ABCD-EFGH")
            >>> print(result['message'])
        """
        data = {"action": "recover", "token": code}
        return self._request("POST", "/user/2fa", data)

    def get_2fa_status(self) -> Dict[str, Any]:
        """
        Get 2FA status for current user
        
        Returns:
            Dict containing 2FA status information
        
        Example:
            >>> result = client.get_2fa_status()
            >>> print(result['data']['two_factor_enabled'])
        """
        return self._request("GET", "/user/2fa")

    def upgrade_license(
        self, license_key: str, app_id: str, app_secret: str,
        new_type: Optional[str] = None, extension_days: Optional[int] = None
    ) -> Dict[str, Any]:
        """
        Upgrade a license
        
        Args:
            license_key (str): The license key to upgrade
            app_id (str): Application ID
            app_secret (str): Application secret
            new_type (str, optional): New license type (trial, standard, premium, lifetime)
            extension_days (int, optional): Number of days to extend expiration
        
        Returns:
            Dict containing license upgrade result
        
        Example:
            >>> result = client.upgrade_license("XXXX-XXXX-XXXX-XXXX", "app_123", "secret_123", "premium", 30)
            >>> print(result['message'])
        """
        data = {"license_key": license_key, "app_id": app_id, "app_secret": app_secret}
        if new_type:
            data["new_type"] = new_type
        if extension_days:
            data["extension_days"] = extension_days

        return self._request("POST", "/license/upgrade", data)

    def redeem_license(
        self, license_key: str, app_id: str, app_secret: str, hwid: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Redeem a trial license
        
        Args:
            license_key (str): The trial license key to redeem
            app_id (str): Application ID
            app_secret (str): Application secret
            hwid (str, optional): Hardware ID for device binding
        
        Returns:
            Dict containing license redemption result
        
        Example:
            >>> result = client.redeem_license("XXXX-XXXX-XXXX-XXXX", "app_123", "secret_123")
            >>> print(result['message'])
        """
        data = {"license_key": license_key, "app_id": app_id, "app_secret": app_secret}
        if hwid:
            data["hwid"] = hwid

        return self._request("POST", "/license/redeem", data)

    def upload_file(
        self, file_path: str, app_id: str, app_secret: str,
        license_required: bool = False, allowed_licenses: Optional[List[str]] = None,
        download_limit: int = 0, expires_at: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Upload a file
        
        Args:
            file_path (str): Path to file to upload
            app_id (str): Application ID
            app_secret (str): Application secret
            license_required (bool): Whether license is required to download
            allowed_licenses (List[str], optional): Specific licenses allowed to download
            download_limit (int): Download limit (0 = unlimited)
            expires_at (str, optional): Expiration date (ISO format)
        
        Returns:
            Dict containing file upload result
        
        Example:
            >>> result = client.upload_file("myfile.zip", "app_123", "secret_123", license_required=True, download_limit=100)
            >>> print(result['data']['file_id'])
        """
        with open(file_path, 'rb') as f:
            files = {'file': f}
            data = {
                'app_id': app_id,
                'app_secret': app_secret,
                'license_required': str(license_required).lower(),
                'download_limit': str(download_limit)
            }
            
            if allowed_licenses:
                data['allowed_licenses'] = ','.join(allowed_licenses)
            if expires_at:
                data['expires_at'] = expires_at

            return self._request("POST", "/files/upload", {"files": files, **data})

    def download_file(
        self, file_id: str, app_id: str, app_secret: str,
        license_key: Optional[str] = None, hwid: Optional[str] = None
    ) -> requests.Response:
        """
        Download a file
        
        Args:
            file_id (str): File ID to download
            app_id (str): Application ID
            app_secret (str): Application secret
            license_key (str, optional): License key (if required)
            hwid (str, optional): Hardware ID (if required)
        
        Returns:
            requests.Response: File download response
        
        Example:
            >>> response = client.download_file("file_123", "app_123", "secret_123", "XXXX-XXXX-XXXX-XXXX")
            >>> with open("downloaded_file.zip", "wb") as f:
            ...     f.write(response.content)
        """
        params = {
            "file_id": file_id,
            "app_id": app_id,
            "app_secret": app_secret
        }
        
        if license_key:
            params["license_key"] = license_key
        if hwid:
            params["hwid"] = hwid

        url = f"{self.base_url}/files/download"
        response = self.session.get(url, params=params, stream=True)
        
        # Handle errors
        if response.status_code == 429:
            raise RateLimitError("Rate limit exceeded. Please try again later.")
        elif response.status_code == 401:
            raise AuthenticationError("Invalid credentials")
        elif response.status_code == 400:
            raise ValidationError("Validation failed")
        elif not response.ok:
            raise VexaAuthError(f"API request failed: {response.status_code} - {response.text}")
            
        return response

    def get_online_users(self, license_key: str, app_id: str, app_secret: str) -> Dict[str, Any]:
        """
        Get online users for a license
        
        Args:
            license_key (str): License key
            app_id (str): Application ID
            app_secret (str): Application secret
        
        Returns:
            Dict containing online users list
        
        Example:
            >>> result = client.get_online_users("XXXX-XXXX-XXXX-XXXX", "app_123", "secret_123")
            >>> print(result['data']['online_users'])
        """
        params = {
            "license_key": license_key,
            "app_id": app_id,
            "app_secret": app_secret
        }
        return self._request("GET", "/online-users", params=params)

    def connect_user(
        self, license_key: str, app_id: str, app_secret: str,
        user_id: str, username: Optional[str] = None, hwid: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Add user to online users list
        
        Args:
            license_key (str): License key
            app_id (str): Application ID
            app_secret (str): Application secret
            user_id (str): User ID
            username (str, optional): Username
            hwid (str, optional): Hardware ID
        
        Returns:
            Dict containing connection result
        
        Example:
            >>> result = client.connect_user("XXXX-XXXX-XXXX-XXXX", "app_123", "secret_123", "user_123", "john_doe")
            >>> print(result['message'])
        """
        data = {
            "license_key": license_key,
            "app_id": app_id,
            "app_secret": app_secret,
            "user_id": user_id
        }
        if username:
            data["username"] = username
        if hwid:
            data["hwid"] = hwid

        return self._request("POST", "/online-users", data)

    def disconnect_user(
        self, license_key: str, app_id: str, app_secret: str, user_id: str
    ) -> Dict[str, Any]:
        """
        Remove user from online users list
        
        Args:
            license_key (str): License key
            app_id (str): Application ID
            app_secret (str): Application secret
            user_id (str): User ID
        
        Returns:
            Dict containing disconnection result
        
        Example:
            >>> result = client.disconnect_user("XXXX-XXXX-XXXX-XXXX", "app_123", "secret_123", "user_123")
            >>> print(result['message'])
        """
        data = {
            "license_key": license_key,
            "app_id": app_id,
            "app_secret": app_secret,
            "user_id": user_id
        }
        return self._request("DELETE", "/online-users", data)

    def extend_license(self, license_key: str, days: int) -> Dict[str, Any]:
        """
        Extend a license expiration date
        
        Args:
            license_key (str): The license key to extend
            days (int): Number of days to extend
        
        Returns:
            Dict containing updated license information
        
        Example:
            >>> result = client.extend_license("XXXX-XXXX-XXXX-XXXX", 30)
            >>> print(result['new_expiry'])
        """
        data = {"license_key": license_key, "days": days}
        return self._request("POST", "/license/extend", data)

    def create_license(
        self,
        license_type: str = "standard",
        duration_days: Optional[int] = None,
        max_uses: int = 1,
    ) -> Dict[str, Any]:
        """
        Create a new license
        
        Args:
            license_type (str): Type of license (trial, standard, premium, lifetime)
            duration_days (int, optional): License duration in days
            max_uses (int): Maximum number of uses
        
        Returns:
            Dict containing new license information
        
        Example:
            >>> license = client.create_license("premium", duration_days=365)
            >>> print(license['license_key'])
        """
        data = {
            "type": license_type,
            "max_uses": max_uses,
        }
        if duration_days:
            data["duration_days"] = duration_days

        return self._request("POST", "/license/create", data)

    def revoke_license(self, license_key: str) -> Dict[str, Any]:
        """
        Revoke a license
        
        Args:
            license_key (str): The license key to revoke
        
        Returns:
            Dict containing revocation confirmation
        
        Example:
            >>> result = client.revoke_license("XXXX-XXXX-XXXX-XXXX")
            >>> print(result['success'])
        """
        data = {"license_key": license_key}
        return self._request("POST", "/license/revoke", data)

    def get_app_info(self) -> Dict[str, Any]:
        """
        Get application information
        
        Returns:
            Dict containing application details
        
        Example:
            >>> info = client.get_app_info()
            >>> print(info['name'])
        """
        return self._request("GET", "/app/info")

    def close(self):
        """Close the HTTP session"""
        self.session.close()

    def __enter__(self):
        """Context manager entry"""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit"""
        self.close()