"""
urlink.client - Core client for URL Shortener API
"""

import json
import ssl
import urllib.request
import urllib.error
import urllib.parse
from typing import Optional, Dict, Any

# Try to use certifi for SSL certificates if available
try:
    import certifi
    SSL_CONTEXT = ssl.create_default_context(cafile=certifi.where())
except ImportError:
    SSL_CONTEXT = ssl.create_default_context()


class UrlinkError(Exception):
    """Base exception for urlink errors."""
    pass


class UrlinkAPIError(UrlinkError):
    """API returned an error response."""
    def __init__(self, message: str, status_code: int = None, response: dict = None):
        super().__init__(message)
        self.status_code = status_code
        self.response = response


class UrlinkConnectionError(UrlinkError):
    """Failed to connect to the API."""
    pass


class UrlinkValidationError(UrlinkError):
    """Input validation failed."""
    pass


class UrlinkClient:
    """
    Client for interacting with the URL Shortener API.
    
    Args:
        api_key: API key for authenticated requests
        base_url: Base URL of the API (defaults to https://urlink.co)
        timeout: Request timeout in seconds (default: 30)
    
    Example:
        client = UrlinkClient(api_key="YOUR-API-KEY")
        result = client.shorten("https://example.com/long-url")
        print(result['short'])
    """
    
    DEFAULT_BASE_URL = "https://urlink.co"
    
    def __init__(
        self,
        api_key: Optional[str] = None,
        base_url: Optional[str] = None,
        timeout: int = 30
    ):
        self.api_key = api_key
        self.base_url = (base_url or self.DEFAULT_BASE_URL).rstrip('/')
        self.timeout = timeout

    def _require_api_key(self) -> str:
        """Ensure an API key is present before making outbound calls."""
        if not self.api_key or not isinstance(self.api_key, str) or not self.api_key.strip():
            raise UrlinkValidationError(
                "API key is required. Configure urlink with urlink.configure(api_key=...) "
                "or instantiate UrlinkClient(api_key='your-key')."
            )
        return self.api_key.strip()
    
    def _make_request(
        self,
        method: str,
        endpoint: str,
        data: Optional[Dict] = None,
        params: Optional[Dict] = None
    ) -> Dict[str, Any]:
        """
        Make an HTTP request to the API.
        
        Args:
            method: HTTP method (GET, POST, etc.)
            endpoint: API endpoint path
            data: Request body data (for POST)
            params: Query parameters (for GET)
        
        Returns:
            Parsed JSON response
        
        Raises:
            UrlinkAPIError: If the API returns an error
            UrlinkConnectionError: If connection fails
        """
        url = f"{self.base_url}{endpoint}"

        api_key = self._require_api_key()
        
        # Add query params for GET requests
        if params:
            query_string = urllib.parse.urlencode(params)
            url = f"{url}?{query_string}"
        
        headers = {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'User-Agent': 'urlink-python/1.0.0'
        }

        headers['X-API-Key'] = api_key
        
        body = None
        if data is not None:
            body = json.dumps(data).encode('utf-8')
        
        request = urllib.request.Request(
            url,
            data=body,
            headers=headers,
            method=method
        )
        
        try:
            with urllib.request.urlopen(request, timeout=self.timeout, context=SSL_CONTEXT) as response:
                response_body = response.read().decode('utf-8')
                return json.loads(response_body)
        except urllib.error.HTTPError as e:
            error_body = {}
            try:
                error_body = json.loads(e.read().decode('utf-8'))
            except:
                pass
            
            error_message = error_body.get('error', f'HTTP {e.code}')
            raise UrlinkAPIError(
                message=error_message,
                status_code=e.code,
                response=error_body
            )
        except urllib.error.URLError as e:
            raise UrlinkConnectionError(f"Failed to connect to API: {e.reason}")
        except json.JSONDecodeError as e:
            raise UrlinkAPIError(f"Invalid JSON response: {e}")
    
    def shorten(
        self,
        url: str,
        domain: Optional[str] = None,
        code: Optional[str] = None,
        user_id: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Shorten a URL.
        
        Args:
            url: The original URL to shorten (must be a valid http/https URL)
            domain: Custom domain to use (optional, uses default if not specified)
            code: Custom short code/ending (optional, generates random if not specified)
            user_id: User ID for authenticated requests (optional)
        
        Returns:
            Dict containing:
                - code: The short code
                - original: The original URL
                - short: The full shortened URL
                - domain: The domain used
                - timestamp: Creation timestamp
        
        Raises:
            UrlinkValidationError: If the URL is invalid
            UrlinkAPIError: If the API returns an error (e.g., code already exists)
        
        Example:
            >>> client = UrlinkClient(api_key="YOUR-API-KEY")
            >>> result = client.shorten("https://example.com/very/long/url", code="my-link")
            >>> print(result['short'])
            'https://urlink.co/my-link'
        """
        # Basic validation
        if not url:
            raise UrlinkValidationError("URL is required")
        
        if not url.startswith(('http://', 'https://')):
            raise UrlinkValidationError("URL must start with http:// or https://")
        
        if code:
            # Validate code format: 3-48 chars, letters/digits/dash/underscore
            import re
            if not re.match(r'^[A-Za-z0-9_-]{3,48}$', code):
                raise UrlinkValidationError(
                    "Custom code must be 3-48 characters and contain only "
                    "letters, numbers, dashes, and underscores"
                )
        
        payload = {'url': url}
        
        if domain:
            payload['domain'] = domain
        
        if code:
            payload['code'] = code
        
        if user_id:
            payload['userId'] = user_id
        
        return self._make_request('POST', '/api/shorten', data=payload)
    
    def check_availability(
        self,
        code: str,
        domain: Optional[str] = None
    ) -> bool:
        """
        Check if a custom short code is available.
        
        Args:
            code: The short code to check
            domain: The domain to check on (optional)
        
        Returns:
            True if the code is available, False otherwise
        
        Example:
            >>> client = UrlinkClient(api_key="YOUR-API-KEY")
            >>> if client.check_availability("my-link"):
            ...     print("Code is available!")
        """
        if not code:
            raise UrlinkValidationError("Code is required")
        
        params = {'code': code}
        if domain:
            params['domain'] = domain
        
        try:
            result = self._make_request('GET', '/api/check', params=params)
            return result.get('available', False)
        except UrlinkAPIError:
            return False
    
    def bulk_shorten(
        self,
        urls: list,
        domain: Optional[str] = None
    ) -> list:
        """
        Shorten multiple URLs at once.
        
        Args:
            urls: List of URLs to shorten
            domain: Custom domain to use for all URLs (optional)
        
        Returns:
            List of results, each containing the shortened URL info
        
        Example:
            >>> client = UrlinkClient(api_key="YOUR-API-KEY")
            >>> results = client.bulk_shorten([
            ...     "https://example.com/page1",
            ...     "https://example.com/page2"
            ... ])
        """
        results = []
        for url in urls:
            try:
                result = self.shorten(url=url, domain=domain)
                results.append({
                    'success': True,
                    'original': url,
                    **result
                })
            except UrlinkError as e:
                results.append({
                    'success': False,
                    'original': url,
                    'error': str(e)
                })
        return results
