# -*- coding: utf-8 -*-
"""
管理端 — 开发调试全链路日志路由。

端点清单（8 个，全部需要 JWT + admin 角色）：
- GET    /api/admin/dev-trace/dates            — 有日志数据的日期列表
- GET    /api/admin/dev-trace/requests         — 按条件分页查询请求列表
- GET    /api/admin/dev-trace/request/{id}     — 指定 request_id 的完整 trace
- POST   /api/admin/dev-trace/cleanup          — 按日期范围手动清理日志
- GET    /api/admin/dev-trace/settings         — 当前设置
- PUT    /api/admin/dev-trace/settings         — 更新运行时设置
- GET    /api/admin/dev-trace/coverage         — 最近一次覆盖率扫描结果
- POST   /api/admin/dev-trace/coverage/scan    — 手动触发覆盖率扫描
"""

from __future__ import annotations

import json
import logging
from datetime import datetime
from pathlib import Path
from typing import Any, Optional

from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel

from app.auth.dependencies import CurrentUser, get_current_user
from app.trace.cleanup import cleanup_date_range
from app.trace.config import get_trace_config

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/admin/dev-trace", tags=["开发调试日志"])

# 日期目录名格式
_DATE_FORMAT = "%Y-%m-%d"


# ── 权限依赖 ──────────────────────────────────────────────


def _require_admin():
    """管理端依赖：仅要求 JWT 认证通过。

    dev-trace 是开发调试工具，不涉及业务数据，无需检查业务角色
    （site_admin / tenant_admin）。只要是 admin-web 的已认证用户即可访问。
    """

    async def _dependency(
        user: CurrentUser = Depends(get_current_user),
    ) -> CurrentUser:
        return user

    return _dependency


# ── Pydantic 请求/响应模型 ────────────────────────────────


class CleanupRequest(BaseModel):
    """手动清理请求体。"""
    start_date: str
    end_date: str


class SettingsUpdate(BaseModel):
    """运行时设置更新请求体（所有字段可选）。"""
    enabled: Optional[bool] = None
    retention_days: Optional[int] = None
    log_sql: Optional[bool] = None
    log_params: Optional[bool] = None


# ── 辅助函数 ──────────────────────────────────────────────


def _get_base_dir() -> Path:
    """获取日志根目录 Path 对象。"""
    return Path(get_trace_config().log_dir)


def _is_date_dir(name: str) -> bool:
    """判断目录名是否为 YYYY-MM-DD 格式。"""
    try:
        datetime.strptime(name, _DATE_FORMAT)
        return True
    except ValueError:
        return False


