# -*- coding: utf-8 -*-
"""
report_service.py — 巡检报告管理

报告存储：service_monitor/data/reports/{report_id}.json
提供：保存 / 读取 / 列表 / 导出(MD/JSON) / 过期清理
"""

from __future__ import annotations

import json
import logging
import secrets
import uuid
from datetime import datetime, timedelta
from typing import List, Optional

try:
    from pytz import timezone as pytz_timezone
    SHANGHAI_TZ = pytz_timezone("Asia/Shanghai")
except ImportError:
    SHANGHAI_TZ = None

from ..utils.paths import REPORTS_DIR, ensure_dirs

logger = logging.getLogger("service_monitor.report_service")

# 报告默认保留天数（可被配置覆盖）
DEFAULT_RETENTION_DAYS = 14

# Token 访问有效期（天）
ACCESS_TOKEN_DAYS = 7

NORMAL, WARNING, CRITICAL = "正常", "警告", "严重"


def _now() -> str:
    return datetime.now().strftime("%Y-%m-%dT%H:%M:%S")


def _report_path(report_id: str):
    return REPORTS_DIR / f"{report_id}.json"


def save(target: dict, suite: str, modules_result: list,
         started_at: str, finished_at: Optional[str] = None) -> str:
    """保存巡检报告，返回 report_id。

    modules_result: [
      {"id","name","category","items":[{key,name,value,threshold,status}], "summary":{...}}
    ]
    """
    ensure_dirs()
    report_id = datetime.now().strftime("%Y%m%d_%H%M%S_") + str(uuid.uuid4())[:6]

    # 汇总
    total = {NORMAL: 0, WARNING: 0, CRITICAL: 0, "total": 0}
    for m in modules_result:
        for it in m.get("items", []):
            total["total"] += 1
            st = it.get("status", NORMAL)
            if st in total:
                total[st] += 1

    # Token 过期时间
    now = datetime.now(SHANGHAI_TZ) if SHANGHAI_TZ else datetime.now()
    expires_at = (now + timedelta(days=ACCESS_TOKEN_DAYS)).isoformat()

    report = {
        "id": report_id,
        "target_id": target.get("id"),
        "target_name": target.get("name"),
        "target_type": target.get("type"),
        "suite": suite,
        "started_at": started_at,
        "finished_at": finished_at or _now(),
        "summary": total,
        "modules": modules_result,
        "access_token": f"rpt_{secrets.token_hex(32)}",
        "access_token_expires_at": expires_at,
    }

    _report_path(report_id).write_text(
        json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8"
    )
    logger.info("保存报告: %s (目标=%s, 套件=%s, 项=%d)",
                report_id, target.get("name"), suite, total["total"])
    return report_id


def get_report(report_id: str) -> Optional[dict]:
    """读取报告。"""
    p = _report_path(report_id)
    if not p.exists():
        return None
    try:
        return json.loads(p.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError) as e:
        logger.error("读取报告失败 %s: %s", report_id, e)
        return None


def list_reports(target_id: Optional[str] = None, limit: int = 50) -> List[dict]:
    """列出报告摘要（按时间倒序）。"""
    ensure_dirs()
    summaries = []
    for p in sorted(REPORTS_DIR.glob("*.json"), reverse=True):
        try:
            r = json.loads(p.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, OSError):
            continue
        if target_id and r.get("target_id") != target_id:
            continue
        summaries.append({
            "id": r.get("id"),
            "target_id": r.get("target_id"),
            "target_name": r.get("target_name"),
            "target_type": r.get("target_type"),
            "suite": r.get("suite"),
            "started_at": r.get("started_at"),
            "finished_at": r.get("finished_at"),
            "summary": r.get("summary", {}),
        })
        if len(summaries) >= limit:
            break
    return summaries


def delete_report(report_id: str) -> bool:
    """删除单份报告。"""
    p = _report_path(report_id)
    if p.exists():
        p.unlink()
        logger.info("删除报告: %s", report_id)
        return True
    return False


