"""
Weekly Report Service - Phase 21.3 定期報告
============================================

整合 StatsService + K3sMonitorService，生成週報並推送 Telegram。

數據來源:
- StatsService (告警/AI 效能統計)
- K3sMonitorService (K3s 健康)
- Git API (開發活動)

符合 leWOOOgo 積木化規範:
- Service 層封裝所有邏輯
- 透過 DI 注入依賴

@author Claude Code (首席架構師)
@version 1.0.0
@date 2026-03-31 (台北時間)
@see ADR-041-periodic-reporting-architecture.md
"""

import asyncio
import json
import subprocess
from datetime import datetime, timedelta
from pathlib import Path
from urllib.parse import quote, urlencode
from urllib.request import Request, urlopen
from typing import Protocol, runtime_checkable

import structlog
from zoneinfo import ZoneInfo

from src.core.config import settings
from src.services.stats_service import StatsService, get_stats_service
from src.services.k3s_monitor_service import K3sMonitorService, get_k3s_monitor_service
from src.services.telegram_gateway import WeeklyReportMessage, get_telegram_gateway

logger = structlog.get_logger(__name__)

# 台北時區
TZ_TAIPEI = ZoneInfo("Asia/Taipei")


# =============================================================================
# Protocol (Interface)
# =============================================================================


@runtime_checkable
class IWeeklyReportService(Protocol):
    """
    週報服務介面

    Phase 21.3: 定義 Protocol 供依賴注入
    """

    async def generate_report(self) -> WeeklyReportMessage:
        """生成週報"""
        ...

    async def send_weekly_report(self) -> bool:
        """發送週報"""
        ...


# =============================================================================
# Implementation
# =============================================================================


