from unittest.mock import AsyncMock, patch

import pytest
from arcade_tdk.errors import RetryableToolError, ToolExecutionError
from httpx import Response

from arcade_github.tools.models import (
    DiffSide,
    ReviewCommentSubjectType,
)
from arcade_github.tools.pull_requests import (
    create_reply_for_review_comment,
    create_review_comment,
    get_pull_request,
    list_pull_request_commits,
    list_pull_requests,
    list_review_comments_on_pull_request,
    update_pull_request,
)


@pytest.fixture
def mock_context():
    context = AsyncMock()
    context.authorization.token = "mock_token"  # noqa: S105
    return context


@pytest.fixture
def mock_client():
    with patch("arcade_github.tools.pull_requests.httpx.AsyncClient") as client:
        yield client.return_value.__aenter__.return_value


@pytest.mark.asyncio
@pytest.mark.parametrize(
    "func,args,status_code,json_response,expected_result,error_message",
    [
        (list_pull_requests, ("owner", "repo"), 200, [], '{"pull_requests": []}', None),
        (
            get_pull_request,
            ("owner", "repo", 1),
            404,
            {"message": "Not Found"},
            None,
            "Error accessing.*: Resource not found",
        ),
        (
            update_pull_request,
            ("owner", "repo", 1, "New Title"),
            409,
            {"message": "Conflict"},
            None,
            "Error accessing.*: Failed to process request",
        ),
        (
            list_pull_request_commits,
            ("owner", "repo", 1),
            500,
            {"message": "Internal Server Error"},
            None,
            "Error accessing.*: Failed to process request",
        ),
        (
            list_review_comments_on_pull_request,
            ("owner", "repo", 1),
            403,
            {"message": "API rate limit exceeded"},
            None,
            "Error accessing.*: Forbidden",
        ),
    ],
)
async def test_pull_request_functions(
    mock_context,
    mock_client,
    func,
    args,
    status_code,
    json_response,
    expected_result,
    error_message,
):
    mock_client.get.return_value = mock_client.post.return_value = (
        mock_client.patch.return_value
    ) = Response(status_code, json=json_response)

    if error_message:
        with pytest.raises(ToolExecutionError, match=error_message):
            await func(mock_context, *args)
    else:
        result = await func(mock_context, *args)
        assert result == expected_result


@pytest.mark.asyncio
@pytest.mark.parametrize(
    "func,args,json_response,expected_assertions",
    [
        (
            list_pull_requests,
            ("owner", "repo"),
            [
                {
                    "number": 1,
                    "title": "Test PR",
                    "body": "This is a test PR",
                    "state": "open",
                    "html_url": "https://github.com/owner/repo/pull/1",
                    "created_at": "2023-05-01T12:00:00Z",
                    "updated_at": "2023-05-01T12:00:00Z",
                    "user": {"login": "testuser"},
                    "base": {"ref": "main"},
                    "head": {"ref": "feature-branch"},
                }
            ],
            ["Test PR", "https://github.com/owner/repo/pull/1"],
        ),
        (
            update_pull_request,
            ("owner", "repo", 1, "Updated PR Title", "Updated PR body"),
            {
                "number": 1,
                "title": "Updated PR Title",
                "body": "Updated PR body",
                "state": "open",
                "html_url": "https://github.com/owner/repo/pull/1",
                "created_at": "2023-05-01T12:00:00Z",
                "updated_at": "2023-05-02T12:00:00Z",
                "user": {"login": "testuser"},
            },
            ["Updated PR Title", "Updated PR body"],
        ),
        (
            list_pull_request_commits,
            ("owner", "repo", 1),
            [
                {
                    "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
                    "commit": {
                        "author": {
                            "name": "Test Author",
                            "email": "author@example.com",
                            "date": "2023-05-01T12:00:00Z",
                        },
                        "message": "Test commit message",
                    },
                }
            ],
            ["6dcb09b5b57875f334f61aebed695e2e4193db5e", "Test commit message"],
        ),
        (
            create_reply_for_review_comment,
            ("owner", "repo", 1, 42, "Thanks for the suggestion."),
            {
                "id": 123,
                "body": "Thanks for the suggestion.",
                "user": {"login": "testuser"},
                "created_at": "2023-05-02T12:00:00Z",
                "updated_at": "2023-05-02T12:00:00Z",
            },
            ["Thanks for the suggestion.", "testuser"],
        ),
        (
            list_review_comments_on_pull_request,
            ("owner", "repo", 1),
            [
                {
                    "id": 1,
                    "body": "Great changes!",
                    "user": {"login": "reviewer1"},
                    "created_at": "2023-05-01T12:00:00Z",
                    "updated_at": "2023-05-01T12:00:00Z",
                    "path": "file1.txt",
                    "line": 5,
                }
            ],
            ["Great changes!", "reviewer1", "file1.txt"],
        ),
        (
            get_pull_request,
            ("owner", "repo", 1, False, False),
            {
                "number": 1,
                "title": "Test PR",
                "body": "This is a test PR",
                "state": "open",
                "html_url": "https://github.com/owner/repo/pull/1",
                "created_at": "2023-05-01T12:00:00Z",
                "updated_at": "2023-05-01T12:00:00Z",
                "user": {"login": "testuser"},
                "base": {"ref": "main"},
                "head": {"ref": "feature-branch"},
            },
            ["Test PR", "https://github.com/owner/repo/pull/1"],
        ),
        (
            get_pull_request,
            ("owner", "repo", 1, True, False),
            {
                "number": 1,
                "title": "Test PR",
                "body": "This is a test PR",
                "state": "open",
                "html_url": "https://github.com/owner/repo/pull/1",
                "created_at": "2023-05-01T12:00:00Z",
                "updated_at": "2023-05-01T12:00:00Z",
                "user": {"login": "testuser"},
                "base": {"ref": "main"},
                "head": {"ref": "feature-branch"},
                "diff_content": "Sample diff content",
            },
            ["Test PR", "https://github.com/owner/repo/pull/1", "diff_content"],
        ),
        (
            create_review_comment,
            (
                "owner",
                "repo",
                1,
                "Great changes!",
                "file1.txt",
                "6dcb09b5b57875f334f61aebed695e2e4193db5e",
                1,
                2,
                DiffSide.RIGHT,
                None,
                ReviewCommentSubjectType.LINE,
            ),
            {
                "id": 1,
                "body": "Great changes!",
                "path": "file1.txt",
                "line": 2,
                "side": "RIGHT",
                "commit_id": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
                "user": {"login": "testuser"},
                "created_at": "2023-05-01T12:00:00Z",
                "updated_at": "2023-05-01T12:00:00Z",
                "html_url": "https://github.com/owner/repo/pull/1#discussion_r1",
            },
            ["Great changes!", "file1.txt", "6dcb09b5b57875f334f61aebed695e2e4193db5e"],
        ),
    ],
)
async def test_pull_request_functions_success(
    mock_context, mock_client, func, args, json_response, expected_assertions
):
    mock_client.get.return_value = mock_client.post.return_value = (
        mock_client.patch.return_value
    ) = Response(200, json=json_response)

    result = await func(mock_context, *args)
    for assertion in expected_assertions:
        assert assertion in result


