"""因子元数据管理器。

提供基于DuckDB查询JSONL文件、零拷贝输出Polars DataFrame的因子管理能力。

支持:
    - JSONL文件的读写操作
    - DuckDB SQL查询（输出Polars DataFrame）
    - 字段校验和异常处理
    - 扩展字段的自由定义

使用示例:
    >>> from src.factors.metadata import FactorManager
    >>> manager = FactorManager("data/factors.jsonl")
    >>>
    >>> # 添加因子
    >>> manager.add_factor({
    ...     "factor_id": "F_001",
    ...     "name": "mom_5d",
    ...     "desc": "5日价格动量",
    ...     "dsl": "cs_rank(close / delay(close, 5) - 1)",
    ...     "category": "momentum"  # 扩展字段
    ... })
    >>>
    >>> # 查询因子
    >>> df = manager.search_factors("category = 'momentum'")
    >>> print(df)
"""

import json
import os
from pathlib import Path
from typing import Any, Dict, List, Optional

import duckdb
import polars as pl

from src.factors.metadata.exceptions import (
    DuplicateFactorError,
    FactorNotFoundError,
    FileOperationError,
    QueryError,
)
from src.factors.metadata.validator import FactorValidator


class FactorManager:
    """因子元数据管理器。

    封装底层JSONL文件I/O和DuckDB SQL查询，提供高层次的因子管理接口。

    Attributes:
        filepath: JSONL文件的完整路径
        _conn: DuckDB连接对象（懒加载）

    Example:
        >>> manager = FactorManager()  # 使用默认路径
        >>> df = manager.get_factors_by_name("mom_5d")
        >>> print(df["dsl"][0])
    """

    _DEFAULT_FILENAME = "factors.jsonl"

    def __init__(self, filepath: Optional[str] = None) -> None:
        """初始化因子管理器。

        如果文件不存在，会自动创建空的JSONL文件。

        Args:
            filepath: JSONL文件路径（相对或绝对路径），为None时使用默认路径

        Raises:
            FileOperationError: 当文件创建失败时
        """
        if filepath is None:
            # 使用默认路径：从配置读取数据目录
            from src.config.settings import settings

            self.filepath = settings.data_path_resolved / self._DEFAULT_FILENAME
        else:
            self.filepath = Path(filepath).resolve()

        self._conn: Optional[duckdb.DuckDBPyConnection] = None

        # 确保文件存在
        self._ensure_file_exists()

    def _ensure_file_exists(self) -> None:
        """确保JSONL文件存在。

        如果文件不存在，创建空文件和父目录。

        Raises:
            FileOperationError: 当文件创建失败时
        """
        if not self.filepath.exists():
            try:
                self.filepath.parent.mkdir(parents=True, exist_ok=True)
                self.filepath.write_text("", encoding="utf-8")
            except Exception as e:
                raise FileOperationError(
                    filepath=str(self.filepath), operation="create", original_error=e
                )

    def _get_connection(self) -> duckdb.DuckDBPyConnection:
        """获取DuckDB连接（懒加载）。

        Returns:
            DuckDB连接对象
        """
        if self._conn is None:
            self._conn = duckdb.connect(":memory:")
            # DuckDB 0.7+ 内置JSON支持，无需手动安装扩展
        return self._conn

    def _close_connection(self) -> None:
        """关闭DuckDB连接。"""
        if self._conn is not None:
            self._conn.close()
            self._conn = None

    def add_factor(self, factor_dict: Dict[str, Any]) -> None:
        """添加新因子到JSONL文件。

        校验核心字段后，将因子追加到文件末尾。

        Args:
            factor_dict: 因子字典，必须包含核心字段

        Raises:
            ValidationError: 当缺少核心字段或类型错误
            DuplicateFactorError: 当factor_id已存在
            FileOperationError: 当文件写入失败

        Example:
            >>> manager.add_factor({
            ...     "factor_id": "F_001",
            ...     "name": "mom_5d",
            ...     "desc": "5日动量",
            ...     "dsl": "cs_rank(close / delay(close, 5) - 1)",
            ...     "category": "momentum"  # 扩展字段
            ... })
        """
        # 校验字段
        FactorValidator.validate(factor_dict)

        # 检查factor_id是否已存在
        factor_id = factor_dict["factor_id"]
        existing = self._get_factor_by_id(factor_id)
        if existing is not None:
            raise DuplicateFactorError(factor_id)

        # 追加到文件
        try:
            with open(self.filepath, "a", encoding="utf-8") as f:
                json_line = json.dumps(factor_dict, ensure_ascii=False)
                f.write(json_line + "\n")
        except Exception as e:
            raise FileOperationError(
                filepath=str(self.filepath), operation="write", original_error=e
            )

    def get_factors_by_name(self, name: str) -> pl.DataFrame:
        """根据名称查询因子。

        使用DuckDB执行SQL查询，返回Polars DataFrame。

        Args:
            name: 因子名称（如 'mom_5d'）

        Returns:
            匹配因子的Polars DataFrame（可能为空）

        Raises:
            QueryError: 当SQL查询执行失败时

        Example:
            >>> df = manager.get_factors_by_name("mom_5d")
            >>> if len(df) > 0:
            ...     print(df["dsl"][0])
        """
        sql = f"""
            SELECT *
            FROM read_json_auto('{self.filepath}')
            WHERE name = '{name}'
        """
        return self._execute_query(sql)

    def get_factor_dsl(self, factor_id: str) -> str:
        """根据ID获取因子的DSL公式。

        Args:
            factor_id: 因子唯一标识符

        Returns:
            DSL公式字符串

        Raises:
            FactorNotFoundError: 当因子不存在时
            QueryError: 当SQL查询执行失败时

        Example:
            >>> dsl = manager.get_factor_dsl("F_001")
            >>> print(dsl)
            'cs_rank(close / delay(close, 5) - 1)'
        """
        sql = f"""
            SELECT dsl
            FROM read_json_auto('{self.filepath}')
            WHERE factor_id = '{factor_id}'
        """
        df = self._execute_query(sql)

        if len(df) == 0:
            raise FactorNotFoundError(factor_id)

        return df["dsl"][0]

    def search_factors(self, sql_condition: str) -> pl.DataFrame:
        """使用自定义SQL条件查询因子。

        允许传入WHERE子句条件，利用DuckDB进行高级筛选。

        Args:
            sql_condition: SQL WHERE子句条件（如 "category = 'momentum'"）

        Returns:
            匹配因子的Polars DataFrame

        Raises:
            QueryError: 当SQL查询执行失败时

        Example:
            >>> # 查询动量类因子
            >>> df = manager.search_factors("category = 'momentum'")
            >>>
            >>> # 查询包含特定关键词的因子
            >>> df = manager.search_factors("desc LIKE '%动量%'")
            >>>
            >>> # 复杂条件查询
            >>> df = manager.search_factors(
            ...     "category = 'momentum' AND name LIKE 'mom_%'"
            ... )
        """
        sql = f"""
            SELECT *
            FROM read_json_auto('{self.filepath}')
            WHERE {sql_condition}
        """
        return self._execute_query(sql)

    def get_all_factors(self) -> pl.DataFrame:
        """获取所有因子。

        Returns:
            所有因子的Polars DataFrame

        Raises:
            QueryError: 当SQL查询执行失败时
        """
        sql = f"""
            SELECT *
            FROM read_json_auto('{self.filepath}')
        """
        return self._execute_query(sql)

    def _get_factor_by_id(self, factor_id: str) -> Optional[Dict[str, Any]]:
        """根据ID获取因子（内部方法）。

        Args:
            factor_id: 因子唯一标识符

        Returns:
            因子字典，不存在返回None
        """
        try:
            sql = f"""
                SELECT *
                FROM read_json_auto('{self.filepath}')
                WHERE factor_id = '{factor_id}'
            """
            df = self._execute_query(sql)

            if len(df) == 0:
                return None

            # 转换为字典
            return df.to_dicts()[0]
        except QueryError:
            return None

    def _execute_query(self, sql: str) -> pl.DataFrame:
        """执行DuckDB SQL查询并返回Polars DataFrame。

        Args:
            sql: SQL查询语句

        Returns:
            查询结果的Polars DataFrame

        Raises:
            QueryError: 当SQL执行失败时
        """
        try:
            conn = self._get_connection()
            result = conn.execute(sql).pl()
            return result
        except Exception as e:
            raise QueryError(sql, e)

    def __enter__(self) -> "FactorManager":
        """上下文管理器入口。"""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        """上下文管理器出口，确保连接关闭。"""
        self._close_connection()

    def __repr__(self) -> str:
        """返回管理器字符串表示。"""
        return f"FactorManager('{self.filepath}')"
