"""GitHub issue handler."""

import os
from datetime import datetime
from typing import Self

from tenacity import (
    retry,
    retry_if_exception_type,
    stop_after_attempt,
    wait_exponential,
)

from code_reviewer.errors import AuthenticationFailed, InvalidResponse, NetworkError

from ..._cli import CLIClient
from ..._http import HttpClient
from ..._transport import APIClient
from ...logging import PlatformLogger
from ..handler import IssueHandler
from ..schema import Issue, IssueComment, LinkedIssue, LinkedIssueRelation

# Module-level platform logger for GitHub adaptor
logger = PlatformLogger("github")


class GitHubIssueHandler(IssueHandler):
    """GitHub issue handler."""

    def __init__(self, api_url: str, client: APIClient):
        """
        Initialize GitHub issue handler.

        Args:
            api_url: GitHub API base URL (e.g., "https://api.github.com")
            client: API client (HttpClient or CLIClient)
        """
        self.api_url = api_url
        self.http = client

    @classmethod
    def from_token(cls, api_url: str, token: str) -> Self:
        """Create handler with HTTP token auth."""
        client = HttpClient(token=token, platform="GitHub", auth_header="Authorization")
        return cls(api_url, client)

    @classmethod
    def from_cli(cls, api_url: str = "https://api.github.com") -> Self:
        """Create handler using gh CLI for auth."""
        client = CLIClient(binary="gh", api_url=api_url)
        return cls(api_url, client)

    @classmethod
    def from_env(cls) -> Self:
        """
        Create GitHub issue handler from environment variables.

        Requires GITHUB_API_TOKEN or GITHUB_TOKEN environment variable.
        Optionally uses GITHUB_API_URL (defaults to https://api.github.com).

        Returns:
            GitHubIssueHandler instance

        Raises:
            AuthenticationFailed: If required environment variables are missing
        """
        api_url = os.getenv("GITHUB_API_URL", "https://api.github.com")
        token = os.getenv("GITHUB_API_TOKEN") or os.getenv("GITHUB_TOKEN")

        if not token:
            raise AuthenticationFailed(
                "Missing GITHUB_API_TOKEN or GITHUB_TOKEN environment variable"
            )

        return cls.from_token(api_url, token)

    def _headers(self) -> dict[str, str]:
        """Get common request headers (non-auth)."""
        return {
            "User-Agent": "Reviewate",
            "Accept": "application/vnd.github.v3+json",
        }

    def _parse_datetime(self, dt_str: str) -> datetime:
        """Parse ISO datetime string."""
        try:
            return datetime.fromisoformat(dt_str.replace("Z", "+00:00"))
        except ValueError as e:
            raise InvalidResponse(f"Invalid date format: {e}") from e

    @retry(
        retry=retry_if_exception_type((NetworkError, InvalidResponse)),
        stop=stop_after_attempt(3),
        wait=wait_exponential(multiplier=10, min=1, max=100),
        reraise=True,
    )
    async def fetch_issue(self, repo: str, issue_number: int) -> Issue:
        """Fetch a GitHub issue.

        Retries on network errors with exponential backoff: 1s, 10s, 100s (max 3 attempts).
        """
        logger.info(f"Fetching GitHub issue {repo}#{issue_number}")
        response = await self.http.get(
            f"{self.api_url}/repos/{repo}/issues/{issue_number}",
            headers=self._headers(),
        )
        issue_data = response.json()
        # Parse to platform-agnostic Issue
        return Issue(
            id=str(issue_data.get("id", "")),
            number=issue_data.get("number", 0),
            title=issue_data.get("title", ""),
            description=issue_data.get("body", ""),
            state=issue_data.get("state", ""),
            labels=[label["name"] for label in issue_data.get("labels", [])],
            created_at=self._parse_datetime(issue_data.get("created_at", "")),
            updated_at=self._parse_datetime(issue_data.get("updated_at", "")),
            web_url=issue_data.get("html_url", ""),
        )

    @retry(
        retry=retry_if_exception_type((NetworkError, InvalidResponse)),
        stop=stop_after_attempt(3),
        wait=wait_exponential(multiplier=10, min=1, max=100),
        reraise=True,
    )
    async def fetch_issue_comments(
        self, repo: str, issue_number: int, limit: int = 50
    ) -> list[IssueComment]:
        """Fetch comments for a GitHub issue.

        Retries on network errors with exponential backoff: 1s, 10s, 100s (max 3 attempts).
        """
        logger.info(f"Fetching comments for GitHub issue {repo}#{issue_number}")
        response = await self.http.get(
            f"{self.api_url}/repos/{repo}/issues/{issue_number}/comments",
            headers=self._headers(),
            params={"per_page": limit},
        )
        comments_data = response.json()

        comments = []
        for comment in comments_data:
            # Skip bot comments
            user = comment.get("user", {})
            if user.get("type") == "Bot":
                continue

            comments.append(
                IssueComment(
                    id=str(comment.get("id", "")),
                    author=user.get("login", "unknown"),
                    body=comment.get("body", ""),
                    created_at=self._parse_datetime(comment.get("created_at", "")),
                    updated_at=self._parse_datetime(comment.get("updated_at", "")),
                )
            )

        return comments

    @retry(
        retry=retry_if_exception_type((NetworkError, InvalidResponse)),
        stop=stop_after_attempt(3),
        wait=wait_exponential(multiplier=10, min=1, max=100),
        reraise=True,
    )
    async def fetch_linked_issues(self, repo: str, issue_number: int) -> list[LinkedIssue]:
        """Fetch issues linked to this issue via GitHub timeline API.

        Uses the timeline API to find cross-referenced events.
        Retries on network errors with exponential backoff: 1s, 10s, 100s (max 3 attempts).
        """
        logger.info(f"Fetching linked issues for GitHub issue {repo}#{issue_number}")
        response = await self.http.get(
            f"{self.api_url}/repos/{repo}/issues/{issue_number}/timeline",
            headers={
                **self._headers(),
                "Accept": "application/vnd.github.mockingbird-preview+json",
            },
        )
        events = response.json()

        linked = []
        seen_ids: set[int] = set()

        for event in events:
            if event.get("event") != "cross-referenced":
                continue

            source = event.get("source", {})
            issue_data = source.get("issue", {})

            if not issue_data:
                continue

            # Skip pull requests — they appear under source.issue with a "pull_request" key
            if "pull_request" in issue_data:
                continue

            issue_id = issue_data.get("id")
            if issue_id in seen_ids:
                continue
            seen_ids.add(issue_id)

            try:
                issue = Issue(
                    id=str(issue_data.get("id", "")),
                    number=issue_data.get("number", 0),
                    title=issue_data.get("title", ""),
                    description=issue_data.get("body", "") or "",
                    state=issue_data.get("state", ""),
                    labels=[label["name"] for label in issue_data.get("labels", [])],
                    created_at=self._parse_datetime(issue_data.get("created_at", "")),
                    updated_at=self._parse_datetime(issue_data.get("updated_at", "")),
                    web_url=issue_data.get("html_url", ""),
                )
                linked.append(
                    LinkedIssue(
                        issue=issue,
                        relation=LinkedIssueRelation.RELATED,
                        source="api",
                    )
                )
            except Exception as e:
                logger.warning(f"Failed to parse linked issue: {e}")
                continue

        return linked

    @retry(
        retry=retry_if_exception_type((NetworkError, InvalidResponse)),
        stop=stop_after_attempt(3),
        wait=wait_exponential(multiplier=10, min=1, max=100),
        reraise=True,
    )
    async def fetch_pr_linked_issues(self, repo: str, pr_number: int) -> list[LinkedIssue]:
        """Fetch issues that the PR closes or references.

        Uses the timeline API to find cross-referenced events, then checks
        if the PR closes those issues.
        Retries on network errors with exponential backoff: 1s, 10s, 100s (max 3 attempts).
        """
        logger.info(f"Fetching linked issues for GitHub PR {repo}#{pr_number}")
        response = await self.http.get(
            f"{self.api_url}/repos/{repo}/issues/{pr_number}/timeline",
            headers={
                **self._headers(),
                "Accept": "application/vnd.github.mockingbird-preview+json",
            },
        )
        events = response.json()

        linked = []
        seen_ids: set[int] = set()

        for event in events:
            # Look for "connected" events (issue linked to PR)
            # and "cross-referenced" events
            event_type = event.get("event")
            if event_type not in ("cross-referenced", "connected"):
                continue

            source = event.get("source", {})
            issue_data = source.get("issue", {}) if event_type == "cross-referenced" else event

            if not issue_data:
                continue

            # Skip pull requests — they appear under source.issue with a "pull_request" key
            if "pull_request" in issue_data:
                continue

            issue_id = issue_data.get("id")
            if not issue_id or issue_id in seen_ids:
                continue
            seen_ids.add(issue_id)

            # Determine relation type
            relation = LinkedIssueRelation.RELATED
            if event_type == "connected":
                relation = LinkedIssueRelation.CLOSES

            try:
                issue = Issue(
                    id=str(issue_data.get("id", "")),
                    number=issue_data.get("number", 0),
                    title=issue_data.get("title", ""),
                    description=issue_data.get("body", "") or "",
                    state=issue_data.get("state", ""),
                    labels=[label["name"] for label in issue_data.get("labels", [])],
                    created_at=self._parse_datetime(issue_data.get("created_at", "")),
                    updated_at=self._parse_datetime(issue_data.get("updated_at", "")),
                    web_url=issue_data.get("html_url", ""),
                )
                linked.append(
                    LinkedIssue(
                        issue=issue,
                        relation=relation,
                        source="api",
                    )
                )
            except Exception as e:
                logger.warning(f"Failed to parse PR-linked issue: {e}")
                continue

        return linked

    async def close(self) -> None:
        """Close the API client."""
        await self.http.close()
