"""
Base client class for all SDK clients.

This module provides a base class that handles common functionality
such as automatic credential refresh on 401 errors.
"""

import requests
from typing import Dict, Any, Optional, Union, List
from requests import Response
from requests.exceptions import RequestException, HTTPError
from adxp_sdk.auth import BaseCredentials, PasswordCredentials, TokenCredentials


# 기본 요청 타임아웃(초). 미설정 시 서버가 응답해도 소켓 읽기에서 무한 대기할 수 있음.
DEFAULT_REQUEST_TIMEOUT = (10, 60)  # (connect, read)

class BaseClient:
    """
    Base class for all SDK clients.
    
    Provides common functionality including:
    - Automatic credential refresh on 401 Unauthorized errors
    - Common HTTP request handling
    """
    
    def __init__(self, credentials: BaseCredentials):
        """
        Initialize the base client.
        
        Args:
            credentials: Authentication credentials (BaseCredentials)
        """
        self.credentials = credentials
        self.base_url = credentials.base_url
        self.headers = credentials.get_headers()
    
    def _make_request(
        self,
        method: str,
        endpoint: str,
        data: Optional[Union[Dict[str, Any], List[Any]]] = None,
        params: Optional[Dict[str, Any]] = None,
        json_data: Optional[Union[Dict[str, Any], List[Any]]] = None,
        files: Optional[Union[Dict[str, Any], List[tuple]]] = None,
        max_retries: int = 1,
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """
        Make HTTP request to the API with automatic credential refresh on 401 errors.
        
        Args:
            method: HTTP method (GET, POST, PUT, DELETE)
            endpoint: API endpoint path
            data: Form data for the request
            params: Query parameters
            json_data: JSON data for the request
            files: Files for multipart/form-data uploads
            max_retries: Maximum number of retries after refresh (default: 1)
        
        Returns:
            Union[Dict[str, Any], List[Any], Any]: API response data (dict, list, or any JSON-serializable type)
            Note: The actual return type depends on the API response. Most APIs return dict,
            but some may return list directly (e.g., lineage API).
        
        Raises:
            RequestException: If the API request fails after all retries
        """
        # endpoint가 이미 전체 URL인지 확인
        if endpoint.startswith('http://') or endpoint.startswith('https://'):
            url = endpoint
        else:
            # 상대 경로인 경우 base_url과 결합
            url = f"{self.base_url.rstrip('/')}/{endpoint.lstrip('/')}"
        
        last_exception = None
        
        for attempt in range(max_retries + 1):
            try:
                # File upload인 경우 Content-Type 헤더를 제거해야 함
                # requests가 multipart/form-data의 Content-Type을 자동으로 설정함
                headers = self.headers.copy() if files is None else {k: v for k, v in self.headers.items() if k.lower() != 'content-type'}
                
                # HTTP 요청 실행 (timeout 미설정 시 서버 응답 후에도 소켓에서 무한 대기 가능)
                timeout = DEFAULT_REQUEST_TIMEOUT
                if method.upper() == "GET":
                    response = requests.get(url, headers=headers, params=params, timeout=timeout)
                elif method.upper() == "POST":
                    if files:
                        response = requests.post(url, headers=headers, data=data, files=files, params=params, timeout=timeout)
                    elif json_data:
                        response = requests.post(url, headers=headers, json=json_data, params=params, timeout=timeout)
                    else:
                        response = requests.post(url, headers=headers, data=data, params=params, timeout=timeout)
                elif method.upper() == "PUT":
                    if files:
                        response = requests.put(url, headers=headers, data=data, files=files, params=params, timeout=timeout)
                    elif json_data:
                        response = requests.put(url, headers=headers, json=json_data, params=params, timeout=timeout)
                    else:
                        response = requests.put(url, headers=headers, data=data, params=params, timeout=timeout)
                elif method.upper() == "DELETE":
                    if json_data:
                        response = requests.delete(url, headers=headers, json=json_data, params=params, timeout=timeout)
                    else:
                        response = requests.delete(url, headers=headers, params=params, timeout=timeout)
                else:
                    raise ValueError(f"Unsupported HTTP method: {method}")
                
                # Check status before raise_for_status to handle 401 explicitly
                if response.status_code == 401:
                    # 401 Unauthorized 에러인 경우
                    # PasswordCredentials 또는 TokenCredentials인 경우 refresh 시도
                    if isinstance(self.credentials, (PasswordCredentials, TokenCredentials)):
                        if attempt < max_retries:
                            try:
                                # Credentials refresh
                                self.credentials.authenticate()
                                # 헤더 갱신
                                self.headers = self.credentials.get_headers()
                                # 재시도를 위해 continue
                                continue
                            except Exception as refresh_error:
                                # Refresh 실패 시 원본 에러를 발생시킴
                                raise RequestException(
                                    f"API request failed with 401 Unauthorized and credential refresh failed: {str(refresh_error)}"
                                )
                    
                    # Refresh를 지원하지 않거나 모든 재시도가 실패한 경우
                    if attempt >= max_retries:
                        raise RequestException(
                            f"API request failed with 401 Unauthorized: {response.status_code} Client Error: {response.reason} for url: {response.url}"
                        )
                
                response.raise_for_status()
                
                # 빈 응답 처리 (삭제 API 등 - 204 No Content)
                if response.status_code == 204 or not response.text.strip():
                    return {
                        "success": True,
                        "message": "Request processed successfully",
                        "status_code": response.status_code
                    }
                
                return response.json()
                
            except requests.exceptions.HTTPError as e:
                # 401이 아닌 다른 HTTP 에러 (401은 이미 위에서 처리됨)
                raise RequestException(f"API request failed: {str(e)}") from e
                    
            except requests.exceptions.RequestException as e:
                # 기타 네트워크 에러 (HTTPError를 제외한 모든 RequestException)
                # HTTPError는 이미 위에서 처리되었으므로 여기서는 다른 네트워크 에러만 처리
                raise RequestException(f"API request failed: {str(e)}") from e
        
        # 모든 재시도가 실패한 경우
        if last_exception:
            raise RequestException(
                f"API request failed after {max_retries + 1} attempts: {str(last_exception)}"
            ) from last_exception
        
        raise RequestException("Unexpected error in _make_request")

    def _make_request_with_response(
        self,
        method: str,
        endpoint: str,
        data: Optional[Union[Dict[str, Any], List[Any]]] = None,
        params: Optional[Dict[str, Any]] = None,
        json_data: Optional[Union[Dict[str, Any], List[Any]]] = None,
        files: Optional[Dict[str, Any]] = None,
        timeout: Optional[float] = None,
        verify: bool = True,
        stream: bool = False,
        max_retries: int = 1,
    ) -> Response:
        """
        Make HTTP request to the API and return the raw Response object.
        
        This method is useful when you need access to the Response object directly
        (e.g., for streaming, custom parsing, or accessing response headers).
        It includes automatic credential refresh on 401 errors.
        
        Args:
            method: HTTP method (GET, POST, PUT, DELETE)
            endpoint: API endpoint path
            data: Form data for the request
            params: Query parameters
            json_data: JSON data for the request
            files: Files for multipart/form-data uploads
            timeout: Request timeout in seconds (optional)
            verify: Whether to verify SSL certificates (default: True)
            stream: Whether to stream the response (default: False)
            max_retries: Maximum number of retries after refresh (default: 1)
        
        Returns:
            Response: Raw requests.Response object
        
        Raises:
            RequestException: If the API request fails after all retries
        
        Example:
            # Get Response object for custom parsing
            response = client._make_request_with_response(
                method="POST",
                endpoint="queries",
                json_data=payload
            )
            payload = response.json()
            # Custom parsing logic...
            
            # Get streaming response
            response = client._make_request_with_response(
                method="GET",
                endpoint="download",
                stream=True
            )
            for chunk in response.iter_content(chunk_size=8192):
                # Process chunk...
        """
        # endpoint가 이미 전체 URL인지 확인
        if endpoint.startswith('http://') or endpoint.startswith('https://'):
            url = endpoint
        else:
            # 상대 경로인 경우 base_url과 결합
            url = f"{self.base_url.rstrip('/')}/{endpoint.lstrip('/')}"
        
        last_exception = None
        
        for attempt in range(max_retries + 1):
            try:
                # File upload인 경우 Content-Type 헤더를 제거해야 함
                # requests가 multipart/form-data의 Content-Type을 자동으로 설정함
                headers = self.headers.copy() if files is None else {k: v for k, v in self.headers.items() if k.lower() != 'content-type'}
                
                # HTTP 요청 실행
                request_kwargs = {
                    "headers": headers,
                    "params": params,
                    "verify": verify,
                    "stream": stream,
                    "timeout": timeout if timeout is not None else DEFAULT_REQUEST_TIMEOUT,
                }
                
                if method.upper() == "GET":
                    response = requests.get(url, **request_kwargs)
                elif method.upper() == "POST":
                    if files:
                        # File upload (multipart/form-data)
                        request_kwargs["data"] = data
                        request_kwargs["files"] = files
                        response = requests.post(url, **request_kwargs)
                    elif json_data:
                        request_kwargs["json"] = json_data
                        response = requests.post(url, **request_kwargs)
                    else:
                        request_kwargs["data"] = data
                        response = requests.post(url, **request_kwargs)
                elif method.upper() == "PUT":
                    if files:
                        # File upload (multipart/form-data)
                        request_kwargs["data"] = data
                        request_kwargs["files"] = files
                        response = requests.put(url, **request_kwargs)
                    elif json_data:
                        request_kwargs["json"] = json_data
                        response = requests.put(url, **request_kwargs)
                    else:
                        request_kwargs["data"] = data
                        response = requests.put(url, **request_kwargs)
                elif method.upper() == "DELETE":
                    if json_data:
                        request_kwargs["json"] = json_data
                        response = requests.delete(url, **request_kwargs)
                    else:
                        response = requests.delete(url, **request_kwargs)
                else:
                    raise ValueError(f"Unsupported HTTP method: {method}")
                
                # Check status before raise_for_status to handle 401 explicitly
                if response.status_code == 401:
                    # 401 Unauthorized 에러인 경우
                    # PasswordCredentials 또는 TokenCredentials인 경우 refresh 시도
                    if isinstance(self.credentials, (PasswordCredentials, TokenCredentials)):
                        if attempt < max_retries:
                            try:
                                # Credentials refresh
                                self.credentials.authenticate()
                                # 헤더 갱신
                                self.headers = self.credentials.get_headers()
                                # 재시도를 위해 continue
                                continue
                            except Exception as refresh_error:
                                # Refresh 실패 시 원본 에러를 발생시킴
                                raise RequestException(
                                    f"API request failed with 401 Unauthorized and credential refresh failed: {str(refresh_error)}"
                                )
                    
                    # Refresh를 지원하지 않거나 모든 재시도가 실패한 경우
                    if attempt >= max_retries:
                        raise RequestException(
                            f"API request failed with 401 Unauthorized: {response.status_code} Client Error: {response.reason} for url: {response.url}"
                        )
                
                response.raise_for_status()
                return response
                
            except requests.exceptions.HTTPError as e:
                # 401이 아닌 다른 HTTP 에러 (401은 이미 위에서 처리됨)
                raise RequestException(f"API request failed: {str(e)}") from e
                    
            except requests.exceptions.RequestException as e:
                # 기타 네트워크 에러
                last_exception = e
                if attempt < max_retries:
                    continue
                raise RequestException(f"API request failed: {str(e)}") from e
        
        # 모든 재시도가 실패한 경우
        if last_exception:
            raise RequestException(
                f"API request failed after {max_retries + 1} attempts: {str(last_exception)}"
            ) from last_exception
        
        raise RequestException("Unexpected error in _make_request_with_response")