def cleanup_expired(retention_days: int = DEFAULT_RETENTION_DAYS) -> int:
    """清理过期报告，返回清理数量。"""
    ensure_dirs()
    cutoff = datetime.now() - timedelta(days=retention_days)
    removed = 0
    for p in REPORTS_DIR.glob("*.json"):
        try:
            r = json.loads(p.read_text(encoding="utf-8"))
            finished = r.get("finished_at") or r.get("started_at")
            if finished:
                dt = datetime.strptime(finished[:19], "%Y-%m-%dT%H:%M:%S")
                if dt < cutoff:
                    p.unlink()
                    removed += 1
        except (json.JSONDecodeError, OSError, ValueError):
            continue
    if removed:
        logger.info("清理过期报告 %d 份（保留 %d 天）", removed, retention_days)
    return removed


# ============================================================
# 导出
# ============================================================

def export_json(report_id: str) -> Optional[dict]:
    """导出 JSON（即报告本身）。"""
    return get_report(report_id)


def export_markdown(report_id: str) -> Optional[str]:
    """导出 Markdown 报告。"""
    r = get_report(report_id)
    if not r:
        return None

    s = r.get("summary", {})
    lines = [
        f"# 服务巡检报告",
        "",
        f"- **目标**：{r.get('target_name')}（{r.get('target_type')}）",
        f"- **套件**：{'快速巡检' if r.get('suite') == 'quick' else '全量巡检'}",
        f"- **开始时间**：{r.get('started_at')}",
        f"- **完成时间**：{r.get('finished_at')}",
        "",
        f"## 汇总",
        "",
        f"| 总项数 | 正常 | 警告 | 严重 |",
        f"|--------|------|------|------|",
        f"| {s.get('total', 0)} | {s.get(NORMAL, 0)} | {s.get(WARNING, 0)} | {s.get(CRITICAL, 0)} |",
        "",
    ]

    # 异常项置顶
    abnormal = []
    for m in r.get("modules", []):
        for it in m.get("items", []):
            if it.get("status") in (WARNING, CRITICAL):
                abnormal.append((m.get("name"), it))
    if abnormal:
        lines += ["## ⚠️ 异常项", "", "| 模块 | 检测项 | 当前值 | 阈值 | 状态 |",
                  "|------|--------|--------|------|------|"]
        for mod_name, it in abnormal:
            lines.append(
                f"| {mod_name} | {it.get('name')} | {it.get('value')} | "
                f"{it.get('threshold') or '-'} | {it.get('status')} |"
            )
        lines.append("")

    # 各模块明细
    lines += ["## 检测明细", ""]
    for m in r.get("modules", []):
        lines += [f"### {m.get('name')}", "",
                  "| 检测项 | 当前值 | 阈值 | 状态 |",
                  "|--------|--------|------|------|"]
        for it in m.get("items", []):
            lines.append(
                f"| {it.get('name')} | {it.get('value')} | "
                f"{it.get('threshold') or '-'} | {it.get('status')} |"
            )
        lines.append("")

    return "\n".join(lines)


# ============================================================
# Token 验证
# ============================================================

def validate_access_token(report_id: str, token: str) -> bool:
    """验证报告访问 token 是否有效。

    Args:
        report_id: 报告 ID
        token: 访问 token（rpt_xxx 格式）

    Returns:
        True=token 有效，False=无效或过期
    """
    if not token or not token.startswith("rpt_"):
        return False

    report = get_report(report_id)
    if not report:
        return False

    stored_token = report.get("access_token", "")
    if not stored_token or stored_token != token:
        return False

    expires_at_str = report.get("access_token_expires_at", "")
    if not expires_at_str:
        return False

    try:
        expires_at = datetime.fromisoformat(expires_at_str)
        now = datetime.now(SHANGHAI_TZ) if SHANGHAI_TZ else datetime.now()
        # 如果 expires_at 无时区信息，假设为本地时间
        if expires_at.tzinfo is None and now.tzinfo is not None:
            expires_at = expires_at.replace(tzinfo=now.tzinfo)
        elif expires_at.tzinfo is not None and now.tzinfo is None:
            now = now.replace(tzinfo=expires_at.tzinfo)
        if now > expires_at:
            return False
    except (ValueError, TypeError):
        return False

    return True
