"""
G-Prophet API Client
"""

import time
import logging
from typing import Any, Dict, List, Optional

import httpx

logger = logging.getLogger(__name__)


class GProphetError(Exception):
    """Base exception for G-Prophet API errors."""

    def __init__(self, code: str, message: str, details: Any = None, status_code: int = 0):
        self.code = code
        self.message = message
        self.details = details
        self.status_code = status_code
        super().__init__(f"[{code}] {message}")


class RateLimitError(GProphetError):
    """Raised when rate limit is exceeded."""

    def __init__(self, message: str, retry_after: int = 60):
        self.retry_after = retry_after
        super().__init__("RATE_LIMITED", message, status_code=429)


class InsufficientPointsError(GProphetError):
    """Raised when points are insufficient."""

    def __init__(self, message: str, required: int = 0, available: int = 0):
        self.required = required
        self.available = available
        super().__init__("INSUFFICIENT_POINTS", message, status_code=402)


class GProphet:
    """
    G-Prophet API Client.

    Args:
        api_key: Your API key (starts with 'gp_sk_')
        base_url: API base URL (default: https://gprophet.com/api/external/v1)
        timeout: Request timeout in seconds (default: 60)
        max_retries: Max retries on rate limit (default: 3)
    """

    DEFAULT_BASE_URL = "https://gprophet.com/api/external/v1"

    def __init__(
        self,
        api_key: str,
        base_url: str = None,
        timeout: int = 60,
        max_retries: int = 3,
    ):
        if not api_key or not api_key.startswith("gp_sk_"):
            raise ValueError("API key must start with 'gp_sk_'")

        self.api_key = api_key
        self.base_url = (base_url or self.DEFAULT_BASE_URL).rstrip("/")
        self.timeout = timeout
        self.max_retries = max_retries
        self._client = httpx.Client(
            base_url=self.base_url,
            headers={"X-API-Key": self.api_key},
            timeout=timeout,
        )

    def close(self):
        """Close the HTTP client."""
        self._client.close()

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.close()

    # ==================== Internal ====================

    def _request(self, method: str, path: str, **kwargs) -> Dict:
        """Make an API request with automatic retry on rate limit."""
        for attempt in range(self.max_retries + 1):
            resp = self._client.request(method, path, **kwargs)

            if resp.status_code == 429 and attempt < self.max_retries:
                retry_after = int(resp.headers.get("Retry-After", "5"))
                logger.warning(f"Rate limited, retrying in {retry_after}s (attempt {attempt + 1})")
                time.sleep(retry_after)
                continue

            data = resp.json()

            if resp.status_code == 429:
                details = data.get("error", {}).get("details", {})
                raise RateLimitError(
                    data.get("error", {}).get("message", "Rate limited"),
                    retry_after=details.get("retry_after_seconds", 60)
                )

            if resp.status_code == 402:
                details = data.get("error", {}).get("details", {})
                raise InsufficientPointsError(
                    data.get("error", {}).get("message", "Insufficient points"),
                    required=details.get("required", 0),
                    available=details.get("available", 0)
                )

            if not data.get("success", False):
                err = data.get("error", {})
                raise GProphetError(
                    code=err.get("code", "UNKNOWN"),
                    message=err.get("message", "Unknown error"),
                    details=err.get("details"),
                    status_code=resp.status_code
                )

            return data.get("data")

        # Should not reach here
        raise GProphetError("MAX_RETRIES", "Max retries exceeded")

    def _get(self, path: str, params: Dict = None) -> Dict:
        return self._request("GET", path, params=params)

    def _post(self, path: str, json: Dict = None) -> Dict:
        return self._request("POST", path, json=json)

    # ==================== System ====================

    def health(self) -> Dict:
        """Check API health status."""
        resp = self._client.get("/health")
        return resp.json()

    def info(self) -> Dict:
        """Get API metadata: supported markets, algorithms, pricing."""
        return self._get("/info")

    # ==================== Account ====================

    def balance(self) -> Dict:
        """Get current points balance and quota info."""
        return self._get("/account/balance")

    def usage(self, days: int = 7) -> Dict:
        """Get API usage statistics."""
        return self._get("/account/usage", params={"days": days})

    # ==================== Predictions ====================

    def predict(
        self,
        symbol: str,
        market: str = "US",
        days: int = 7,
        algorithm: str = "auto",
    ) -> Dict:
        """
        Predict stock/crypto price movement.

        Args:
            symbol: Stock/crypto symbol (e.g., 'AAPL', '600519', 'BTCUSDT')
            market: Market code ('US', 'CN', 'HK', 'CRYPTO')
            days: Prediction horizon (1-30)
            algorithm: Algorithm ('auto', 'gprophet2026v1', 'lstm', 'transformer', 'random_forest', 'ensemble')

        Returns:
            Prediction result with predicted_price, direction, confidence, etc.
        """
        return self._post("/predictions/predict", json={
            "symbol": symbol, "market": market, "days": days, "algorithm": algorithm
        })

    def compare(
        self,
        symbol: str,
        market: str = "US",
        days: int = 5,
        algorithms: List[str] = None,
    ) -> Dict:
        """
        Compare predictions from multiple algorithms.

        Args:
            symbol: Stock/crypto symbol
            market: Market code
            days: Prediction horizon (1-30)
            algorithms: List of algorithms to compare
        """
        return self._post("/predictions/compare", json={
            "symbol": symbol, "market": market, "days": days,
            "algorithms": algorithms or ["gprophet2026v1", "lstm", "transformer", "ensemble"]
        })

    # ==================== Market Data ====================

    def quote(self, symbol: str, market: str = "US") -> Dict:
        """Get real-time quote for a symbol."""
        return self._get("/market-data/quote", params={"symbol": symbol, "market": market})

    def batch_quote(self, symbols: List[str], market: str = "US") -> Dict:
        """Get quotes for multiple symbols at once."""
        return self._post("/market-data/batch-quote", json={"symbols": symbols, "market": market})

    def history(self, symbol: str, market: str = "US", period: str = "3m") -> Dict:
        """
        Get historical OHLCV data.

        Args:
            symbol: Stock/crypto symbol
            market: Market code
            period: Time range ('1w', '1m', '3m', '6m', '1y', '2y')
        """
        return self._get("/market-data/history", params={
            "symbol": symbol, "market": market, "period": period
        })

    def search(self, keyword: str, market: str = "US", limit: int = 10) -> Dict:
        """Search for stocks/crypto by keyword."""
        return self._get("/market-data/search", params={
            "keyword": keyword, "market": market, "limit": limit
        })

    # ==================== Technical Analysis ====================

    def technical(
        self,
        symbol: str,
        market: str = "US",
        indicators: List[str] = None,
    ) -> Dict:
        """
        Calculate technical indicators and generate trading signals.

        Args:
            symbol: Stock/crypto symbol
            market: Market code
            indicators: List of indicators ('rsi', 'macd', 'bollinger', 'kdj', 'sma', 'ema')
        """
        return self._post("/technical/analyze", json={
            "symbol": symbol, "market": market,
            "indicators": indicators or ["rsi", "macd", "bollinger", "kdj"]
        })

    # ==================== Sentiment ====================

    def fear_greed(self, days: int = 1) -> Dict:
        """Get crypto Fear & Greed Index."""
        return self._get("/sentiment/fear-greed", params={"days": days})

    def market_overview(self, market: str = "CN") -> Dict:
        """Get market overview (breadth, hot concepts, major indices)."""
        return self._get("/sentiment/market-overview", params={"market": market})

    # ==================== Analysis ====================

    def analyze_stock(
        self,
        symbol: str,
        market: str = "US",
        locale: str = "zh-CN",
        callback_url: str = None,
        wait: bool = False,
        poll_interval: int = 5,
        max_wait: int = 300,
    ) -> Dict:
        """
        Run AI stock analysis (58 points).

        Args:
            symbol: Stock/crypto symbol
            market: Market code ('US', 'CN', 'CRYPTO')
            locale: Language ('zh-CN', 'en-US')
            callback_url: Optional webhook URL for result delivery
            wait: If True, poll until completion (blocking)
            poll_interval: Seconds between polls (default 5)
            max_wait: Max seconds to wait (default 300)

        Returns:
            If wait=False: task info with task_id
            If wait=True: completed analysis result
        """
        result = self._post("/analysis/stock", json={
            "symbol": symbol, "market": market, "locale": locale,
            "callback_url": callback_url
        })

        if wait:
            return self._poll_task(result["task_id"], poll_interval, max_wait)
        return result

    def analyze_comprehensive(
        self,
        symbol: str,
        market: str = "US",
        locale: str = "zh-CN",
        callback_url: str = None,
        wait: bool = False,
        poll_interval: int = 5,
        max_wait: int = 300,
    ) -> Dict:
        """
        Run multi-agent comprehensive analysis (150 points).

        Args:
            symbol: Stock/crypto symbol
            market: Market code ('US', 'CN', 'HK', 'CRYPTO')
            locale: Language ('zh-CN', 'en-US')
            callback_url: Optional webhook URL for result delivery
            wait: If True, poll until completion (blocking)
            poll_interval: Seconds between polls (default 5)
            max_wait: Max seconds to wait (default 300)

        Returns:
            If wait=False: task info with task_id
            If wait=True: completed analysis result
        """
        result = self._post("/analysis/comprehensive", json={
            "symbol": symbol, "market": market, "locale": locale,
            "callback_url": callback_url
        })

        if wait:
            return self._poll_task(result["task_id"], poll_interval, max_wait)
        return result

    def task_status(self, task_id: str) -> Dict:
        """Check the status of an async analysis task."""
        return self._get(f"/analysis/task/{task_id}")

    def _poll_task(self, task_id: str, interval: int = 5, max_wait: int = 300) -> Dict:
        """Poll a task until completion."""
        elapsed = 0
        while elapsed < max_wait:
            status = self.task_status(task_id)
            if status["status"] == "completed":
                return status.get("result", status)
            if status["status"] == "failed":
                raise GProphetError(
                    code="TASK_FAILED",
                    message=status.get("error", "Task failed")
                )
            time.sleep(interval)
            elapsed += interval

        raise GProphetError(code="TIMEOUT", message=f"Task {task_id} did not complete within {max_wait}s")
