import json
import requests

from .base import BaseRepoProvider
from .config import Config


class BitbucketRepoProvider(BaseRepoProvider):
    provider_name = "bitbucket"

    def __init__(self, config: Config):
        self.config = config
        self.api_url = "https://api.bitbucket.org/2.0"

    def _auth_header_for_token(self, token):
        headers = {"Content-Type": "application/json"}
        if token:
            headers["Authorization"] = f"Bearer {token}"
        return headers

    def request(self, endpoint: str, params=None, method='GET', data=None, retry_on_auth=True):
        """
        Make a request to the Bitbucket Cloud API with automatic token refresh.
        """
        headers = self._auth_header_for_token(self.config.get_bitbucket_token())
        url = f'{self.api_url}{endpoint}'
        response = requests.request(method, url, headers=headers, params=params, json=data)

        # Retry once on auth failure with fresh token
        if retry_on_auth and response.status_code in (401, 403):
            resolver = self.config.get_token_resolver()
            if resolver:
                fresh_token = resolver(self.provider_name)
                if fresh_token:
                    headers = self._auth_header_for_token(fresh_token)
                    response = requests.request(method, url, headers=headers, params=params, json=data)

        try:
            response.raise_for_status()
        except requests.exceptions.HTTPError as err:
            print(f"Error with request to Bitbucket API: {err}")
            try:
                if response.json():
                    print(response.json())
            except json.JSONDecodeError:
                print(response.text)
            raise

        if method == "DELETE":
            return None

        return response.json()

    def _get_workspace_repo_slug(self, owner=None, repo=None):
        workspace = owner or self.config.get_bitbucket_workspace(raise_error_if_not_found=True)
        repo_slug = repo or self.config.get_bitbucket_repo_slug(raise_error_if_not_found=True)
        return workspace, repo_slug

    def _pr_comments_endpoint(self, workspace, repo_slug, pull_request_number):
        return f"/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_number}/comments"

    def create_pull_request(
        self,
        source_branch=None,
        target_branch=None,
        title=None,
        body=None,
        draft=False,
        issue_number=None,
        owner=None,
        repo=None
    ):
        if source_branch is None:
            raise ValueError("create_pull_request: source_branch is required")
        if target_branch is None:
            raise ValueError("create_pull_request: target_branch is required")
        if title is None:
            raise ValueError("create_pull_request: title is required")

        workspace, repo_slug = self._get_workspace_repo_slug(owner, repo)

        description = body
        if issue_number is not None:
            issue_ref = f"Closes #{issue_number}"
            if description:
                description = f"{description}\n\n{issue_ref}"
            else:
                description = issue_ref

        payload = {
            "title": title,
            "source": {"branch": {"name": source_branch}},
            "destination": {"branch": {"name": target_branch}},
            "draft": draft,
        }

        if description is not None:
            payload["description"] = description

        response = self.request(
            f"/repositories/{workspace}/{repo_slug}/pullrequests",
            method="POST",
            data=payload
        )
        html_url = response.get("links", {}).get("html", {}).get("href")
        if html_url:
            response["html_url"] = html_url
        return response

    def update_pull_request(
        self,
        pull_request_number=None,
        title=None,
        body=None,
        assignees=None,
        labels=None,
        state=None,
        maintainer_can_modify=None,
        target_branch=None,
        owner=None,
        repo=None
    ):
        if pull_request_number is None:
            raise ValueError("update_pull_request: pull_request_number is required")

        workspace, repo_slug = self._get_workspace_repo_slug(owner, repo)
        endpoint = f"/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_number}"
        existing = self.request(endpoint, method="GET")

        payload = {
            "title": title if title is not None else existing.get("title"),
            "description": body if body is not None else existing.get("description"),
            "source": existing.get("source"),
            "destination": existing.get("destination"),
            "close_source_branch": existing.get("close_source_branch", False),
            "draft": existing.get("draft", False),
        }

        if target_branch is not None:
            payload["destination"] = {"branch": {"name": target_branch}}

        if state is not None:
            payload["state"] = state.upper()

        return self.request(endpoint, method="PUT", data=payload)

    def comment_on_pull_request(
        self,
        pull_request_number=None,
        body=None,
        owner=None,
        repo=None
    ):
        if body is None:
            raise ValueError("comment_on_pull_request: body is required")
        if pull_request_number is None:
            raise ValueError("comment_on_pull_request: pull_request_number is required")

        workspace, repo_slug = self._get_workspace_repo_slug(owner, repo)
        return self.request(
            self._pr_comments_endpoint(workspace, repo_slug, pull_request_number),
            method="POST",
            data={"content": {"raw": body}},
        )

    def comment_on_pull_request_file(
        self,
        commit_id=None,
        file_path=None,
        pull_request_number=None,
        body=None,
        line=None,
        side="RIGHT",
        reply_to_id=None,
        subject_type="file",
        owner=None,
        repo=None
    ):
        if body is None:
            raise ValueError("comment_on_pull_request_file: body is required")
        if pull_request_number is None:
            raise ValueError("comment_on_pull_request_file: pull_request_number is required")

        workspace, repo_slug = self._get_workspace_repo_slug(owner, repo)

        payload = {"content": {"raw": body}}

        if reply_to_id is not None:
            payload["parent"] = {"id": reply_to_id}

        if file_path is not None:
            inline = {"path": file_path}
            if subject_type == "line" and line is not None:
                if side == "LEFT":
                    inline["from"] = line
                else:
                    inline["to"] = line
            payload["inline"] = inline

        return self.request(
            self._pr_comments_endpoint(workspace, repo_slug, pull_request_number),
            method="POST",
            data=payload,
        )

    def comment_on_pull_request_lines(
        self,
        commit_id=None,
        file_path=None,
        pull_request_number=None,
        body=None,
        start_line=None,
        end_line=None,
        side="RIGHT",
        owner=None,
        repo=None
    ):
        if body is None:
            raise ValueError("comment_on_pull_request_lines: body is required")
        if file_path is None:
            raise ValueError("comment_on_pull_request_lines: file_path is required")
        if pull_request_number is None:
            raise ValueError("comment_on_pull_request_lines: pull_request_number is required")
        if start_line is None:
            raise ValueError("comment_on_pull_request_lines: start_line is required")

        workspace, repo_slug = self._get_workspace_repo_slug(owner, repo)

        inline = {"path": file_path}
        if side == "LEFT":
            inline["from"] = start_line
            if end_line is not None:
                inline["to"] = end_line
        else:
            inline["to"] = end_line if end_line is not None else start_line
            inline["from"] = start_line

        payload = {
            "content": {"raw": body},
            "inline": inline,
        }

        return self.request(
            self._pr_comments_endpoint(workspace, repo_slug, pull_request_number),
            method="POST",
            data=payload,
        )

    def reply_to_pull_request_comment(
        self,
        reply_to_id=None,
        pull_request_number=None,
        body=None,
        owner=None,
        repo=None
    ):
        if body is None:
            raise ValueError("reply_to_pull_request_comment: body is required")
        if reply_to_id is None:
            raise ValueError("reply_to_pull_request_comment: reply_to_id is required")
        if pull_request_number is None:
            raise ValueError("reply_to_pull_request_comment: pull_request_number is required")

        workspace, repo_slug = self._get_workspace_repo_slug(owner, repo)
        return self.request(
            self._pr_comments_endpoint(workspace, repo_slug, pull_request_number),
            method="POST",
            data={
                "content": {"raw": body},
                "parent": {"id": reply_to_id},
            },
        )

    def review_pull_request(
        self,
        pull_request_number=None,
        body=None,
        commit_id=None,
        comments=None,
        event="COMMENT",
        owner=None,
        repo=None
    ):
        if pull_request_number is None:
            raise ValueError("review_pull_request: pull_request_number is required")

        workspace, repo_slug = self._get_workspace_repo_slug(owner, repo)
        event_upper = event.upper()

        if event_upper == "APPROVE":
            response = self.request(
                f"/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_number}/approve",
                method="POST",
            )
            if body:
                self.comment_on_pull_request(pull_request_number=pull_request_number, body=body, owner=workspace, repo=repo_slug)
            return response

        if event_upper == "REQUEST_CHANGES":
            payload = {"message": body} if body else None
            response = self.request(
                f"/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_number}/request-changes",
                method="POST",
                data=payload,
            )
            if comments:
                comment_responses = []
                for comment in comments:
                    if "path" in comment and comment.get("line") is not None:
                        comment_responses.append(
                            self.comment_on_pull_request_file(
                                commit_id=commit_id or comment.get("commit_id"),
                                file_path=comment["path"],
                                pull_request_number=pull_request_number,
                                body=comment.get("body", ""),
                                line=comment["line"],
                                side=comment.get("side", "RIGHT"),
                                subject_type="line",
                                owner=workspace,
                                repo=repo_slug,
                            )
                        )
                    else:
                        comment_responses.append(
                            self.comment_on_pull_request(
                                pull_request_number=pull_request_number,
                                body=comment.get("body", ""),
                                owner=workspace,
                                repo=repo_slug,
                            )
                        )
                return {"request_changes": response, "comments": comment_responses}
            return response

        if event_upper == "COMMENT":
            if body is None and comments is None:
                raise ValueError("review_pull_request: body or comments is required")

            if body:
                response = self.comment_on_pull_request(
                    pull_request_number=pull_request_number,
                    body=body,
                    owner=workspace,
                    repo=repo_slug,
                )
            else:
                response = []

            if comments:
                comment_responses = []
                for comment in comments:
                    if "path" in comment and comment.get("line") is not None:
                        comment_responses.append(
                            self.comment_on_pull_request_file(
                                commit_id=commit_id or comment.get("commit_id"),
                                file_path=comment["path"],
                                pull_request_number=pull_request_number,
                                body=comment.get("body", ""),
                                line=comment["line"],
                                side=comment.get("side", "RIGHT"),
                                subject_type="line",
                                owner=workspace,
                                repo=repo_slug,
                            )
                        )
                    else:
                        comment_responses.append(
                            self.comment_on_pull_request(
                                pull_request_number=pull_request_number,
                                body=comment.get("body", ""),
                                owner=workspace,
                                repo=repo_slug,
                            )
                        )

                if body:
                    return {"review": response, "comments": comment_responses}
                return comment_responses

            return response

        raise ValueError(f"review_pull_request: unsupported event type '{event}'")

    def update_pull_request_comment(self, comment_id=None, body=None, owner=None, repo=None):
        raise NotImplementedError(
            "Bitbucket update_pull_request_comment is not supported through this SDK interface. "
            "Use reply_to_pull_request_comment or create a new comment."
        )

    def delete_pull_request_comment(self, comment_id=None, owner=None, repo=None):
        raise NotImplementedError(
            "Bitbucket delete_pull_request_comment is not supported through this SDK interface. "
            "Use the Bitbucket comments API directly if comment deletion is required."
        )
