"""GitLab issue handler."""

import os
from datetime import datetime
from typing import Self
from urllib.parse import quote

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

from code_reviewer.errors import AuthenticationFailed, InvalidResponse, NetworkError, NotFound

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

logger = PlatformLogger("gitlab")


class GitLabIssueHandler(IssueHandler):
    """GitLab issue handler."""

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

        Args:
            api_url: GitLab API base URL (e.g., "https://gitlab.com/api/v4")
            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="GitLab", auth_header="PRIVATE-TOKEN")
        return cls(api_url, client)

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

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

        Requires GITLAB_API_TOKEN or GITLAB_TOKEN environment variable.
        Optionally uses GITLAB_API_URL (defaults to https://gitlab.com/api/v4).

        Returns:
            GitLabIssueHandler instance

        Raises:
            AuthenticationFailed: If required environment variables are missing
        """
        api_url = os.getenv("GITLAB_API_URL", "https://gitlab.com/api/v4")
        token = os.getenv("GITLAB_API_TOKEN") or os.getenv("GITLAB_TOKEN")

        if not token:
            raise AuthenticationFailed(
                "Missing GITLAB_API_TOKEN or GITLAB_TOKEN environment variable"
            )

        return cls.from_token(api_url, token)

    def _headers(self) -> dict[str, str]:
        """Get common request headers (non-auth)."""
        return {}

    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 GitLab issue.

        Retries on network errors with exponential backoff: 1s, 10s, 100s (max 3 attempts).
        """
        logger.info(f"Fetching GitLab issue {repo}#{issue_number}")
        # In GitLab, issues are identified by IID (internal ID) within a project
        response = await self.http.get(
            f"{self.api_url}/projects/{quote(repo, safe='')}/issues",
            headers=self._headers(),
            params={"iids": issue_number},
        )
        issues = response.json()

        if not issues:
            raise InvalidResponse(f"Issue {issue_number} not found in {repo}")

        issue_data = issues[0]

        # Parse to platform-agnostic Issue
        return Issue(
            id=str(issue_data.get("id", "")),
            number=issue_data.get("iid", 0),
            title=issue_data.get("title", ""),
            description=issue_data.get("description", ""),
            state=issue_data.get("state", ""),
            labels=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("web_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 notes (comments) for a GitLab issue.

        Retries on network errors with exponential backoff: 1s, 10s, 100s (max 3 attempts).
        """
        logger.info(f"Fetching notes for GitLab issue {repo}#{issue_number}")
        response = await self.http.get(
            f"{self.api_url}/projects/{quote(repo, safe='')}/issues/{issue_number}/notes",
            headers=self._headers(),
            params={"per_page": limit, "sort": "asc"},
        )
        notes_data = response.json()

        comments = []
        for note in notes_data:
            # Skip system notes
            if note.get("system", False):
                continue

            author = note.get("author", {})
            comments.append(
                IssueComment(
                    id=str(note.get("id", "")),
                    author=author.get("username", "unknown"),
                    body=note.get("body", ""),
                    created_at=self._parse_datetime(note.get("created_at", "")),
                    updated_at=self._parse_datetime(note.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 GitLab issue links API.

        Retries on network errors with exponential backoff: 1s, 10s, 100s (max 3 attempts).
        """
        logger.info(f"Fetching linked issues for GitLab issue {repo}#{issue_number}")

        linked = []

        # Fetch issue links
        try:
            response = await self.http.get(
                f"{self.api_url}/projects/{quote(repo, safe='')}/issues/{issue_number}/links",
                headers=self._headers(),
            )
            links_data = response.json()

            for link in links_data:
                try:
                    issue = Issue(
                        id=str(link.get("id", "")),
                        number=link.get("iid", 0),
                        title=link.get("title", ""),
                        description=link.get("description", "") or "",
                        state=link.get("state", ""),
                        labels=link.get("labels", []),
                        created_at=self._parse_datetime(link.get("created_at", "")),
                        updated_at=self._parse_datetime(link.get("updated_at", "")),
                        web_url=link.get("web_url", ""),
                    )

                    # Determine relation type from link_type if available
                    link_type = link.get("link_type", "relates_to")
                    if link_type == "is_blocked_by":
                        relation = LinkedIssueRelation.RELATED
                    elif link_type == "blocks":
                        relation = LinkedIssueRelation.RELATED
                    else:
                        relation = LinkedIssueRelation.RELATED

                    linked.append(
                        LinkedIssue(
                            issue=issue,
                            relation=relation,
                            source="api",
                        )
                    )
                except Exception as e:
                    logger.warning(f"Failed to parse linked issue: {e}")
                    continue
        except NotFound:
            # Issue links endpoint not available or no links
            pass

        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 MR closes.

        Uses the closes_issues endpoint to find issues that will be closed when MR is merged.
        Retries on network errors with exponential backoff: 1s, 10s, 100s (max 3 attempts).
        """
        logger.info(f"Fetching linked issues for GitLab MR {repo}!{pr_number}")

        linked = []

        try:
            response = await self.http.get(
                f"{self.api_url}/projects/{quote(repo, safe='')}/merge_requests/{pr_number}/closes_issues",
                headers=self._headers(),
            )
            issues_data = response.json()

            for issue_data in issues_data:
                try:
                    issue = Issue(
                        id=str(issue_data.get("id", "")),
                        number=issue_data.get("iid", 0),
                        title=issue_data.get("title", ""),
                        description=issue_data.get("description", "") or "",
                        state=issue_data.get("state", ""),
                        labels=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("web_url", ""),
                    )
                    linked.append(
                        LinkedIssue(
                            issue=issue,
                            relation=LinkedIssueRelation.CLOSES,
                            source="api",
                        )
                    )
                except Exception as e:
                    logger.warning(f"Failed to parse MR-linked issue: {e}")
                    continue
        except NotFound:
            # No closing issues or endpoint not available
            pass

        return linked

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