#!/usr/bin/env python3
"""
Gitea API 客户端封装 - Actions + PR + Webhook 相关接口

基于 urllib 实现，无第三方依赖，与 ci_dashboard.py 风格一致。
支持 token 和 basic auth 两种认证方式。
"""

import base64
import json
import sys
import urllib.error
import urllib.request

from . import config


class GiteaClient:
    """Gitea API 客户端"""

    def __init__(
        self,
        base_url=None,
        repo=None,
        token=None,
        username=None,
        password=None,
    ):
        self.base_url = (base_url or config.GITEA_URL).rstrip("/")
        self.repo = repo or config.GITEA_REPO
        self.token = token or config.GITEA_TOKEN
        self.username = username or config.GITEA_USERNAME
        self.password = password or config.GITEA_PASSWORD
        self.api_base = f"{self.base_url}/api/v1/repos/{self.repo}"

    def _request(self, path, method="GET", data=None):
        """通用 HTTP 请求

        Args:
            path: API 路径（相对于 /api/v1/repos/{repo}/）
            method: HTTP 方法
            data: 请求体（dict 或 bytes）

        Returns:
            解析后的 JSON 数据，失败返回 None
        """
        url = f"{self.api_base}/{path}"
        body = None
        if data is not None:
            if isinstance(data, (dict, list)):
                body = json.dumps(data).encode("utf-8")
            else:
                body = data if isinstance(data, bytes) else str(data).encode()

        req = urllib.request.Request(url, data=body, method=method)
        req.add_header("Content-Type", "application/json")

        if self.token:
            req.add_header("Authorization", f"token {self.token}")
        elif self.username and self.password:
            auth = base64.b64encode(f"{self.username}:{self.password}".encode()).decode()
            req.add_header("Authorization", f"Basic {auth}")

        try:
            with urllib.request.urlopen(req, timeout=30) as resp:
                resp_body = resp.read().decode()
                if not resp_body:
                    return {}
                return json.loads(resp_body)
        except urllib.error.HTTPError as e:
            err_body = ""
            try:
                err_body = e.read().decode()
            except Exception:
                pass
            print(
                f"[WARN] HTTP {e.code}: {url} - {err_body[:200]}",
                file=sys.stderr,
            )
            return None
        except Exception as e:
            print(f"[WARN] 请求失败 {url}: {e}", file=sys.stderr)
            return None

    # ── Actions: Workflow Runs ────────────────────────

    def list_runs(
        self,
        status=None,
        branch=None,
        event=None,
        workflow_id=None,
        page=1,
        limit=config.PAGE_LIMIT,
    ):
        """获取 workflow runs 列表

        Returns:
            (runs列表, 总数)
        """
        params = []
        if status:
            params.append(f"status={status}")
        if branch:
            params.append(f"branch={branch}")
        if event:
            params.append(f"event={event}")
        if workflow_id:
            params.append(f"workflow_id={workflow_id}")
        params.append(f"page={page}")
        params.append(f"limit={limit}")
        path = f"actions/runs?{'&'.join(params)}"
        data = self._request(path)
        if not data:
            return [], 0
        runs = data.get("workflow_runs", [])
        total = data.get("total_count", 0)
        return runs, total

    def get_run(self, run_id):
        """获取单个 run 详情"""
        return self._request(f"actions/runs/{run_id}")

    def get_run_jobs(self, run_id):
        """获取 run 的 jobs 列表"""
        data = self._request(f"actions/runs/{run_id}/jobs")
        if not data:
            return []
        return data.get("jobs", [])

    def get_job_log(self, run_id, job_id):
        """获取 job 日志（纯文本）"""
        url = f"{self.api_base}/actions/runs/{run_id}/jobs/{job_id}/logs"
        req = urllib.request.Request(url)
        if self.token:
            req.add_header("Authorization", f"token {self.token}")
        elif self.username and self.password:
            auth = base64.b64encode(f"{self.username}:{self.password}".encode()).decode()
            req.add_header("Authorization", f"Basic {auth}")
        try:
            with urllib.request.urlopen(req, timeout=30) as resp:
                return resp.read().decode("utf-8", errors="replace")
        except Exception as e:
            print(f"[WARN] 获取日志失败 job={job_id}: {e}", file=sys.stderr)
            return ""

    def rerun_run(self, run_id):
        """重新运行整个 workflow run"""
        return self._request(f"actions/runs/{run_id}/rerun", method="POST")

    def rerun_failed_jobs(self, run_id):
        """重新运行失败的 jobs"""
        return self._request(f"actions/runs/{run_id}/rerun-failed-jobs", method="POST")

    def cancel_run(self, run_id):
        """取消 run"""
        return self._request(f"actions/runs/{run_id}/cancel", method="POST")

    # ── Actions: Workflows ────────────────────────────

    def list_workflows(self):
        """获取 workflow 列表"""
        data = self._request("actions/workflows")
        if not data:
            return []
        return data.get("workflows", [])

    def get_workflow(self, workflow_id):
        """获取单个 workflow 详情"""
        return self._request(f"actions/workflows/{workflow_id}")

    # ── Pull Requests ─────────────────────────────────

    def get_pr(self, pr_number):
        """获取 PR 详情"""
        return self._request(f"pulls/{pr_number}")

    def get_pr_ci_runs(self, pr_number, limit=20):
        """获取 PR 关联的 CI runs（通过 head_sha 查询）"""
        pr = self.get_pr(pr_number)
        if not pr:
            return []
        head_sha = pr.get("head", {}).get("sha", "")
        if not head_sha:
            return []
        # 用 head_sha 过滤 runs
        runs, _ = self.list_runs(limit=limit)
        return [r for r in runs if r.get("head_sha", "") == head_sha]

    # ── 便捷方法 ──────────────────────────────────────

    def get_latest_run(self, branch, workflow_id=None, status=None):
        """获取指定分支最新的 run"""
        runs, _ = self.list_runs(branch=branch, workflow_id=workflow_id, status=status, limit=5)
        return runs[0] if runs else None

    def get_failed_jobs_summary(self, run_id, max_lines_per_job=30):
        """获取失败 job 的摘要信息（用于通知）

        Returns:
            list[dict]: 每个失败 job 的 {name, conclusion, failed_step, log_tail}
        """
        jobs = self.get_run_jobs(run_id)
        if not jobs:
            return []

        failed = [j for j in jobs if j.get("status") == "completed" and j.get("conclusion") == "failure"]
        if not failed:
            # 运行中的也返回，方便定位
            failed = [j for j in jobs if j.get("status") != "completed"]

        result = []
        for job in failed[:5]:  # 最多取 5 个失败 job
            job_id = job.get("id", "")
            name = job.get("name", "Unknown")
            conclusion = job.get("conclusion", job.get("status", "unknown"))

            # 找失败的 step
            failed_step = ""
            steps = job.get("steps", [])
            for step in steps:
                if step.get("conclusion") == "failure":
                    failed_step = step.get("name", "")
                    break

            # 取日志尾部
            log_tail = ""
            if job_id:
                log = self.get_job_log(run_id, job_id)
                if log:
                    lines = log.strip().splitlines()
                    log_tail = "\n".join(lines[-max_lines_per_job:])

            result.append(
                {
                    "name": name,
                    "conclusion": conclusion,
                    "failed_step": failed_step,
                    "log_tail": log_tail,
                    "job_id": job_id,
                }
            )
        return result
