import base64
import re
from typing import Any

import httpx

from openhands.resolver.interfaces.issue import (
    Issue,
    IssueHandlerInterface,
    ReviewThread,
)


class AzureDevOpsIssueHandler(IssueHandlerInterface):
    def __init__(
        self,
        token: str,
        organization: str,
        project: str,
        repository: str,
    ):
        self.token = token
        self.organization = organization
        self.project = project
        self.repository = repository
        self.owner = f'{organization}/{project}'
        self.base_api_url = f'https://dev.azure.com/{organization}/{project}/_apis'
        self.repo_api_url = f'{self.base_api_url}/git/repositories/{repository}'
        self.work_items_api_url = f'{self.base_api_url}/wit'
        self.default_branch = 'main'

    def set_owner(self, owner: str) -> None:
        """Set the owner of the repository."""
        self.owner = owner
        parts = owner.split('/')
        if len(parts) >= 2:
            self.organization = parts[0]
            self.project = parts[1]
            self.base_api_url = (
                f'https://dev.azure.com/{self.organization}/{self.project}/_apis'
            )
            self.repo_api_url = (
                f'{self.base_api_url}/git/repositories/{self.repository}'
            )
            self.work_items_api_url = f'{self.base_api_url}/wit'

    def get_headers(self) -> dict[str, str]:
        """Get the headers for the Azure DevOps API."""
        auth_str = base64.b64encode(f':{self.token}'.encode()).decode()
        return {
            'Authorization': f'Basic {auth_str}',
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        }

    def download_issues(self) -> list[Any]:
        """Download issues from Azure DevOps."""
        # Use WIQL to query for active work items
        wiql_url = f'{self.work_items_api_url}/wiql?api-version=7.1'
        wiql_query = {
            'query': "SELECT [System.Id], [System.Title], [System.State] FROM WorkItems WHERE [System.State] = 'Active' ORDER BY [System.CreatedDate] DESC"
        }

        response = httpx.post(wiql_url, headers=self.get_headers(), json=wiql_query)
        response.raise_for_status()

        work_item_references = response.json().get('workItems', [])

        # Get details for each work item
        work_items = []
        for work_item_ref in work_item_references:
            work_item_id = work_item_ref.get('id')
            work_item_url = f'{self.work_items_api_url}/workitems/{work_item_id}?api-version=7.1&$expand=all'

            item_response = httpx.get(work_item_url, headers=self.get_headers())
            item_response.raise_for_status()

            work_items.append(item_response.json())

        return work_items

    def get_issue_comments(
        self, issue_number: int, comment_id: int | None = None
    ) -> list[str] | None:
        """Get comments for an issue."""
        comments_url = f'{self.work_items_api_url}/workitems/{issue_number}/comments?api-version=7.1-preview.3'

        response = httpx.get(comments_url, headers=self.get_headers())
        response.raise_for_status()

        comments_data = response.json().get('comments', [])

        if comment_id is not None:
            # Return a specific comment
            for comment in comments_data:
                if comment.get('id') == comment_id:
                    return [comment.get('text', '')]
            return None

        # Return all comments
        return [comment.get('text', '') for comment in comments_data]

    def get_base_url(self) -> str:
        """Get the base URL for the Azure DevOps repository."""
        return f'https://dev.azure.com/{self.organization}/{self.project}'

    def get_branch_url(self, branch_name: str) -> str:
        """Get the URL for a branch."""
        return f'{self.get_base_url()}/_git/{self.repository}?version=GB{branch_name}'

    def get_download_url(self) -> str:
        """Get the download URL for the repository."""
        return f'{self.get_base_url()}/_git/{self.repository}'

    def get_clone_url(self) -> str:
        """Get the clone URL for the repository."""
        return f'https://dev.azure.com/{self.organization}/{self.project}/_git/{self.repository}'

    def get_pull_url(self, pr_number: int) -> str:
        """Get the URL for a pull request."""
        return f'{self.get_base_url()}/_git/{self.repository}/pullrequest/{pr_number}'

    def get_graphql_url(self) -> str:
        """Get the GraphQL URL for Azure DevOps."""
        return f'https://dev.azure.com/{self.organization}/_apis/graphql?api-version=7.1-preview.1'

    def get_compare_url(self, branch_name: str) -> str:
        """Get the URL to compare branches."""
        return f'{self.get_base_url()}/_git/{self.repository}/branches?baseVersion=GB{self.default_branch}&targetVersion=GB{branch_name}&_a=files'

    def get_branch_name(self, base_branch_name: str) -> str:
        """Generate a branch name for a new pull request."""
        return f'openhands/issue-{base_branch_name}'

    def get_default_branch_name(self) -> str:
        """Get the default branch name for the repository."""
        # Get repository details to find the default branch
        response = httpx.get(
            f'{self.repo_api_url}?api-version=7.1', headers=self.get_headers()
        )
        response.raise_for_status()

        repo_data = response.json()
        default_branch = repo_data.get('defaultBranch', 'refs/heads/main')

        # Remove 'refs/heads/' prefix
        return default_branch.replace('refs/heads/', '')

    def branch_exists(self, branch_name: str) -> bool:
        """Check if a branch exists."""
        # List all branches and check if the branch exists
        response = httpx.get(
            f'{self.repo_api_url}/refs?filter=heads/{branch_name}&api-version=7.1',
            headers=self.get_headers(),
        )
        response.raise_for_status()

        refs = response.json().get('value', [])
        return len(refs) > 0

    def reply_to_comment(self, pr_number: int, comment_id: str, reply: str) -> None:
        """Reply to a comment on a pull request."""
        # Get the thread ID from the comment ID
        threads_url = (
            f'{self.repo_api_url}/pullRequests/{pr_number}/threads?api-version=7.1'
        )

        response = httpx.get(threads_url, headers=self.get_headers())
        response.raise_for_status()

        threads = response.json().get('value', [])
        thread_id = None

        for thread in threads:
            for comment in thread.get('comments', []):
                if str(comment.get('id')) == comment_id:
                    thread_id = thread.get('id')
                    break
            if thread_id:
                break

        if not thread_id:
            raise ValueError(f'Comment ID {comment_id} not found in PR {pr_number}')

        # Add a comment to the thread
        comment_url = f'{self.repo_api_url}/pullRequests/{pr_number}/threads/{thread_id}/comments?api-version=7.1'

        comment_data = {
            'content': reply,
            'parentCommentId': int(comment_id),
        }

        response = httpx.post(
            comment_url, headers=self.get_headers(), json=comment_data
        )
        response.raise_for_status()

    def send_comment_msg(self, issue_number: int, msg: str) -> None:
        """Send a comment to an issue."""
        comment_url = f'{self.work_items_api_url}/workitems/{issue_number}/comments?api-version=7.1-preview.3'

        comment_data = {
            'text': msg,
        }

        response = httpx.post(
            comment_url, headers=self.get_headers(), json=comment_data
        )
        response.raise_for_status()

    def get_authorize_url(self) -> str:
        """Get the authorization URL for Azure DevOps."""
        return 'https://app.vsaex.visualstudio.com/app/register'

    def create_pull_request(self, data: dict[str, Any] | None = None) -> dict[str, Any]:
        """Create a pull request."""
        if data is None:
            data = {}

        source_branch = data.get('source_branch')
        target_branch = data.get('target_branch', self.default_branch)
        title = data.get('title', 'Pull request created by OpenHands')
        description = data.get('description', '')

        pr_data = {
            'sourceRefName': f'refs/heads/{source_branch}',
            'targetRefName': f'refs/heads/{target_branch}',
            'title': title,
            'description': description,
        }

        response = httpx.post(
            f'{self.repo_api_url}/pullrequests?api-version=7.1',
            headers=self.get_headers(),
            json=pr_data,
        )
        response.raise_for_status()

        pr_response = response.json()

        return {
            'id': pr_response.get('pullRequestId'),
            'number': pr_response.get('pullRequestId'),
            'url': pr_response.get('url'),
        }

    def request_reviewers(self, reviewer: str, pr_number: int) -> None:
        """Request reviewers for a pull request."""
        # Get the reviewer's ID
        reviewer_url = f'https://vssps.dev.azure.com/{self.organization}/_apis/graph/users?api-version=7.1-preview.1'

        response = httpx.get(reviewer_url, headers=self.get_headers())
        response.raise_for_status()

        users = response.json().get('value', [])
        reviewer_id = None

        for user in users:
            if (
                user.get('displayName') == reviewer
                or user.get('mailAddress') == reviewer
            ):
                reviewer_id = user.get('descriptor')
                break

        if not reviewer_id:
            raise ValueError(f'Reviewer {reviewer} not found')

        # Add reviewer to the pull request
        reviewers_url = f'{self.repo_api_url}/pullRequests/{pr_number}/reviewers/{reviewer_id}?api-version=7.1'

        reviewer_data = {
            'vote': 0,  # No vote yet
        }

        response = httpx.put(
            reviewers_url, headers=self.get_headers(), json=reviewer_data
        )
        response.raise_for_status()

    def get_context_from_external_issues_references(
        self,
        closing_issues: list[str],
        closing_issue_numbers: list[int],
        issue_body: str,
        review_comments: list[str] | None,
        review_threads: list[ReviewThread],
        thread_comments: list[str] | None,
    ) -> list[str]:
        """Get context from external issue references."""
        context = []

        # Add issue body
        if issue_body:
            context.append(f'Issue description:\n{issue_body}')

        # Add thread comments
        if thread_comments:
            context.append('Thread comments:\n' + '\n'.join(thread_comments))

        # Add review comments
        if review_comments:
            context.append('Review comments:\n' + '\n'.join(review_comments))

        # Add review threads
        if review_threads:
            for thread in review_threads:
                context.append(
                    f'Review thread for files {", ".join(thread.files)}:\n{thread.comment}'
                )

        return context

    def get_converted_issues(
        self, issue_numbers: list[int] | None = None, comment_id: int | None = None
    ) -> list[Issue]:
        """Download issues from Azure DevOps and convert them to the Issue model."""
        if issue_numbers is None:
            # Download all issues
            work_items = self.download_issues()
        else:
            # Download specific issues
            work_items = []
            for issue_number in issue_numbers:
                work_item_url = f'{self.work_items_api_url}/workitems/{issue_number}?api-version=7.1&$expand=all'

                response = httpx.get(work_item_url, headers=self.get_headers())
                response.raise_for_status()

                work_items.append(response.json())

        issues = []
        for work_item in work_items:
            # Get basic issue information
            issue_number = work_item.get('id')
            title = work_item.get('fields', {}).get('System.Title', '')
            description = work_item.get('fields', {}).get('System.Description', '')

            # Get comments
            thread_comments = self.get_issue_comments(issue_number, comment_id)

            # Check if this is a pull request work item
            is_pr = False
            pr_number = None
            head_branch = None
            base_branch = None

            # Look for PR links in the work item relations
            for relation in work_item.get('relations', []):
                if relation.get(
                    'rel'
                ) == 'ArtifactLink' and 'pullrequest' in relation.get('url', ''):
                    is_pr = True
                    # Extract PR number from URL
                    pr_url = relation.get('url', '')
                    pr_match = re.search(r'pullRequests/(\d+)', pr_url)
                    if pr_match:
                        pr_number = int(pr_match.group(1))
                    break

            # If this is a PR, get the branch information
            if is_pr and pr_number:
                pr_url = f'{self.repo_api_url}/pullRequests/{pr_number}?api-version=7.1'

                pr_response = httpx.get(pr_url, headers=self.get_headers())
                pr_response.raise_for_status()

                pr_data = pr_response.json()
                head_branch = pr_data.get('sourceRefName', '').replace(
                    'refs/heads/', ''
                )
                base_branch = pr_data.get('targetRefName', '').replace(
                    'refs/heads/', ''
                )

                # Get PR review comments
                review_comments = []
                review_threads = []

                threads_url = f'{self.repo_api_url}/pullRequests/{pr_number}/threads?api-version=7.1'

                threads_response = httpx.get(threads_url, headers=self.get_headers())
                threads_response.raise_for_status()

                threads = threads_response.json().get('value', [])

                for thread in threads:
                    thread_comments = [
                        comment.get('content', '')
                        for comment in thread.get('comments', [])
                    ]
                    review_comments.extend(thread_comments)

                    # Get files associated with this thread
                    thread_files = []
                    if thread.get('threadContext', {}).get('filePath'):
                        thread_files.append(
                            thread.get('threadContext', {}).get('filePath')
                        )

                    if thread_comments:
                        review_threads.append(
                            ReviewThread(
                                comment='\n'.join(thread_comments),
                                files=thread_files,
                            )
                        )

            # Create the Issue object
            issue = Issue(
                owner=self.owner,
                repo=self.repository,
                number=issue_number,
                title=title,
                body=description,
                thread_comments=thread_comments,
                closing_issues=None,
                review_comments=review_comments if is_pr else None,
                review_threads=review_threads if is_pr else None,
                thread_ids=None,
                head_branch=head_branch,
                base_branch=base_branch,
            )

            issues.append(issue)

        return issues
