"""ShinnyQuant 行情数据服务客户端"""

from io import StringIO
from typing import Optional, Union
import requests
import pandas as pd

from .models import Period
from .exceptions import AuthenticationError, ApiError


class ShinnyQuantClient:
    """ShinnyQuant 行情数据服务客户端

    提供期货合约历史 K 线数据查询。

    免费访问：
        - 最近 1 年的分钟线数据 (period=60)
        - 任意历史区间的日线数据 (period=86400)

    专业版访问：
        - 需要通过 login() 获取 token
        - 可获取全部历史分钟线与日线数据

    Examples:
        免费访问日线数据::

            client = ShinnyQuantClient()
            df = client.get_kline(
                symbol="SHFE.rb2501",
                period=Period.DAILY,
                start_time="2023-08-01 09:00:00",
                end_time="2023-08-31 15:00:00",
            )

        专业版访问分钟线数据（初始化时登录）::

            client = ShinnyQuantClient(auth=("username", "password"))
            df = client.get_kline(
                symbol="SHFE.rb2501",
                period=Period.MINUTE,
                start_time="2023-01-01 09:00:00",
                end_time="2023-12-31 15:00:00",
            )

        专业版访问分钟线数据（手动登录）::

            client = ShinnyQuantClient()
            client.login("your_username", "your_password")
            df = client.get_kline(
                symbol="SHFE.rb2501",
                period=Period.MINUTE,
                start_time="2023-01-01 09:00:00",
                end_time="2023-12-31 15:00:00",
            )
    """

    BASE_URL = "https://edb.shinnytech.com"
    MD_URL = "https://edb.shinnytech.com/md"
    DATA_URL = "https://edb.shinnytech.com/data"

    # 可选的返回列
    AVAILABLE_COLUMNS = ["open", "high", "low", "close", "volume", "open_oi", "close_oi"]

    def __init__(
        self,
        auth: Optional[tuple[str, str]] = None,
        timeout: int = 30,
        debug: bool = False,
    ):
        """初始化客户端

        Args:
            auth: 认证信息元组 (username, password)，提供后自动登录
            timeout: 请求超时时间（秒）
            debug: 是否开启调试模式，开启后请求失败时打印请求报文
        """
        self._token: Optional[str] = None
        self._timeout = timeout
        self._debug = debug
        self._session = requests.Session()

        # 如果提供了认证信息，自动登录
        if auth is not None:
            username, password = auth
            self.login(username, password)

    @property
    def token(self) -> Optional[str]:
        """当前访问令牌"""
        return self._token

    @property
    def is_authenticated(self) -> bool:
        """是否已认证"""
        return self._token is not None

    def login(self, username: str, password: str) -> str:
        """登录获取访问令牌

        Args:
            username: 用户名
            password: 密码

        Returns:
            JWT 访问令牌

        Raises:
            AuthenticationError: 认证失败
        """
        url = f"{self.BASE_URL}/token"
        try:
            resp = self._session.post(
                url,
                json={"username": username, "password": password},
                timeout=self._timeout,
            )
        except requests.RequestException as e:
            raise AuthenticationError(f"请求失败: {e}")

        if resp.status_code == 401:
            self._print_request_debug(resp)
            raise AuthenticationError("用户名或密码错误")

        if not resp.ok:
            self._print_request_debug(resp)
            raise AuthenticationError(f"认证失败: {resp.status_code}, {resp.text}")

        try:
            data = resp.json()
            self._token = data["token"]
            return self._token
        except (ValueError, KeyError) as e:
            raise AuthenticationError(f"响应解析失败: {e}")

    def logout(self) -> None:
        """清除当前令牌"""
        self._token = None

    def get_kline(
        self,
        symbol: str,
        period: Union[Period, int],
        start_time: Optional[str] = None,
        end_time: Optional[str] = None,
        columns: Optional[list[str]] = None,
        token: Optional[str] = None,
        datetime_as_index: bool = True,
    ) -> pd.DataFrame:
        """获取 K 线数据

        Args:
            symbol: 合约标识，如 "SHFE.rb2501"、"KQ.m@CFFEX.IF"
            period: K 线周期，60（分钟线）或 86400（日线）
            start_time: 起始时间，格式 "YYYY-MM-DD HH:MM:SS"
            end_time: 结束时间，格式 "YYYY-MM-DD HH:MM:SS"
            columns: 返回列，可选 open,high,low,close,volume,open_oi,close_oi
            token: 访问令牌，不传则使用 login() 获取的令牌
            datetime_as_index: 是否将 datetime 列设为行索引，默认 True

        Returns:
            包含 K 线数据的 DataFrame，datetime_as_index=True 时索引为 datetime

        Raises:
            ApiError: API 调用错误
        """
        url = f"{self.MD_URL}/kline"

        params = {
            "period": int(period),
            "symbol": symbol,
        }

        if start_time:
            params["start_time"] = start_time
        if end_time:
            params["end_time"] = end_time
        if columns:
            params["col"] = ",".join(columns)

        headers = {}
        effective_token = token or self._token
        if effective_token:
            headers["Authorization"] = f"Bearer {effective_token}"

        try:
            resp = self._session.get(
                url,
                params=params,
                headers=headers,
                timeout=self._timeout,
                stream=True,
            )
        except requests.RequestException as e:
            raise ApiError(0, f"请求失败: {e}")

        if not resp.ok:
            self._handle_error_response(resp)

        # 解析 CSV 数据
        df = pd.read_csv(StringIO(resp.text))

        if "datetime_nano" in df.columns:
            df["datetime"] = pd.to_datetime(df["datetime_nano"], unit="ns")
            df["datetime"] = df["datetime"].dt.tz_localize("UTC").dt.tz_convert("Asia/Shanghai")
            if datetime_as_index:
                df.set_index("datetime", inplace=True)

        return df

    def get_kline_raw(
        self,
        symbol: str,
        period: Union[Period, int],
        start_time: Optional[str] = None,
        end_time: Optional[str] = None,
        columns: Optional[list[str]] = None,
        token: Optional[str] = None,
    ) -> str:
        """获取 K 线原始 CSV 数据

        参数与 get_kline 相同，但返回原始 CSV 字符串。

        Returns:
            CSV 格式字符串

        Raises:
            ApiError: API 调用错误
        """
        url = f"{self.MD_URL}/kline"

        params = {
            "period": int(period),
            "symbol": symbol,
        }

        if start_time:
            params["start_time"] = start_time
        if end_time:
            params["end_time"] = end_time
        if columns:
            params["col"] = ",".join(columns)

        headers = {}
        effective_token = token or self._token
        if effective_token:
            headers["Authorization"] = f"Bearer {effective_token}"

        try:
            resp = self._session.get(
                url,
                params=params,
                headers=headers,
                timeout=self._timeout,
            )
        except requests.RequestException as e:
            raise ApiError(0, f"请求失败: {e}")

        if not resp.ok:
            self._handle_error_response(resp)

        return resp.text

    def _print_request_debug(self, resp: requests.Response) -> None:
        """打印请求报文便于调试（仅 debug=True 时生效）"""
        if not self._debug:
            return
        req = resp.request
        print("=" * 50)
        print("请求失败，报文信息：")
        print(f"  URL: {req.url}")
        print(f"  Method: {req.method}")
        print(f"  Headers: {dict(req.headers)}")
        if req.body:
            print(f"  Body: {req.body}")
        print(f"  Response Status: {resp.status_code}")
        print(f"  Response Body: {resp.text[:500]}")
        print("=" * 50)

    def _handle_error_response(self, resp: requests.Response) -> None:
        """处理错误响应"""
        self._print_request_debug(resp)

        text = resp.text.strip()
        if "," in text:
            parts = text.split(",", 1)
            try:
                code = int(parts[0])
                message = parts[1] if len(parts) > 1 else ""
                raise ApiError(code, message)
            except ValueError:
                pass
        raise ApiError(resp.status_code, text)

    # ==================== EDB 经济数据服务 ====================

    def get_edb_index_table(
        self,
        ids: Optional[list[int]] = None,
        search: Optional[str] = None,
    ) -> pd.DataFrame:
        """查询 EDB 指标目录

        Args:
            ids: 指标 ID 列表，指定要查询的指标
            search: 关键词搜索（匹配 cn_name、table_name）

        Returns:
            包含指标目录的 DataFrame，列包括:
            - id: 指标 ID
            - cn_name: 指标中文名
            - table_name: 指标表名/展示名
            - frequency: 频率（日/周/月）
            - unit: 单位
            - start_date: 起始日期
            - end_date: 结束日期

        Raises:
            ApiError: API 调用错误
            AuthenticationError: 未认证或认证失败

        Note:
            此接口需要专业版权限，请先调用 login() 或使用 auth 参数初始化。
        """
        if not self.is_authenticated:
            raise AuthenticationError("EDB 服务需要认证，请先登录")

        url = f"{self.DATA_URL}/index_table"
        payload: dict = {}
        if ids:
            payload["ids"] = ids
        if search:
            payload["search"] = search

        headers = {"Authorization": f"Bearer {self._token}"}

        try:
            resp = self._session.post(
                url,
                json=payload,
                headers=headers,
                timeout=self._timeout,
            )
        except requests.RequestException as e:
            raise ApiError(0, f"请求失败: {e}")

        if not resp.ok:
            self._handle_error_response(resp)

        result = resp.json()
        if result.get("error_code", 0) != 0:
            self._print_request_debug(resp)
            raise ApiError(result["error_code"], result.get("error_msg", ""))

        data = result.get("data", [])
        if not data:
            return pd.DataFrame(columns=[
                "id", "cn_name", "table_name", "frequency", "unit", "start_date", "end_date"
            ])

        return pd.DataFrame(data)

    def get_edb_data(
        self,
        ids: list[int],
        start: Optional[str] = None,
        end: Optional[str] = None,
    ) -> pd.DataFrame:
        """查询 EDB 指标数值

        Args:
            ids: 指标 ID 列表（必填，长度 1-100）
            start: 起始日期，格式 YYYY-MM-DD（可选）
            end: 结束日期，格式 YYYY-MM-DD（可选）

        Returns:
            包含指标数值的 DataFrame，索引为日期，列为指标 ID

        Raises:
            ApiError: API 调用错误
            AuthenticationError: 未认证或认证失败
            ValueError: ids 为空或超过 100 个

        Note:
            此接口需要专业版权限，请先调用 login() 或使用 auth 参数初始化。

        Example:
            >>> client = ShinnyQuantClient(auth=("user", "pass"))
            >>> df = client.get_edb_data(ids=[1, 2], start="2024-01-01", end="2024-12-31")
            >>> print(df)
                            1           2
            2024-01-05  957664.0  123456.0
            2024-01-12  956037.0  123457.0
        """
        if not self.is_authenticated:
            raise AuthenticationError("EDB 服务需要认证，请先登录")

        if not ids:
            raise ValueError("ids 不能为空")
        if len(ids) > 100:
            raise ValueError("ids 数量不能超过 100")

        url = f"{self.DATA_URL}/index_data"
        payload: dict = {"ids": ids}
        if start:
            payload["start"] = start
        if end:
            payload["end"] = end

        headers = {"Authorization": f"Bearer {self._token}"}

        try:
            resp = self._session.post(
                url,
                json=payload,
                headers=headers,
                timeout=self._timeout,
            )
        except requests.RequestException as e:
            raise ApiError(0, f"请求失败: {e}")

        if not resp.ok:
            self._handle_error_response(resp)

        result = resp.json()
        if result.get("error_code", 0) != 0:
            self._print_request_debug(resp)
            raise ApiError(result["error_code"], result.get("error_msg", ""))

        data = result.get("data", {})
        result_ids = data.get("ids", [])
        values = data.get("values", {})

        if not values:
            return pd.DataFrame(columns=result_ids)

        # 构建 DataFrame: 日期为索引，指标 ID 为列
        df = pd.DataFrame.from_dict(values, orient="index", columns=result_ids)
        df.index = pd.to_datetime(df.index)
        df.index.name = "date"
        df.sort_index(inplace=True)

        return df

    def search_edb_index(self, keyword: str) -> pd.DataFrame:
        """搜索 EDB 指标（便捷方法）

        Args:
            keyword: 搜索关键词

        Returns:
            匹配的指标目录 DataFrame
        """
        return self.get_edb_index_table(search=keyword)

    def close(self) -> None:
        """关闭客户端连接"""
        self._session.close()

    def __enter__(self) -> "ShinnyQuantClient":
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        self.close()