def _read_jsonl_file(filepath: Path) -> list[dict[str, Any]]:
    """逐行读取 .jsonl 文件，跳过解析失败的行。"""
    records: list[dict[str, Any]] = []
    if not filepath.exists():
        return records
    with open(filepath, "r", encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            try:
                records.append(json.loads(line))
            except json.JSONDecodeError:
                continue
    return records


def _match_filter(record: dict[str, Any], **filters: Any) -> bool:
    """检查单条 trace 记录是否满足所有筛选条件。"""
    # trace_type
    if filters.get("trace_type") and record.get("trace_type") != filters["trace_type"]:
        return False

    # method
    if filters.get("method") and record.get("method", "").upper() != filters["method"].upper():
        return False

    # path_contains
    if filters.get("path_contains") and filters["path_contains"].lower() not in (record.get("path") or "").lower():
        return False

    # status_code
    if filters.get("status_code") is not None and record.get("status_code") != filters["status_code"]:
        return False

    # min_duration
    if filters.get("min_duration") is not None and (record.get("total_duration_ms") or 0) < filters["min_duration"]:
        return False

    # has_error
    if filters.get("has_error") is not None:
        has_err = record.get("error") is not None
        if filters["has_error"] != has_err:
            return False

    # span_type — 检查 spans 列表中是否包含指定类型
    if filters.get("span_type"):
        span_types = {s.get("span_type") for s in record.get("spans", [])}
        if filters["span_type"] not in span_types:
            return False

    # start_time / end_time — 基于 record.timestamp
    ts_str = record.get("timestamp", "")
    if ts_str and (filters.get("start_time") or filters.get("end_time")):
        try:
            rec_dt = datetime.fromisoformat(ts_str)
            rec_time = rec_dt.time()
            if filters.get("start_time") and rec_time < filters["start_time"]:
                return False
            if filters.get("end_time") and rec_time > filters["end_time"]:
                return False
        except (ValueError, TypeError):
            pass

    return True


# ── 1. GET /dates — 有日志数据的日期列表 ─────────────────


@router.get("/dates")
async def list_dates(
    user: CurrentUser = Depends(_require_admin()),
) -> dict[str, list[str]]:
    """返回有日志数据的日期列表（降序排列）。"""
    base = _get_base_dir()
    if not base.exists():
        return {"dates": []}

    dates = sorted(
        [d.name for d in base.iterdir() if d.is_dir() and _is_date_dir(d.name)],
        reverse=True,
    )
    return {"dates": dates}


# ── 2. GET /requests — 按条件分页查询请求列表 ─────────────


@router.get("/requests")
async def list_requests(
    date: str = Query(..., description="日期，格式 YYYY-MM-DD"),
    start_time: Optional[str] = Query(None, description="起始时间 HH:MM:SS"),
    end_time: Optional[str] = Query(None, description="结束时间 HH:MM:SS"),
    trace_type: Optional[str] = Query(None, description="trace 类型：http/sse/ws/job"),
    method: Optional[str] = Query(None, description="HTTP 方法：GET/POST/PUT/DELETE"),
    path_contains: Optional[str] = Query(None, description="路径关键词"),
    status_code: Optional[int] = Query(None, description="HTTP 状态码"),
    min_duration: Optional[float] = Query(None, description="最小耗时（ms）"),
    has_error: Optional[bool] = Query(None, description="是否有错误"),
    span_type: Optional[str] = Query(None, description="包含的 span 类型"),
    page: int = Query(1, ge=1, description="页码"),
    page_size: int = Query(50, ge=1, le=200, description="每页条数"),
    user: CurrentUser = Depends(_require_admin()),
) -> dict[str, Any]:
    """按条件分页查询指定日期的请求列表。"""
    # 解析时间参数
    parsed_start = None
    parsed_end = None
    if start_time:
        try:
            parsed_start = datetime.strptime(start_time, "%H:%M:%S").time()
        except ValueError:
            raise HTTPException(status_code=400, detail="start_time 格式无效，应为 HH:MM:SS")
    if end_time:
        try:
            parsed_end = datetime.strptime(end_time, "%H:%M:%S").time()
        except ValueError:
            raise HTTPException(status_code=400, detail="end_time 格式无效，应为 HH:MM:SS")

    # 读取指定日期目录下所有 .jsonl 文件
    date_dir = _get_base_dir() / date
    if not date_dir.exists() or not date_dir.is_dir():
        return {"items": [], "total": 0, "page": page, "page_size": page_size}

    all_records: list[dict[str, Any]] = []
    for f in sorted(date_dir.glob("*.jsonl")):
        all_records.extend(_read_jsonl_file(f))

    # 过滤
    filtered = [
        r for r in all_records
        if _match_filter(
            r,
            trace_type=trace_type,
            method=method,
            path_contains=path_contains,
            status_code=status_code,
            min_duration=min_duration,
            has_error=has_error,
            span_type=span_type,
            start_time=parsed_start,
            end_time=parsed_end,
        )
    ]

    # 按时间降序排列
    filtered.sort(key=lambda r: r.get("timestamp", ""), reverse=True)

    total = len(filtered)
    start_idx = (page - 1) * page_size
    items = filtered[start_idx : start_idx + page_size]

    # 列表项不返回完整 spans，只返回摘要字段
    summary_items = []
    for item in items:
        summary_items.append({
            "request_id": item.get("request_id"),
            "trace_type": item.get("trace_type"),
            "timestamp": item.get("timestamp"),
            "method": item.get("method"),
            "path": item.get("path"),
            "status_code": item.get("status_code"),
            "total_duration_ms": item.get("total_duration_ms"),
            "user_id": item.get("user_id"),
            "site_id": item.get("site_id"),
            "db_query_count": item.get("db_query_count"),
            "db_total_ms": item.get("db_total_ms"),
            "error": item.get("error"),
            "span_count": len(item.get("spans", [])),
        })

    return {"items": summary_items, "total": total, "page": page, "page_size": page_size}


# ── 3. GET /request/{request_id} — 完整 trace 记录 ──────


@router.get("/request/{request_id}")
async def get_request_detail(
    request_id: str,
    user: CurrentUser = Depends(_require_admin()),
) -> dict[str, Any]:
    """返回指定 request_id 的完整 trace 记录（含所有 spans）。"""
    base = _get_base_dir()
    if not base.exists():
        raise HTTPException(status_code=404, detail="未找到该 request_id 的 trace 记录")

    # 搜索所有日期目录下的 .jsonl 文件
    for date_dir in sorted(base.iterdir(), reverse=True):
        if not date_dir.is_dir() or not _is_date_dir(date_dir.name):
            continue
        for f in date_dir.glob("*.jsonl"):
            for record in _read_jsonl_file(f):
                if record.get("request_id") == request_id:
                    return record

    raise HTTPException(status_code=404, detail="未找到该 request_id 的 trace 记录")


# ── 4. POST /cleanup — 按日期范围手动清理 ────────────────


@router.post("/cleanup")
async def cleanup_logs(
    body: CleanupRequest,
    user: CurrentUser = Depends(_require_admin()),
) -> dict[str, Any]:
    """按日期范围手动清理日志目录。"""
    # 校验日期格式
    try:
        datetime.strptime(body.start_date, _DATE_FORMAT)
        datetime.strptime(body.end_date, _DATE_FORMAT)
    except ValueError:
        raise HTTPException(status_code=400, detail="日期格式无效，应为 YYYY-MM-DD")

    if body.start_date > body.end_date:
        raise HTTPException(status_code=400, detail="start_date 不能晚于 end_date")

    result = cleanup_date_range(body.start_date, body.end_date)
    return {
        "deleted_dates": result["deleted_dirs"],
        "deleted_files": result["deleted_count"],
    }


# ── 5. GET /settings — 当前设置 ──────────────────────────


@router.get("/settings")
async def get_settings(
    user: CurrentUser = Depends(_require_admin()),
) -> dict[str, Any]:
    """返回当前 trace 运行时设置。"""
    return get_trace_config().get_settings()


# ── 6. PUT /settings — 更新运行时设置 ────────────────────


@router.put("/settings")
async def update_settings(
    body: SettingsUpdate,
    user: CurrentUser = Depends(_require_admin()),
) -> dict[str, Any]:
    """更新 trace 运行时设置（不需重启，重启后回退到 .env 值）。"""
    cfg = get_trace_config()
    cfg.update_settings(
        enabled=body.enabled,
        retention_days=body.retention_days,
        log_sql=body.log_sql,
        log_params=body.log_params,
    )
    return cfg.get_settings()


# ── 7. GET /coverage — 最近一次覆盖率扫描结果 ────────────


@router.get("/coverage")
async def get_coverage(
    user: CurrentUser = Depends(_require_admin()),
) -> dict[str, Any]:
    """返回最近一次覆盖率扫描结果（缓存）。"""
    from app.trace.coverage import get_cached_coverage, run_coverage_scan

    result = get_cached_coverage()
    if result is None:
        # 首次访问时自动扫描一次
        result = run_coverage_scan()
    return result


# ── 8. POST /coverage/scan — 手动触发覆盖率扫描 ──────────


@router.post("/coverage/scan")
async def trigger_coverage_scan(
    user: CurrentUser = Depends(_require_admin()),
) -> dict[str, Any]:
    """手动触发覆盖率扫描，返回最新结果。"""
    from app.trace.coverage import run_coverage_scan

    return run_coverage_scan()
