"""Tests for src/bitbucket_client.py."""

import pytest
import respx
from httpx import Response

from src.bitbucket_client import BitbucketClient, BitbucketError


@pytest.fixture
def client():
    """Create a BitbucketClient with test credentials."""
    # Pass credentials directly to avoid .env interference
    return BitbucketClient(
        workspace="test-workspace",
        email="test@example.com",
        api_token="test-token",
    )


class TestListRepositories:
    """Tests for list_repositories method."""

    @respx.mock
    def test_returns_list_of_repos(self, client):
        """Should return list of repository dicts."""
        respx.get("https://api.bitbucket.org/2.0/repositories/test-workspace").mock(
            return_value=Response(200, json={
                "values": [
                    {"name": "repo1", "full_name": "test-workspace/repo1"},
                    {"name": "repo2", "full_name": "test-workspace/repo2"},
                ],
                "pagelen": 50,
            })
        )

        result = client.list_repositories(limit=50)

        assert len(result) == 2
        assert result[0]["name"] == "repo1"
        assert result[1]["name"] == "repo2"

    @respx.mock
    def test_returns_empty_list_on_no_results(self, client):
        """Should return empty list when no repos found."""
        respx.get("https://api.bitbucket.org/2.0/repositories/test-workspace").mock(
            return_value=Response(200, json={"values": [], "pagelen": 50})
        )

        result = client.list_repositories()

        assert result == []

    @respx.mock
    def test_handles_404(self, client):
        """Should return empty list on 404."""
        respx.get("https://api.bitbucket.org/2.0/repositories/test-workspace").mock(
            return_value=Response(404)
        )

        result = client.list_repositories()

        assert result == []


class TestListBranches:
    """Tests for list_branches method."""

    @respx.mock
    def test_returns_list_of_branches(self, client):
        """Should return list of branch dicts."""
        respx.get(
            "https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo/refs/branches"
        ).mock(
            return_value=Response(200, json={
                "values": [
                    {"name": "main", "target": {"hash": "abc123"}},
                    {"name": "develop", "target": {"hash": "def456"}},
                ],
            })
        )

        result = client.list_branches("test-repo")

        assert len(result) == 2
        assert result[0]["name"] == "main"


class TestListPipelines:
    """Tests for list_pipelines method."""

    @respx.mock
    def test_returns_sorted_pipelines(self, client):
        """Should return pipelines sorted by created_on."""
        respx.get(
            "https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo/pipelines/"
        ).mock(
            return_value=Response(200, json={
                "values": [
                    {"uuid": "{uuid1}", "build_number": 2},
                    {"uuid": "{uuid2}", "build_number": 1},
                ],
            })
        )

        result = client.list_pipelines("test-repo", limit=10)

        assert len(result) == 2
        assert result[0]["build_number"] == 2


class TestGetPipeline:
    """Tests for get_pipeline method - verifies UUID normalization."""

    @respx.mock
    def test_normalizes_uuid_without_braces(self, client):
        """Should add braces to UUID if missing."""
        route = respx.get(
            "https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo/pipelines/{12345678-1234-1234-1234-123456789012}"
        ).mock(
            return_value=Response(200, json={"uuid": "{12345678-1234-1234-1234-123456789012}"})
        )

        # Call with UUID without braces
        result = client.get_pipeline("test-repo", "12345678-1234-1234-1234-123456789012")

        assert route.called
        assert result["uuid"] == "{12345678-1234-1234-1234-123456789012}"

    @respx.mock
    def test_preserves_uuid_with_braces(self, client):
        """Should preserve braces if already present."""
        route = respx.get(
            "https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo/pipelines/{12345678-1234-1234-1234-123456789012}"
        ).mock(
            return_value=Response(200, json={"uuid": "{12345678-1234-1234-1234-123456789012}"})
        )

        # Call with UUID with braces
        result = client.get_pipeline("test-repo", "{12345678-1234-1234-1234-123456789012}")

        assert route.called
        assert result is not None


class TestGetWebhook:
    """Tests for get_webhook method - verifies UUID normalization."""

    @respx.mock
    def test_normalizes_webhook_uid(self, client):
        """Should add braces to webhook UID if missing."""
        route = respx.get(
            "https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo/hooks/{webhook-uid}"
        ).mock(
            return_value=Response(200, json={"uuid": "{webhook-uid}", "url": "https://example.com"})
        )

        result = client.get_webhook("test-repo", "webhook-uid")

        assert route.called
        assert result["uuid"] == "{webhook-uid}"


