"""ClickHouse Repository（宽表查询 + FINAL/argMax 去重 + DataScope WHERE 注入）.

P0 整改：ReplacingMergeTree 查询必须加 FINAL 或用 argMax 聚合确保去重生效.
支持降级模式：clickhouse_host 未配置或不可达时返回 None，服务仍可启动.
"""

import asyncio
from datetime import datetime
from typing import Any

import structlog

from ..config import settings

logger = structlog.get_logger(__name__)

_client: Any | None = None
_client_initialized: bool = False


def get_client() -> Any | None:
    """获取 ClickHouse 客户端（惰性初始化，失败返回 None 降级）."""
    global _client, _client_initialized

    if not settings.clickhouse_host:
        return None

    if _client_initialized:
        return _client

    _client_initialized = True
    try:
        import clickhouse_connect

        kwargs: dict[str, Any] = {
            "host": settings.clickhouse_host,
            "port": settings.clickhouse_port,
            "database": settings.clickhouse_database,
            "connect_timeout": settings.clickhouse_connect_timeout_ms / 1000,
        }
        if settings.clickhouse_user:
            kwargs["username"] = settings.clickhouse_user
        if settings.clickhouse_password:
            kwargs["password"] = settings.clickhouse_password
        _client = clickhouse_connect.get_client(**kwargs)
        logger.info(
            "clickhouse_client_initialized",
            host=settings.clickhouse_host,
            port=settings.clickhouse_port,
            database=settings.clickhouse_database,
        )
    except Exception as exc:  # noqa: BLE001
        logger.warning("clickhouse_client_init_failed_degraded", error=str(exc))
        _client = None

    return _client


async def close_client() -> None:
    """关闭客户端."""
    global _client, _client_initialized
    if _client is not None:
        try:
            await asyncio.to_thread(_client.close)
        except Exception as exc:  # noqa: BLE001
            logger.warning("clickhouse_client_close_failed", error=str(exc))
        finally:
            _client = None
            _client_initialized = False


async def ping() -> bool:
    """ClickHouse 连通性检查（供 /readyz 使用）."""
    client = get_client()
    if client is None:
        return False
    try:
        await asyncio.to_thread(client.query, "SELECT 1")
        return True
    except Exception as exc:  # noqa: BLE001
        logger.warning("clickhouse_ping_failed", error=str(exc))
        return False


# ===== 查询方法（FINAL/argMax 去重） =====


async def query_class_performance(
    class_id: str,
    subject_id: str = "",
    start_date: int = 0,
    end_date: int = 0,
) -> dict | None:
    """查询班级成绩分析（聚合 student_dashboard_view，FINAL 去重）."""
    client = get_client()
    if client is None:
        return None

    where_parts = ["class_id = {cid:String}"]
    params: dict[str, Any] = {"cid": class_id}
    if subject_id:
        where_parts.append("subject_id = {sid:String}")
        params["sid"] = subject_id
    if start_date:
        where_parts.append("last_updated >= {sd:DateTime64(3)}")
        params["sd"] = datetime.fromtimestamp(start_date)
    if end_date:
        where_parts.append("last_updated <= {ed:DateTime64(3)}")
        params["ed"] = datetime.fromtimestamp(end_date)

    where_clause = " AND ".join(where_parts)
    try:
        result = await asyncio.to_thread(
            client.query,
            f"SELECT "
            f"  count(DISTINCT student_id) AS total_students, "
            f"  avg(score) AS average_score, "
            f"  countIf(score >= 60) / count() AS pass_rate "
            f"FROM student_dashboard_view FINAL "
            f"WHERE {where_clause}",
            parameters=params,
        )
        rows = result.result_rows
    except Exception as exc:  # noqa: BLE001
        logger.warning("query_class_performance_failed", error=str(exc), class_id=class_id)
        return None

    if not rows:
        return {"classId": class_id, "averageScore": 0.0, "passRate": 0.0, "totalStudents": 0}

    total_students, average_score, pass_rate = rows[0]
    return {
        "classId": class_id,
        "averageScore": float(average_score) if average_score is not None else 0.0,
        "passRate": float(pass_rate) if pass_rate is not None else 0.0,
        "totalStudents": int(total_students) if total_students is not None else 0,
    }