@pytest.mark.asyncio
async def test_create_review_comment_file_subject_type(mock_context, mock_client):
    mock_client.post.return_value = Response(
        200,
        json={
            "id": 1,
            "body": "File comment",
            "path": "file1.txt",
            "commit_id": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
            "user": {"login": "testuser"},
            "created_at": "2023-05-01T12:00:00Z",
            "updated_at": "2023-05-01T12:00:00Z",
            "html_url": "https://github.com/owner/repo/pull/1#discussion_r1",
        },
    )

    result = await create_review_comment(
        mock_context,
        "owner",
        "repo",
        1,
        "File comment",
        "file1.txt",
        "6dcb09b5b57875f334f61aebed695e2e4193db5e",
        subject_type=ReviewCommentSubjectType.FILE,
    )

    assert "File comment" in result
    assert "file1.txt" in result
    assert "6dcb09b5b57875f334f61aebed695e2e4193db5e" in result
    assert "start_line" not in mock_client.post.call_args[1]["json"]
    assert "end_line" not in mock_client.post.call_args[1]["json"]


@pytest.mark.asyncio
async def test_create_review_comment_missing_commit_id(mock_context, mock_client):
    mock_client.get.return_value = Response(
        200,
        json=[{"sha": "latest_commit_sha"}],
    )
    mock_client.post.return_value = Response(
        200,
        json={
            "id": 1,
            "body": "Comment with auto-fetched commit ID",
            "path": "file1.txt",
            "commit_id": "latest_commit_sha",
            "user": {"login": "testuser"},
            "created_at": "2023-05-01T12:00:00Z",
            "updated_at": "2023-05-01T12:00:00Z",
            "html_url": "https://github.com/owner/repo/pull/1#discussion_r1",
        },
    )

    result = await create_review_comment(
        mock_context,
        "owner",
        "repo",
        1,
        "Comment with auto-fetched commit ID",
        "file1.txt",
        start_line=1,
        end_line=2,
    )

    assert "Comment with auto-fetched commit ID" in result
    assert "latest_commit_sha" in result
    assert mock_client.get.called
    assert mock_client.post.called


@pytest.mark.asyncio
async def test_create_review_comment_invalid_input(mock_context, mock_client):
    with pytest.raises(
        RetryableToolError, match="'start_line' and 'end_line' parameters are required"
    ):
        await create_review_comment(
            mock_context,
            "owner",
            "repo",
            1,
            "Invalid comment",
            "file1.txt",
            subject_type=ReviewCommentSubjectType.LINE,
        )


@pytest.mark.asyncio
async def test_create_review_comment_no_commits(mock_context, mock_client):
    mock_client.get.return_value = Response(200, json=[])

    with pytest.raises(RetryableToolError, match="Failed to get the latest commit SHA"):
        await create_review_comment(
            mock_context,
            "owner",
            "repo",
            1,
            "Comment with no commits",
            "file1.txt",
            start_line=1,
            end_line=2,
        )