class TestRequestText:
    """Tests for _request_text helper method."""

    @respx.mock
    def test_returns_text_on_success(self, client):
        """Should return text content on 200 response."""
        respx.get(
            "https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo/src/main/README.md"
        ).mock(
            return_value=Response(200, text="# Hello World\n\nThis is a README.")
        )

        result = client._request_text("repositories/test-workspace/test-repo/src/main/README.md")

        assert result == "# Hello World\n\nThis is a README."

    @respx.mock
    def test_returns_none_on_404(self, client):
        """Should return None on 404 response."""
        respx.get(
            "https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo/src/main/missing.txt"
        ).mock(
            return_value=Response(404)
        )

        result = client._request_text("repositories/test-workspace/test-repo/src/main/missing.txt")

        assert result is None

    @respx.mock
    def test_raises_on_error(self, client):
        """Should raise BitbucketError on error responses."""
        from src.bitbucket_client import BitbucketError

        respx.get(
            "https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo/src/main/file.txt"
        ).mock(
            return_value=Response(500)
        )

        with pytest.raises(BitbucketError, match="Request failed: 500"):
            client._request_text("repositories/test-workspace/test-repo/src/main/file.txt")


class TestGetFileContent:
    """Tests for get_file_content method."""

    @respx.mock
    def test_returns_file_content(self, client):
        """Should return file content as string."""
        respx.get(
            "https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo/src/main/src/main.py"
        ).mock(
            return_value=Response(200, text="print('hello')")
        )

        result = client.get_file_content("test-repo", "src/main.py")

        assert result == "print('hello')"

    @respx.mock
    def test_returns_none_when_not_found(self, client):
        """Should return None for non-existent files."""
        respx.get(
            "https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo/src/main/missing.py"
        ).mock(
            return_value=Response(404)
        )

        result = client.get_file_content("test-repo", "missing.py")

        assert result is None


class TestGetPrDiff:
    """Tests for get_pr_diff method."""

    @respx.mock
    def test_returns_diff_content(self, client):
        """Should return diff as string."""
        diff_content = "diff --git a/file.py b/file.py\n+new line"
        respx.get(
            "https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo/pullrequests/123/diff"
        ).mock(
            return_value=Response(200, text=diff_content)
        )

        result = client.get_pr_diff("test-repo", 123)

        assert result == diff_content

    @respx.mock
    def test_returns_empty_on_404(self, client):
        """Should return empty string on 404."""
        respx.get(
            "https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo/pullrequests/999/diff"
        ).mock(
            return_value=Response(404)
        )

        result = client.get_pr_diff("test-repo", 999)

        assert result == ""


class TestGetPipelineLogs:
    """Tests for get_pipeline_logs method."""

    @respx.mock
    def test_returns_logs(self, client):
        """Should return log content as string."""
        log_content = "Step 1: Building...\nStep 2: Testing...\nDone!"
        respx.get(
            "https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo/pipelines/"
            "{pipeline-uuid}/steps/{step-uuid}/log"
        ).mock(
            return_value=Response(200, text=log_content)
        )

        result = client.get_pipeline_logs("test-repo", "pipeline-uuid", "step-uuid")

        assert result == log_content

    @respx.mock
    def test_returns_empty_on_404(self, client):
        """Should return empty string on 404."""
        respx.get(
            "https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo/pipelines/"
            "{missing-uuid}/steps/{step-uuid}/log"
        ).mock(
            return_value=Response(404)
        )

        result = client.get_pipeline_logs("test-repo", "missing-uuid", "step-uuid")

        assert result == ""


class TestRequireResult:
    """Tests for _require_result helper method."""

    def test_returns_result_when_valid(self, client):
        """Should return the result when it's valid."""
        result = {"id": 1, "name": "test"}

        returned = client._require_result(result, "create something", "test-id")

        assert returned == result

    def test_raises_on_none(self, client):
        """Should raise BitbucketError when result is None."""
        from src.bitbucket_client import BitbucketError

        with pytest.raises(BitbucketError, match="Failed to create something: test-id"):
            client._require_result(None, "create something", "test-id")

    def test_raises_on_empty_dict(self, client):
        """Should raise BitbucketError when result is empty dict."""
        from src.bitbucket_client import BitbucketError

        with pytest.raises(BitbucketError, match="Failed to do action"):
            client._require_result({}, "do action")

    def test_error_message_without_identifier(self, client):
        """Should format error message correctly without identifier."""
        from src.bitbucket_client import BitbucketError

        with pytest.raises(BitbucketError) as exc_info:
            client._require_result(None, "create webhook")

        assert str(exc_info.value) == "Failed to create webhook"
        assert ": " not in str(exc_info.value)  # No colon when no identifier


class TestRepoPath:
    """Tests for _repo_path helper method."""

    def test_base_path(self, client):
        """Should return base repository path."""
        path = client._repo_path("my-repo")
        assert path == "repositories/test-workspace/my-repo"

    def test_with_single_part(self, client):
        """Should append single path part."""
        path = client._repo_path("my-repo", "pullrequests")
        assert path == "repositories/test-workspace/my-repo/pullrequests"

    def test_with_multiple_parts(self, client):
        """Should append multiple path parts."""
        path = client._repo_path("my-repo", "pullrequests", "123", "merge")
        assert path == "repositories/test-workspace/my-repo/pullrequests/123/merge"

    def test_with_pipelines_path(self, client):
        """Should work with pipeline paths."""
        path = client._repo_path("my-repo", "pipelines", "{uuid}", "steps", "{step-uuid}", "log")
        assert path == "repositories/test-workspace/my-repo/pipelines/{uuid}/steps/{step-uuid}/log"