async def query_student_dashboard(student_id: str) -> dict | None:
    """查询学生学情宽表（FINAL 去重，返回最近 50 条）."""
    client = get_client()
    if client is None:
        return None

    try:
        result = await asyncio.to_thread(
            client.query,
            "SELECT student_id, class_id, exam_id, subject_id, score, "
            "rank_in_class, knowledge_point_id, mastery_level, error_count, "
            "last_updated "
            "FROM student_dashboard_view FINAL "
            "WHERE student_id = {sid:String} "
            "ORDER BY last_updated DESC "
            "LIMIT 50",
            parameters={"sid": student_id},
        )
        rows = result.result_rows
    except Exception as exc:  # noqa: BLE001
        logger.warning("query_student_dashboard_failed", error=str(exc), student_id=student_id)
        return None

    columns = [
        "student_id",
        "class_id",
        "exam_id",
        "subject_id",
        "score",
        "rank_in_class",
        "knowledge_point_id",
        "mastery_level",
        "error_count",
        "last_updated",
    ]
    records = [dict(zip(columns, row, strict=True)) for row in rows]
    return {"studentId": student_id, "records": records, "total": len(records)}


async def query_student_weakness(student_id: str, subject_id: str = "") -> dict | None:
    """查询学生薄弱知识点（mastery_level < 0.6，FINAL 去重）."""
    dashboard = await query_student_dashboard(student_id)
    if dashboard is None:
        return None

    weak_points = [
        {
            "knowledgePointId": r["knowledge_point_id"],
            "title": r.get("knowledge_point_id", ""),  # content CDC 同步后补充标题
            "mastery": r["mastery_level"],
            "errorCount": r["error_count"],
        }
        for r in dashboard["records"]
        if r.get("mastery_level") is not None
        and r["mastery_level"] < 0.6
        and (not subject_id or r.get("subject_id") == subject_id)
    ]
    return {"studentId": student_id, "weakPoints": weak_points}


async def query_student_errors(student_id: str) -> list[dict] | None:
    """查询学生错题本（FINAL 去重）."""
    client = get_client()
    if client is None:
        return None

    try:
        result = await asyncio.to_thread(
            client.query,
            "SELECT student_id, question_id, knowledge_point_id, error_count, "
            "last_error_time, content "
            "FROM student_errors FINAL "
            "WHERE student_id = {sid:String} "
            "ORDER BY last_error_time DESC "
            "LIMIT 100",
            parameters={"sid": student_id},
        )
        rows = result.result_rows
    except Exception as exc:  # noqa: BLE001
        logger.warning("query_student_errors_failed", error=str(exc), student_id=student_id)
        return None

    columns = [
        "student_id",
        "question_id",
        "knowledge_point_id",
        "error_count",
        "last_error_time",
        "content",
    ]
    return [dict(zip(columns, row, strict=True)) for row in rows]


async def query_learning_trend(
    student_id: str,
    start_date: int = 0,
    end_date: int = 0,
    subject_id: str = "",
) -> dict | None:
    """查询学习趋势（按时间排序的成绩曲线，FINAL 去重）."""
    client = get_client()
    if client is None:
        return None

    where_parts = ["student_id = {sid:String}"]
    params: dict[str, Any] = {"sid": student_id}
    if subject_id:
        where_parts.append("subject_id = {sub:String}")
        params["sub"] = subject_id
    if start_date:
        where_parts.append("last_updated >= {sd:DateTime64(3)}")
        params["sd"] = datetime.fromtimestamp(start_date)
    if end_date:
        where_parts.append("last_updated <= {ed:DateTime64(3)}")
        params["ed"] = datetime.fromtimestamp(end_date)

    where_clause = " AND ".join(where_parts)
    try:
        result = await asyncio.to_thread(
            client.query,
            f"SELECT last_updated, score, exam_id "
            f"FROM student_dashboard_view FINAL "
            f"WHERE {where_clause} "
            f"ORDER BY last_updated ASC "
            f"LIMIT 100",
            parameters=params,
        )
        rows = result.result_rows
    except Exception as exc:  # noqa: BLE001
        logger.warning("query_learning_trend_failed", error=str(exc), student_id=student_id)
        return None

    points = [
        {"date": int(row[0].timestamp()) if row[0] else 0, "score": float(row[1] or 0)}
        for row in rows
    ]
    return {"studentId": student_id, "points": points}


