"""GitHub connector — read and file issues via the GitHub REST API.

Writes (creating issues, commenting) are marked sensitive so they route through
the policy engine / approval inbox. Reads (fetching and listing issues) are not.
A ``base_url`` config makes it work against GitHub Enterprise as well.
"""

from __future__ import annotations

import httpx

from legate.agent.tooling import ToolContext, ToolResult, ToolSpec
from legate.connectors.base import BaseConnector, ConnectionResult
from legate.connectors.runtime import new_async_client

_DEFAULT_BASE = "https://api.github.com"
_API_VERSION = "2022-11-28"
_MAX_ITEMS = 50


class GitHubConnector(BaseConnector):
    type = "github"
    title = "GitHub"
    config_schema = {
        "type": "object",
        "properties": {
            "default_repo": {
                "type": "string",
                "description": "owner/repo used when none is given.",
            },
            "base_url": {"type": "string", "description": "API base; set for GitHub Enterprise."},
        },
    }
    secret_schema = {
        "type": "object",
        "properties": {"token": {"type": "string", "description": "Personal access token."}},
        "required": ["token"],
    }

    def list_tools(self) -> list[ToolSpec]:
        repo_prop = {"type": "string", "description": "owner/repo; defaults to the connector's."}
        return [
            ToolSpec(
                name="github.get_issue",
                description="Fetch a single issue (or pull request) by number.",
                parameters={
                    "type": "object",
                    "properties": {"repo": repo_prop, "number": {"type": "integer"}},
                    "required": ["number"],
                },
                is_sensitive=False,
                scopes=["github:read"],
            ),
            ToolSpec(
                name="github.list_issues",
                description="List issues in a repository.",
                parameters={
                    "type": "object",
                    "properties": {
                        "repo": repo_prop,
                        "state": {"type": "string", "description": "open | closed | all."},
                        "labels": {"type": "string", "description": "Comma-separated label names."},
                        "limit": {"type": "integer"},
                    },
                },
                is_sensitive=False,
                scopes=["github:read"],
            ),
            ToolSpec(
                name="github.create_issue",
                description="Open a new issue in a repository.",
                parameters={
                    "type": "object",
                    "properties": {
                        "repo": repo_prop,
                        "title": {"type": "string"},
                        "body": {"type": "string"},
                        "labels": {"type": "array"},
                    },
                    "required": ["title"],
                },
                is_sensitive=True,
                scopes=["github:write"],
            ),
            ToolSpec(
                name="github.comment_issue",
                description="Add a comment to an issue or pull request.",
                parameters={
                    "type": "object",
                    "properties": {
                        "repo": repo_prop,
                        "number": {"type": "integer"},
                        "body": {"type": "string"},
                    },
                    "required": ["number", "body"],
                },
                is_sensitive=True,
                scopes=["github:write"],
            ),
        ]

    def _base(self, config: dict) -> str:
        return (config.get("base_url") or _DEFAULT_BASE).rstrip("/")

    def _headers(self, secrets: dict) -> dict:
        return {
            "Authorization": f"Bearer {secrets.get('token', '')}",
            "Accept": "application/vnd.github+json",
            "X-GitHub-Api-Version": _API_VERSION,
        }

    def _repo(self, args: dict, config: dict) -> str | None:
        return args.get("repo") or config.get("default_repo")

    async def _request(
        self,
        method: str,
        url: str,
        secrets: dict,
        *,
        json: dict | None = None,
        params: dict | None = None,
    ) -> httpx.Response:
        async with new_async_client() as client:
            return await client.request(
                method, url, headers=self._headers(secrets), json=json, params=params
            )

    async def test_connection(self, config: dict, secrets: dict) -> ConnectionResult:
        try:
            resp = await self._request("GET", f"{self._base(config)}/user", secrets)
        except httpx.HTTPError as exc:
            return ConnectionResult(ok=False, message=f"connection failed: {exc}")
        if resp.status_code == 200:
            login = resp.json().get("login", "user")
            return ConnectionResult(ok=True, message=f"authenticated as {login}")
        return ConnectionResult(ok=False, message=f"HTTP {resp.status_code}")

    @staticmethod
    def _issue_summary(issue: dict) -> dict:
        return {
            "number": issue.get("number"),
            "title": issue.get("title"),
            "state": issue.get("state"),
            "url": issue.get("html_url"),
        }

    async def execute(
        self, tool: str, args: dict, config: dict, secrets: dict, ctx: ToolContext
    ) -> ToolResult:
        base = self._base(config)
        repo = self._repo(args, config)
        if tool != "github.get_issue" and not secrets.get("token"):
            return ToolResult(ok=False, error="missing token")
        if not repo:
            return ToolResult(ok=False, error="no repo specified (set default_repo or pass repo)")

        try:
            if tool == "github.get_issue":
                resp = await self._request(
                    "GET", f"{base}/repos/{repo}/issues/{int(args['number'])}", secrets
                )
                if resp.status_code != 200:
                    return ToolResult(ok=False, error=f"HTTP {resp.status_code}")
                return ToolResult(ok=True, data=self._issue_summary(resp.json()))

            if tool == "github.list_issues":
                params = {
                    "state": args.get("state", "open"),
                    "per_page": min(int(args.get("limit", 20)), _MAX_ITEMS),
                }
                if args.get("labels"):
                    params["labels"] = args["labels"]
                resp = await self._request(
                    "GET", f"{base}/repos/{repo}/issues", secrets, params=params
                )
                if resp.status_code != 200:
                    return ToolResult(ok=False, error=f"HTTP {resp.status_code}")
                issues = [self._issue_summary(i) for i in resp.json()]
                return ToolResult(ok=True, data={"issues": issues})

            if tool == "github.create_issue":
                payload: dict = {"title": args.get("title", "")}
                if args.get("body"):
                    payload["body"] = args["body"]
                if args.get("labels"):
                    payload["labels"] = args["labels"]
                resp = await self._request(
                    "POST", f"{base}/repos/{repo}/issues", secrets, json=payload
                )
                if resp.status_code not in (200, 201):
                    return ToolResult(ok=False, error=f"HTTP {resp.status_code}")
                return ToolResult(ok=True, data=self._issue_summary(resp.json()))

            if tool == "github.comment_issue":
                resp = await self._request(
                    "POST",
                    f"{base}/repos/{repo}/issues/{int(args['number'])}/comments",
                    secrets,
                    json={"body": args.get("body", "")},
                )
                if resp.status_code not in (200, 201):
                    return ToolResult(ok=False, error=f"HTTP {resp.status_code}")
                return ToolResult(
                    ok=True, data={"id": resp.json().get("id"), "url": resp.json().get("html_url")}
                )

            return ToolResult(ok=False, error=f"unknown github tool: {tool}")
        except httpx.HTTPError as exc:
            return ToolResult(ok=False, error=f"github request failed: {exc}")
