"""
Base KWS API Client

Handles authentication, HTTP requests, and provides base functionality
for all API endpoint wrappers.
"""

import requests
from typing import Optional, Dict, Any, List
from urllib.parse import urljoin
import logging
import time

logger = logging.getLogger(__name__)


class KWSClient:
    """
    Main client for interacting with KWS Platform API.
    
    Example:
        >>> client = KWSClient(base_url="http://localhost:8000")
        >>> client.login(username="admin", password="password")
        >>> datasets = client.datasets.list()
    """
    
    def __init__(
        self,
        base_url: str = "http://localhost:8000",
        token: Optional[str] = None,
        api_key: Optional[str] = None,
        timeout: int = 30,
    ):
        """
        Initialize KWS API client.

        Args:
            base_url: Base URL of the KWS API server
            token: Optional JWT (set via login() or directly)
            api_key: Optional API key cho Colab - dùng X-API-Key thay JWT
            timeout: Request timeout in seconds
        """
        self.base_url = base_url.rstrip("/")
        self.api_url = urljoin(self.base_url, "/api/v1/")
        self.token = token
        self.api_key = api_key
        self.timeout = timeout
        self.session = requests.Session()
        if api_key:
            self.session.headers["X-API-Key"] = api_key
        
        # Initialize API modules
        from .api import (
            AuthAPI, DatasetsAPI, ModelsAPI, ExperimentsAPI,
            KeywordsAPI, SentencesAPI, AudioAPI, FeaturesAPI,
            DatasetSplitsAPI, JobsAPI, EmbeddingsAPI, FewShotAPI,
            PredictAPI, DashboardAPI, TasksAPI, CollectionAPI,
            RecordingsAPI, MetricsAPI, VisualizeAPI, CompareAPI,
            DevicesAPI, ArtifactsAPI, DeployedModelsAPI,
            QualityControlAPI, RewardPayoutAPI, ConfigAPI,
            MetadataAPI, NotificationsAPI, AdminAPI, UsersAPI,
            MLFlowAPI
        )
        
        self.auth = AuthAPI(self)
        self.datasets = DatasetsAPI(self)
        self.models = ModelsAPI(self)
        self.experiments = ExperimentsAPI(self)
        self.keywords = KeywordsAPI(self)
        self.sentences = SentencesAPI(self)
        self.audio = AudioAPI(self)
        self.features = FeaturesAPI(self)
        self.dataset_splits = DatasetSplitsAPI(self)
        self.jobs = JobsAPI(self)
        self.embeddings = EmbeddingsAPI(self)
        self.fewshot = FewShotAPI(self)
        self.predict = PredictAPI(self)
        self.dashboard = DashboardAPI(self)
        self.tasks = TasksAPI(self)
        self.collection = CollectionAPI(self)
        self.recordings = RecordingsAPI(self)
        self.metrics = MetricsAPI(self)
        self.visualize = VisualizeAPI(self)
        self.compare = CompareAPI(self)
        self.devices = DevicesAPI(self)
        self.artifacts = ArtifactsAPI(self)
        self.deployed_models = DeployedModelsAPI(self)
        self.quality_control = QualityControlAPI(self)
        self.reward_payout = RewardPayoutAPI(self)
        self.config = ConfigAPI(self)
        self.metadata = MetadataAPI(self)
        self.notifications = NotificationsAPI(self)
        self.admin = AdminAPI(self)
        self.users = UsersAPI(self)
        self.mlflow = MLFlowAPI(self)
    
    def login(self, username: str, password: str) -> Dict[str, Any]:
        """
        Login and get authentication token.
        
        Args:
            username: Admin username
            password: Admin password
            
        Returns:
            Token response with access_token, admin_id, role
            
        Raises:
            requests.HTTPError: If login fails
        """
        response = self._request(
            "POST",
            "auth/admin/login",
            json={"username": username, "password": password},
            require_auth=False
        )
        self.token = response["access_token"]
        self.session.headers.update({"Authorization": f"Bearer {self.token}"})
        return response
    
    def logout(self) -> Dict[str, Any]:
        """
        Logout and clear authentication token.
        
        Returns:
            Message response
        """
        response = self._request("POST", "auth/admin/logout")
        self.token = None
        self.session.headers.pop("Authorization", None)
        return response
    
    def refresh_token(self, refresh_token: str) -> Dict[str, Any]:
        """
        Refresh authentication token.
        
        Args:
            refresh_token: Refresh token
            
        Returns:
            New token response
        """
        response = self._request(
            "POST",
            "auth/admin/refresh",
            json={"refresh_token": refresh_token},
            require_auth=False
        )
        self.token = response["access_token"]
        self.session.headers.update({"Authorization": f"Bearer {self.token}"})
        return response
    
    def get_me(self) -> Dict[str, Any]:
        """
        Get current admin information.
        
        Returns:
            Admin profile information
        """
        return self._request("GET", "auth/admin/me")

    def health(self) -> Dict[str, Any]:
        """
        Lightweight health check (no auth).

        This hits the non-versioned endpoint `/api/health` which should return:
            {"status": "OK"}
        """
        url = urljoin(self.base_url + "/", "api/health")
        resp = self.session.get(url, timeout=self.timeout)
        resp.raise_for_status()
        return resp.json()

    def health_root(self) -> Dict[str, Any]:
        """
        Alternative lightweight health endpoint `/health` (no auth).
        """
        url = urljoin(self.base_url + "/", "health")
        resp = self.session.get(url, timeout=self.timeout)
        resp.raise_for_status()
        return resp.json()
    
    def _request(
        self,
        method: str,
        endpoint: str,
        params: Optional[Dict] = None,
        json: Optional[Dict] = None,
        data: Optional[Any] = None,
        files: Optional[Dict] = None,
        require_auth: bool = True,
        stream: bool = False,
    ) -> Any:
        """
        Make HTTP request to API endpoint.
        
        Args:
            method: HTTP method (GET, POST, PUT, DELETE, etc.)
            endpoint: API endpoint path (relative to /api/v1/)
            params: Query parameters
            json: JSON body
            data: Form data
            files: Files to upload
            require_auth: Whether authentication is required
            stream: Whether to stream the response
            
        Returns:
            Response data (parsed JSON or raw response for streaming)
            
        Raises:
            requests.HTTPError: If request fails
        """
        url = urljoin(self.api_url, endpoint.lstrip("/"))

        headers = {}
        if require_auth:
            if self.api_key:
                headers["X-API-Key"] = self.api_key
            elif self.token:
                headers["Authorization"] = f"Bearer {self.token}"
        
        try:
            # For streaming downloads, disable redirects completely
            # Backend should stream files directly, not redirect to MinIO
            allow_redirects = not stream  # Disable redirects for stream requests
            
            response = self.session.request(
                method=method,
                url=url,
                params=params,
                json=json,
                data=data,
                files=files,
                headers=headers,
                timeout=self.timeout,
                stream=stream,
                allow_redirects=allow_redirects,
            )
            
            # Check redirect history for any MinIO redirects (even if allow_redirects=False)
            if hasattr(response, 'history') and response.history:
                for hist_response in response.history:
                    hist_url = hist_response.url if hasattr(hist_response, 'url') else str(hist_response)
                    if 'minio' in hist_url.lower() or ':9000' in hist_url:
                        error_msg = (
                            f"Request was redirected to MinIO URL: {hist_url}. "
                            "Backend should stream files directly via API, not redirect to MinIO. "
                            "Please ensure backend is using StreamingResponse, not RedirectResponse."
                        )
                        logger.error(error_msg)
                        raise requests.RequestException(error_msg)
            
            # Check final URL (for non-stream requests that allow redirects)
            if not stream and allow_redirects:
                final_url = response.url if hasattr(response, 'url') else url
                if final_url and ('minio' in final_url.lower() or ':9000' in final_url):
                    error_msg = (
                        f"Request was redirected to MinIO URL: {final_url}. "
                        "Backend should stream files directly via API, not redirect to MinIO. "
                        "Please ensure backend is using StreamingResponse, not RedirectResponse."
                    )
                    logger.error(error_msg)
                    raise requests.RequestException(error_msg)
            
            # Check for redirect status codes (should not happen for stream requests)
            if stream and response.status_code in (301, 302, 303, 307, 308):
                location = response.headers.get('Location', '')
                if 'minio' in location.lower() or ':9000' in location:
                    error_msg = (
                        f"Backend returned redirect to MinIO: {location}. "
                        "Backend should stream files directly via API, not redirect to MinIO. "
                        "Please ensure backend is using StreamingResponse, not RedirectResponse."
                    )
                    logger.error(error_msg)
                    raise requests.RequestException(error_msg)
            
            # #region agent log
            if response.status_code >= 400:
                try:
                    import json
                    log_path = r"d:\KWS_Project\.cursor\debug.log"
                    error_detail = response.text[:1000] if hasattr(response, 'text') else str(response)
                    with open(log_path, "a", encoding="utf-8") as f:
                        f.write(json.dumps({"sessionId": "debug-session", "runId": "run1", "hypothesisId": "H", "location": "kwslib/client.py:247", "message": "HTTP error response", "data": {"status_code": response.status_code, "url": url, "method": method, "error_detail": error_detail}, "timestamp": time.time() * 1000}) + "\n")
                except: pass
            # #endregion
            response.raise_for_status()
            
            if stream:
                return response
            
            # Handle empty responses
            if response.status_code == 204:
                return {}
            
            # Try to parse JSON
            try:
                return response.json()
            except ValueError:
                return response.text
                
        except requests.HTTPError as e:
            error_msg = f"API request failed: {method} {url}"
            if e.response is not None:
                try:
                    error_detail = e.response.json()
                    error_msg += f" - {error_detail}"
                except:
                    error_msg += f" - {e.response.text}"
            logger.error(error_msg)
            raise
        except requests.RequestException as e:
            logger.error(f"Request exception: {e}")
            raise
    
    def health_check(self) -> Dict[str, Any]:
        """
        Check API health status.
        
        Returns:
            Health status response
        """
        return self._request("GET", "health", require_auth=False)
    
    def health_db(self) -> Dict[str, Any]:
        """Check database health"""
        return self._request("GET", "health/db")
    
    def health_minio(self) -> Dict[str, Any]:
        """Check MinIO health"""
        return self._request("GET", "health/minio")
    
    def health_redis(self) -> Dict[str, Any]:
        """Check Redis health"""
        return self._request("GET", "health/redis")
    
    def health_status(self) -> Dict[str, Any]:
        """Get system status"""
        return self._request("GET", "health/status")