async def query_mastery_snapshot(student_id: str, subject_id: str = "") -> dict | None:
    """查询知识点掌握度快照（argMax 去重保留最新版本）."""
    client = get_client()
    if client is None:
        return None

    where_parts = ["student_id = {sid:String}"]
    params: dict[str, Any] = {"sid": student_id}
    if subject_id:
        where_parts.append("subject_id = {sub:String}")
        params["sub"] = subject_id

    where_clause = " AND ".join(where_parts)
    try:
        # argMax 获取每个 knowledge_point_id 的最新 mastery_level
        result = await asyncio.to_thread(
            client.query,
            f"SELECT "
            f"  knowledge_point_id, "
            f"  argMax(mastery_level, calculated_at) AS mastery_level, "
            f"  argMax(subject_id, calculated_at) AS subject_id, "
            f"  max(calculated_at) AS calculated_at "
            f"FROM mastery_snapshot "
            f"WHERE {where_clause} "
            f"GROUP BY knowledge_point_id "
            f"ORDER BY mastery_level ASC",
            parameters=params,
        )
        rows = result.result_rows
    except Exception as exc:  # noqa: BLE001
        logger.warning("query_mastery_snapshot_failed", error=str(exc), student_id=student_id)
        return None

    knowledge_points = []
    total_mastery = 0.0
    for row in rows:
        kp_id, mastery, subj, calc_at = row
        mastery_val = float(mastery or 0)
        if mastery_val >= 0.8:
            label = "mastered"
        elif mastery_val >= 0.4:
            label = "progressing"
        else:
            label = "weak"
        knowledge_points.append(
            {
                "knowledge_point_id": kp_id,
                "title": kp_id,  # content CDC 同步后补充
                "subject_id": subj or "",
                "mastery_level": mastery_val,
                "mastery_label": label,
                "calculated_at": int(calc_at.timestamp()) if calc_at else 0,
            }
        )
        total_mastery += mastery_val

    overall = total_mastery / len(knowledge_points) if knowledge_points else 0.0
    return {
        "studentId": student_id,
        "knowledgePoints": knowledge_points,
        "overallMastery": round(overall, 4),
    }


async def query_mastery_distribution(
    class_id: str,
    subject_id: str = "",
    knowledge_point_id: str = "",
) -> dict | None:
    """查询班级掌握度分布（mastered/progressing/weak 三档）."""
    client = get_client()
    if client is None:
        return None

    where_parts = ["class_id = {cid:String}"]
    params: dict[str, Any] = {"cid": class_id}
    if subject_id:
        where_parts.append("subject_id = {sid:String}")
        params["sid"] = subject_id

    where_clause = " AND ".join(where_parts)
    try:
        result = await asyncio.to_thread(
            client.query,
            f"SELECT "
            f"  countIf(mastery_level >= 0.8) AS mastered, "
            f"  countIf(mastery_level >= 0.4 AND mastery_level < 0.8) AS progressing, "
            f"  countIf(mastery_level < 0.4) AS weak, "
            f"  count() AS total "
            f"FROM student_dashboard_view FINAL "
            f"WHERE {where_clause}",
            parameters=params,
        )
        rows = result.result_rows
    except Exception as exc:  # noqa: BLE001
        logger.warning("query_mastery_distribution_failed", error=str(exc), class_id=class_id)
        return None

    if not rows:
        return {
            "classId": class_id,
            "masteredCount": 0,
            "progressingCount": 0,
            "weakCount": 0,
            "totalStudents": 0,
        }

    mastered, progressing, weak, total = rows[0]
    return {
        "classId": class_id,
        "masteredCount": int(mastered or 0),
        "progressingCount": int(progressing or 0),
        "weakCount": int(weak or 0),
        "totalStudents": int(total or 0),
    }


async def query_attendance(
    student_id: str,
    start_date: int = 0,
    end_date: int = 0,
) -> dict | None:
    """查询学生考勤历史（FINAL 去重）."""
    client = get_client()
    if client is None:
        return None

    where_parts = ["student_id = {sid:String}"]
    params: dict[str, Any] = {"sid": student_id}
    if start_date:
        where_parts.append("attendance_date >= {sd:Date}")
        params["sd"] = datetime.fromtimestamp(start_date).date()
    if end_date:
        where_parts.append("attendance_date <= {ed:Date}")
        params["ed"] = datetime.fromtimestamp(end_date).date()

    where_clause = " AND ".join(where_parts)
    try:
        result = await asyncio.to_thread(
            client.query,
            f"SELECT student_id, class_id, attendance_date, status, "
            f"recorded_by, remark, occurred_at "
            f"FROM attendance_logs FINAL "
            f"WHERE {where_clause} "
            f"ORDER BY attendance_date DESC "
            f"LIMIT 100",
            parameters=params,
        )
        rows = result.result_rows
    except Exception as exc:  # noqa: BLE001
        logger.warning("query_attendance_failed", error=str(exc), student_id=student_id)
        return None

    columns = [
        "student_id",
        "class_id",
        "attendance_date",
        "status",
        "recorded_by",
        "remark",
        "occurred_at",
    ]
    records = [dict(zip(columns, row, strict=True)) for row in rows]
    # 统计
    absent_count = sum(1 for r in records if r["status"] == "absent")
    late_count = sum(1 for r in records if r["status"] == "late")
    present_count = sum(1 for r in records if r["status"] == "present")
    return {
        "studentId": student_id,
        "records": records,
        "total": len(records),
        "absentCount": absent_count,
        "lateCount": late_count,
        "presentCount": present_count,
    }