class WeeklyReportService:
    """
    週報服務實作

    整合多個數據來源生成週報
    """

    def __init__(
        self,
        stats_service: StatsService | None = None,
        k3s_monitor: K3sMonitorService | None = None,
    ):
        self._stats_service = stats_service or get_stats_service()
        self._k3s_monitor = k3s_monitor or get_k3s_monitor_service()

    def _get_week_range(self) -> tuple[str, datetime, datetime]:
        """取得本週範圍"""
        now = datetime.now(TZ_TAIPEI)
        # 找到本週一
        monday = now - timedelta(days=now.weekday())
        monday = monday.replace(hour=0, minute=0, second=0, microsecond=0)
        # 本週日
        sunday = monday + timedelta(days=6, hours=23, minutes=59, seconds=59)

        # ISO 週次
        week_num = now.isocalendar()[1]
        week_range = f"{now.year}-W{week_num:02d}"

        return week_range, monday, sunday

    def _get_git_stats(self, since: datetime) -> tuple[int, int, bool]:
        """取得 Git 統計 (commits, deploys)"""
        git_cwd = "/app"
        if not self._has_local_git_worktree(git_cwd):
            return self._get_gitea_commit_stats(since)
        try:
            # 取得本週 commits 數量
            since_str = since.strftime("%Y-%m-%d")
            result = subprocess.run(
                ["git", "log", f"--since={since_str}", "--oneline"],
                capture_output=True,
                text=True,
                timeout=10,
                cwd=git_cwd,  # K8s 容器內的工作目錄
            )
            if result.returncode != 0:
                logger.warning(
                    "git_stats_commits_failed",
                    returncode=result.returncode,
                    stderr=result.stderr[-300:],
                )
                return self._get_gitea_commit_stats(since)
            commits = len(result.stdout.strip().split("\n")) if result.stdout.strip() else 0

            # 取得部署次數 (計算含 "deploy" 或 "CD" 的 commits)
            result_deploy = subprocess.run(
                ["git", "log", f"--since={since_str}", "--oneline", "--grep=deploy", "--grep=CD", "-i"],
                capture_output=True,
                text=True,
                timeout=10,
                cwd=git_cwd,
            )
            if result_deploy.returncode != 0:
                logger.warning(
                    "git_stats_deploys_failed",
                    returncode=result_deploy.returncode,
                    stderr=result_deploy.stderr[-300:],
                )
                return self._get_gitea_commit_stats(since)
            deploys = len(result_deploy.stdout.strip().split("\n")) if result_deploy.stdout.strip() else 0

            return commits, deploys, True
        except Exception as e:
            logger.warning("git_stats_failed", error=str(e))
            return self._get_gitea_commit_stats(since)

    def _has_local_git_worktree(self, cwd: str) -> bool:
        """確認容器內是否真的保留 Git metadata。"""
        return Path(cwd, ".git").exists()

    def _get_gitea_commit_stats(self, since: datetime) -> tuple[int, int, bool]:
        """從 Gitea commits API 讀取週報開發活動統計。"""
        owner = quote(str(settings.GITEA_REPO_OWNER), safe="")
        repo = quote(str(settings.GITEA_REPO_NAME), safe="")
        since_utc = since.astimezone(ZoneInfo("UTC")).isoformat().replace("+00:00", "Z")
        header_candidates = self._gitea_read_headers()

        commits = 0
        deploys = 0
        page = 1
        limit = 50
        max_pages = 1
        try:
            for api_url in self._gitea_api_candidates():
                for headers in header_candidates:
                    commits = deploys = 0
                    page = 1
                    total_commits: int | None = None
                    try:
                        while page <= max_pages:
                            query = urlencode({
                                "sha": "main",
                                "since": since_utc,
                                "page": page,
                                "limit": limit,
                            })
                            url = f"{api_url}/api/v1/repos/{owner}/{repo}/commits?{query}"
                            request = Request(url, headers=headers)
                            with urlopen(request, timeout=1) as response:
                                total_header = response.headers.get("X-Total-Count") or response.headers.get("X-Total")
                                if total_header and total_header.isdigit():
                                    total_commits = int(total_header)
                                payload = json.loads(response.read().decode("utf-8"))
                            if not isinstance(payload, list):
                                logger.warning("gitea_git_stats_unexpected_payload", payload_type=type(payload).__name__)
                                break
                            commits += len(payload)
                            for item in payload:
                                commit = item.get("commit") if isinstance(item, dict) else {}
                                message = str((commit or {}).get("message") or "")
                                subject = message.splitlines()[0].lower()
                                if "deploy" in subject or subject.startswith("chore(cd):"):
                                    deploys += 1
                            if len(payload) < limit:
                                return total_commits or commits, deploys, True
                            page += 1
                        if total_commits is not None and total_commits > 0:
                            logger.info(
                                "gitea_git_stats_sampled",
                                total_commits=total_commits,
                                sampled_pages=max_pages,
                                sampled_deploys=deploys,
                            )
                            return total_commits, deploys, True
                    except Exception as candidate_exc:
                        logger.warning(
                            "gitea_git_stats_candidate_failed",
                            api_url=api_url,
                            auth_mode="token" if "Authorization" in headers else "anonymous",
                            error=str(candidate_exc),
                        )
                        continue
        except Exception as exc:
            logger.warning("gitea_git_stats_failed", error=str(exc))
        return 0, 0, False

    def _gitea_api_candidates(self) -> list[str]:
        """取得 Git 統計可用的 Gitea read-only API base URLs。"""
        candidates = ["https://gitea.wooo.work", settings.GITEA_API_URL.rstrip("/")]
        deduped: list[str] = []
        for candidate in candidates:
            if candidate and candidate not in deduped:
                deduped.append(candidate)
        return deduped

    def _gitea_read_headers(self) -> list[dict[str, str]]:
        """取得 Gitea read-only 統計用 headers，匿名讀優先。"""
        base = {"Accept": "application/json", "User-Agent": "awoooi-weekly-report/1.0"}
        if not settings.GITEA_API_TOKEN:
            return [base]
        token_headers = {**base, "Authorization": f"token {settings.GITEA_API_TOKEN}"}
        return [base, token_headers]

    async def generate_report(self) -> WeeklyReportMessage:
        """
        生成週報

        整合多個數據來源
        """
        now = datetime.now(TZ_TAIPEI)
        week_range, monday, sunday = self._get_week_range()
        report_date = now.strftime("%Y-%m-%d %H:%M")

        # 取得統計數據 (7 天)
        stats_source_ok = True
        try:
            incident_summary = await self._stats_service.get_incident_summary(days=7)
            resolution_stats = await self._stats_service.get_resolution_stats(days=7)
            ai_performance = await self._stats_service.get_ai_performance(days=7)
        except Exception as e:
            logger.warning("stats_fetch_failed", error=str(e))
            stats_source_ok = False
            incident_summary = {}
            resolution_stats = {}
            ai_performance = {}

        # 取得 K3s 狀態
        k3s_source_ok = True
        try:
            k3s_status = await self._k3s_monitor.collect_cluster_status()
        except Exception as e:
            logger.warning("k3s_fetch_failed", error=str(e))
            k3s_source_ok = False
            k3s_status = None

        # 取得 Git 統計
        commits, deploys, git_source_ok = self._get_git_stats(monday)

        # 計算指標
        total_incidents = incident_summary.get("total_incidents", 0)
        resolved_rate = incident_summary.get("resolved_rate", 0.0)

        # 從嚴重度分佈中取得 Critical 數量
        severity_dist = incident_summary.get("severity_distribution", [])
        critical_count = sum(
            s.get("count", 0) for s in severity_dist
            if s.get("severity", "").upper() in ["P0", "CRITICAL"]
        )

        # AI 效能
        ai_proposals = ai_performance.get("total_proposals", 0)
        ai_executed = ai_performance.get("executed_count", 0)
        ai_success_rate = ai_performance.get("success_rate", 0.0)

        # 平均回應時間
        avg_response = resolution_stats.get("avg_minutes") or 0.0

        # K3s 指標
        k3s_uptime = 99.9  # 假設值，實際應從 Prometheus 取得
        pod_restarts = k3s_status.pod_restart_48h if k3s_status else 0
        hpa_events = 0  # 需要從 Prometheus 取得 HPA 事件

        # 2026-04-07 Claude Code: Sprint 4 F1 — 取得處置分佈
        disp_auto = disp_human = disp_manual = disp_cold = disp_total = 0
        try:
            from src.services.anomaly_counter import get_anomaly_counter
            counter = get_anomaly_counter()
            disp_summary, _ = await counter.get_all_disposition_stats()
            disp_auto = disp_summary.get("auto_repair", 0)
            disp_human = disp_summary.get("human_approved", 0)
            disp_manual = disp_summary.get("manual_resolved", 0)
            disp_cold = disp_summary.get("cold_start_trust", 0)
            disp_total = disp_summary.get("total", 0)
        except Exception as _disp_e:
            logger.warning("weekly_report_disposition_failed", error=str(_disp_e))

        ai_slo_source_ok = True
        ai_slo_auto_execute_success_rate: float | None = None
        ai_slo_auto_execute_sample_count = 0
        ai_slo_auto_execute_threshold = 0.85
        ai_slo_auto_execute_violated = False
        ai_slo_top_failure = ""
        ai_slo_verifier_coverage_rate: float | None = None
        ai_slo_unverified_auto_count = 0
        try:
            from src.services.adr100_slo_status_service import (
                get_adr100_slo_status_service,
            )
            from src.services.ai_slo_calculator import AiSloCalculator

            slo_report = await AiSloCalculator(project_id="awoooi").calculate()
            auto_metric = next(
                (metric for metric in slo_report.metrics if metric.name == "auto_execute_success_rate"),
                None,
            )
            if auto_metric is not None:
                ai_slo_auto_execute_success_rate = auto_metric.value
                ai_slo_auto_execute_sample_count = auto_metric.sample_count
                ai_slo_auto_execute_threshold = auto_metric.threshold
                ai_slo_auto_execute_violated = auto_metric.violated

            diagnostics = slo_report.diagnostics.get("auto_execute_success_rate") or {}
            top_failure = (diagnostics.get("top_failure_groups") or [{}])[0]
            if top_failure:
                ai_slo_top_failure = (
                    f"{top_failure.get('alertname') or 'unknown'} / "
                    f"{top_failure.get('playbook_id') or 'unknown'} ×"
                    f"{int(top_failure.get('count') or 0)}: "
                    f"{str(top_failure.get('error_signature') or '')[:90]}"
                )

            adr100_report = await get_adr100_slo_status_service("awoooi").fetch_report()
            verification = adr100_report.get("verification_coverage") or {}
            ai_slo_verifier_coverage_rate = verification.get("coverage_rate")
            ai_slo_unverified_auto_count = int(verification.get("unverified_auto") or 0)
        except Exception as _slo_e:
            ai_slo_source_ok = False
            logger.warning("weekly_report_ai_slo_failed", error=str(_slo_e))

        report_source_confidence = 0
        report_source_ok = 0
        report_source_total = 0
        report_source_gap_ids: list[str] = []
        report_asset_state_lines: list[str] = []
        try:
            from src.services.ai_agent_report_source_health import build_ai_agent_report_source_health

            source_health = await build_ai_agent_report_source_health(days=7)
            source_rollups = source_health.get("rollups") or {}
            report_source_confidence = int(source_rollups.get("confidence_percent") or 0)
            report_source_ok = int(source_rollups.get("source_ok_count") or 0)
            report_source_total = int(source_rollups.get("source_count") or 0)
            report_source_gap_ids = [
                str(source.get("work_item_id"))
                for source in source_health.get("source_health", [])
                if source.get("work_item_id")
            ][:5]
            report_asset_state_lines = [
                (
                    f"{asset.get('label')}: {asset.get('state')} "
                    f"{int(asset.get('done_count') or 0)}/"
                    f"{int(asset.get('done_count') or 0) + int(asset.get('blocked_count') or 0)}"
                )
                for asset in source_health.get("automation_assets", [])
            ][:5]
        except Exception as _source_e:
            logger.warning("weekly_report_source_health_failed", error=str(_source_e))

        # 組裝週報
        report = WeeklyReportMessage(
            week_range=week_range,
            report_date=report_date,
            alert_total=total_incidents,
            alert_critical=critical_count,
            alert_resolved=int(total_incidents * resolved_rate / 100) if total_incidents > 0 else 0,
            resolved_rate=resolved_rate,
            ai_proposal_count=ai_proposals,
            ai_executed_count=ai_executed,
            ai_success_rate=ai_success_rate,
            avg_response_minutes=avg_response,
            k3s_uptime_pct=k3s_uptime,
            pod_restart_total=pod_restarts,
            hpa_scale_events=hpa_events,
            commits_count=commits,
            deploy_count=deploys,
            ai_cost_week=0.0,  # 需要從 AI 成本追蹤取得
            ai_tokens_week=0,  # 需要從 AI 成本追蹤取得
            disposition_auto=disp_auto,
            disposition_human=disp_human,
            disposition_manual=disp_manual,
            disposition_cold_start=disp_cold,
            disposition_total=disp_total,
            stats_source_ok=stats_source_ok,
            k3s_source_ok=k3s_source_ok,
            git_source_ok=git_source_ok,
            cost_source_ok=False,
            ai_slo_source_ok=ai_slo_source_ok,
            ai_slo_auto_execute_success_rate=ai_slo_auto_execute_success_rate,
            ai_slo_auto_execute_sample_count=ai_slo_auto_execute_sample_count,
            ai_slo_auto_execute_threshold=ai_slo_auto_execute_threshold,
            ai_slo_auto_execute_violated=ai_slo_auto_execute_violated,
            ai_slo_top_failure=ai_slo_top_failure,
            ai_slo_verifier_coverage_rate=ai_slo_verifier_coverage_rate,
            ai_slo_unverified_auto_count=ai_slo_unverified_auto_count,
            all_zero_actionable_anomaly=(
                total_incidents == 0
                and ai_proposals == 0
                and ai_executed == 0
                and commits == 0
                and deploys == 0
                and disp_total == 0
            ),
            report_source_confidence_percent=report_source_confidence,
            report_source_ok_count=report_source_ok,
            report_source_total_count=report_source_total,
            report_source_gap_ids=report_source_gap_ids,
            report_asset_state_lines=report_asset_state_lines,
        )

        logger.info(
            "weekly_report_generated",
            week=week_range,
            alerts=total_incidents,
            commits=commits,
        )

        return report

    async def send_weekly_report(self) -> bool:
        """
        發送週報

        生成週報並推送到 Telegram
        """
        try:
            # 生成週報
            report = await self.generate_report()

            # 取得 Telegram Gateway
            gateway = get_telegram_gateway()
            if not gateway._initialized:
                await gateway.initialize()

            # 發送訊息
            formatted = report.format()
            result = await gateway.send_text(formatted)

            if result:
                logger.info("weekly_report_sent", week=report.week_range)
                return True
            else:
                logger.error("weekly_report_failed", week=report.week_range)
                return False

        except Exception as e:
            logger.error("weekly_report_error", error=str(e))
            return False


# =============================================================================
# Dependency Injection
# =============================================================================

_weekly_report_service: WeeklyReportService | None = None


def get_weekly_report_service() -> WeeklyReportService:
    """取得 WeeklyReportService 實例 (Singleton)"""
    global _weekly_report_service
    if _weekly_report_service is None:
        _weekly_report_service = WeeklyReportService()
    return _weekly_report_service


# =============================================================================
# CLI Entry Point (for CronJob)
# =============================================================================


async def main():
    """
    CLI 入口點

    供 K8s CronJob 調用:
    python -m src.services.weekly_report_service
    """
    service = get_weekly_report_service()
    success = await service.send_weekly_report()
    return 0 if success else 1


if __name__ == "__main__":
    import sys
    exit_code = asyncio.run(main())
    sys.exit(exit_code)