async def query_ai_usage_summary() -> dict | None:
    """查询 AI 用量统计（按 provider 聚合，FINAL 去重）."""
    client = get_client()
    if client is None:
        return None

    try:
        result = await asyncio.to_thread(
            client.query,
            "SELECT "
            "  provider, "
            "  count() AS request_count, "
            "  sum(total_tokens) AS total_tokens, "
            "  sum(cost_cents) AS cost_cents "
            "FROM ai_usage_log FINAL "
            "GROUP BY provider "
            "ORDER BY request_count DESC",
        )
        rows = result.result_rows
    except Exception as exc:  # noqa: BLE001
        logger.warning("query_ai_usage_summary_failed", error=str(exc))
        return None

    by_provider = [
        {
            "provider": row[0],
            "requestCount": int(row[1] or 0),
            "totalTokens": int(row[2] or 0),
            "costCents": int(row[3] or 0),
        }
        for row in rows
    ]
    total_requests = sum(p["requestCount"] for p in by_provider)
    total_tokens = sum(p["totalTokens"] for p in by_provider)
    total_cost = sum(p["costCents"] for p in by_provider)
    return {
        "totalRequests": total_requests,
        "totalTokens": total_tokens,
        "totalCostCents": total_cost,
        "byProvider": by_provider,
    }


async def query_teacher_dashboard(user_id: str, class_id: str = "") -> dict | None:
    """查询教师仪表盘聚合数据."""
    client = get_client()
    if client is None:
        return None

    where = "class_id = {cid:String}" if class_id else "1=1"
    params: dict[str, Any] = {}
    if class_id:
        params["cid"] = class_id

    try:
        result = await asyncio.to_thread(
            client.query,
            f"SELECT "
            f"  count(DISTINCT class_id) AS total_classes, "
            f"  count(DISTINCT student_id) AS total_students, "
            f"  avg(score) AS avg_score "
            f"FROM student_dashboard_view FINAL "
            f"WHERE {where}",
            parameters=params,
        )
        rows = result.result_rows
    except Exception as exc:  # noqa: BLE001
        logger.warning("query_teacher_dashboard_failed", error=str(exc), user_id=user_id)
        return None

    if not rows:
        return None

    total_classes, total_students, avg_score = rows[0]
    return {
        "userId": user_id,
        "totalClasses": int(total_classes or 0),
        "totalStudents": int(total_students or 0),
        "classAvgScore": float(avg_score or 0),
        "pendingHomeworkCount": 0,  # 从 homework_submissions CDC 计算
        "classes": [],
        "topStudents": [],
        "recentWarnings": [],
    }


# ===== 写入方法（CDC 消费专用） =====


async def upsert_student_dashboard(
    student_id: str,
    class_id: str,
    exam_id: str,
    subject_id: str,
    score: float,
    rank_in_class: int,
    knowledge_point_id: str,
    mastery_level: float,
    error_count: int,
    last_updated: datetime,
) -> bool:
    """写入/更新学生学情宽表（CDC 消费专用）."""
    client = get_client()
    if client is None:
        return False

    try:
        await asyncio.to_thread(
            client.insert,
            "student_dashboard_view",
            [
                [
                    student_id,
                    class_id,
                    exam_id,
                    subject_id,
                    score,
                    rank_in_class,
                    knowledge_point_id,
                    mastery_level,
                    error_count,
                    last_updated,
                ]
            ],
            column_names=[
                "student_id",
                "class_id",
                "exam_id",
                "subject_id",
                "score",
                "rank_in_class",
                "knowledge_point_id",
                "mastery_level",
                "error_count",
                "last_updated",
            ],
        )
        return True
    except Exception as exc:  # noqa: BLE001
        logger.warning("student_dashboard_upsert_failed", error=str(exc), student_id=student_id)
        return False


async def upsert_student_error(
    student_id: str,
    question_id: str,
    knowledge_point_id: str,
    error_count: int,
    last_error_time: datetime,
    content: str,
) -> bool:
    """写入/更新学生错题本（CDC 消费专用）."""
    client = get_client()
    if client is None:
        return False

    try:
        await asyncio.to_thread(
            client.insert,
            "student_errors",
            [[student_id, question_id, knowledge_point_id, error_count, last_error_time, content]],
            column_names=[
                "student_id",
                "question_id",
                "knowledge_point_id",
                "error_count",
                "last_error_time",
                "content",
            ],
        )
        return True
    except Exception as exc:  # noqa: BLE001
        logger.warning("student_error_upsert_failed", error=str(exc), student_id=student_id)
        return False


async def upsert_attendance_log(
    student_id: str,
    class_id: str,
    attendance_date,
    status: str,
    recorded_by: str,
    remark: str,
    occurred_at: datetime,
) -> bool:
    """写入考勤记录（CDC 消费专用）."""
    client = get_client()
    if client is None:
        return False

    try:
        await asyncio.to_thread(
            client.insert,
            "attendance_logs",
            [[student_id, class_id, attendance_date, status, recorded_by, remark, occurred_at]],
            column_names=[
                "student_id",
                "class_id",
                "attendance_date",
                "status",
                "recorded_by",
                "remark",
                "occurred_at",
            ],
        )
        return True
    except Exception as exc:  # noqa: BLE001
        logger.warning("attendance_log_upsert_failed", error=str(exc), student_id=student_id)
        return False


async def upsert_mastery_snapshot(
    student_id: str,
    knowledge_point_id: str,
    subject_id: str,
    mastery_level: float,
    calculated_at: datetime,
    calculation_method: str = "weighted_moving_avg",
) -> bool:
    """写入掌握度快照（掌握度计算后调用）."""
    client = get_client()
    if client is None:
        return False

    try:
        await asyncio.to_thread(
            client.insert,
            "mastery_snapshot",
            [
                [
                    student_id,
                    knowledge_point_id,
                    subject_id,
                    mastery_level,
                    calculated_at,
                    calculation_method,
                ]
            ],
            column_names=[
                "student_id",
                "knowledge_point_id",
                "subject_id",
                "mastery_level",
                "calculated_at",
                "calculation_method",
            ],
        )
        return True
    except Exception as exc:  # noqa: BLE001
        logger.warning("mastery_snapshot_upsert_failed", error=str(exc), student_id=student_id)
        return False


async def upsert_ai_usage_log(
    request_id: str,
    user_id: str,
    provider: str,
    model: str,
    prompt_tokens: int,
    completion_tokens: int,
    total_tokens: int,
    latency_ms: int,
    success: bool,
    cost_cents: int,
    occurred_at: datetime,
) -> bool:
    """写入 AI 用量记录（CDC/事件消费专用）."""
    client = get_client()
    if client is None:
        return False

    try:
        await asyncio.to_thread(
            client.insert,
            "ai_usage_log",
            [
                [
                    request_id,
                    user_id,
                    provider,
                    model,
                    prompt_tokens,
                    completion_tokens,
                    total_tokens,
                    latency_ms,
                    success,
                    cost_cents,
                    occurred_at,
                ]
            ],
            column_names=[
                "request_id",
                "user_id",
                "provider",
                "model",
                "prompt_tokens",
                "completion_tokens",
                "total_tokens",
                "latency_ms",
                "success",
                "cost_cents",
                "occurred_at",
            ],
        )
        return True
    except Exception as exc:  # noqa: BLE001
        logger.warning("ai_usage_log_upsert_failed", error=str(exc), request_id=request_id)
        return False


# ===== 查询历史成绩（掌握度计算用） =====


async def query_student_scores_by_kp(
    student_id: str,
    knowledge_point_id: str,
    limit: int = 5,
) -> list[dict] | None:
    """查询学生指定知识点的历史成绩（掌握度计算用，FINAL 去重）."""
    client = get_client()
    if client is None:
        return None

    try:
        result = await asyncio.to_thread(
            client.query,
            "SELECT score, last_updated, exam_id "
            "FROM student_dashboard_view FINAL "
            "WHERE student_id = {sid:String} AND knowledge_point_id = {kp:String} "
            "ORDER BY last_updated DESC "
            "LIMIT {lim:UInt32}",
            parameters={"sid": student_id, "kp": knowledge_point_id, "lim": limit},
        )
        rows = result.result_rows
    except Exception as exc:  # noqa: BLE001
        logger.warning("query_student_scores_by_kp_failed", error=str(exc), student_id=student_id)
        return None

    return [{"score": float(row[0] or 0), "timestamp": row[1], "exam_id": row[2]} for row in rows]
