from unittest.mock import AsyncMock, MagicMock, patch

import httpx
import pytest
from pydantic import SecretStr

from openhands.app_server.integrations.forgejo.forgejo_service import ForgejoService
from openhands.app_server.integrations.service_types import (
    ProviderType,
    Repository,
    RequestMethod,
    User,
)
from openhands.app_server.types import AppMode


@pytest.fixture
def forgejo_service():
    return ForgejoService(token=SecretStr('test_token'))


@pytest.mark.asyncio
async def test_get_user(forgejo_service):
    # Mock response data
    mock_user_data = {
        'id': 1,
        'username': 'test_user',
        'avatar_url': 'https://codeberg.org/avatar/test_user',
        'full_name': 'Test User',
        'email': 'test@example.com',
        'organization': 'Test Org',
    }

    # Mock the _make_request method
    forgejo_service._make_request = AsyncMock(return_value=(mock_user_data, {}))

    # Call the method
    user = await forgejo_service.get_user()

    # Verify the result
    assert isinstance(user, User)
    assert user.id == '1'
    assert user.login == 'test_user'
    assert user.avatar_url == 'https://codeberg.org/avatar/test_user'
    assert user.name == 'Test User'
    assert user.email == 'test@example.com'
    assert user.company == 'Test Org'

    # Verify the _fetch_data call
    forgejo_service._make_request.assert_called_once_with(
        f'{forgejo_service.BASE_URL}/user'
    )


@pytest.mark.asyncio
async def test_search_repositories(forgejo_service):
    # Mock response data
    mock_repos_data = {
        'data': [
            {
                'id': 1,
                'full_name': 'test_user/repo1',
                'stars_count': 10,
            },
            {
                'id': 2,
                'full_name': 'test_user/repo2',
                'stars_count': 20,
            },
        ]
    }

    # Mock the _fetch_data method
    forgejo_service._make_request = AsyncMock(return_value=(mock_repos_data, {}))

    # Call the method
    repos = await forgejo_service.search_repositories(
        'test', 10, 'updated', 'desc', public=False, app_mode=AppMode.OPENHANDS
    )

    # Verify the result
    assert len(repos) == 2
    assert all(isinstance(repo, Repository) for repo in repos)
    assert repos[0].id == '1'
    assert repos[0].full_name == 'test_user/repo1'
    assert repos[0].stargazers_count == 10
    assert repos[0].git_provider == ProviderType.FORGEJO
    assert repos[1].id == '2'
    assert repos[1].full_name == 'test_user/repo2'
    assert repos[1].stargazers_count == 20
    assert repos[1].git_provider == ProviderType.FORGEJO

    # Verify the _fetch_data call
    forgejo_service._make_request.assert_called_once_with(
        f'{forgejo_service.BASE_URL}/repos/search',
        {
            'q': 'test',
            'limit': 10,
            'sort': 'updated',
            'order': 'desc',
            'mode': 'source',
        },
    )


@pytest.mark.asyncio
async def test_get_all_repositories(forgejo_service):
    # Mock response data for first page
    mock_repos_data_page1 = [
        {
            'id': 1,
            'full_name': 'test_user/repo1',
            'stars_count': 10,
        },
        {
            'id': 2,
            'full_name': 'test_user/repo2',
            'stars_count': 20,
        },
    ]

    # Mock response data for second page
    mock_repos_data_page2 = [
        {
            'id': 3,
            'full_name': 'test_user/repo3',
            'stars_count': 30,
        },
    ]

    # Mock the _fetch_data method to return different data for different pages
    forgejo_service._make_request = AsyncMock()
    forgejo_service._make_request.side_effect = [
        (
            mock_repos_data_page1,
            {'Link': '<https://codeberg.org/api/v1/user/repos?page=2>; rel="next"'},
        ),
        (mock_repos_data_page2, {'Link': ''}),
    ]

    # Call the method
    repos = await forgejo_service.get_all_repositories('updated', AppMode.OPENHANDS)

    # Verify the result
    assert len(repos) == 3
    assert all(isinstance(repo, Repository) for repo in repos)
    assert repos[0].id == '1'
    assert repos[0].full_name == 'test_user/repo1'
    assert repos[0].stargazers_count == 10
    assert repos[0].git_provider == ProviderType.FORGEJO
    assert repos[1].id == '2'
    assert repos[1].full_name == 'test_user/repo2'
    assert repos[1].stargazers_count == 20
    assert repos[1].git_provider == ProviderType.FORGEJO
    assert repos[2].id == '3'
    assert repos[2].full_name == 'test_user/repo3'
    assert repos[2].stargazers_count == 30
    assert repos[2].git_provider == ProviderType.FORGEJO

    # Verify the _fetch_data calls
    assert forgejo_service._make_request.call_count == 2
    forgejo_service._make_request.assert_any_call(
        f'{forgejo_service.BASE_URL}/user/repos',
        {'page': '1', 'limit': '100', 'sort': 'updated'},
    )
    forgejo_service._make_request.assert_any_call(
        f'{forgejo_service.BASE_URL}/user/repos',
        {'page': '2', 'limit': '100', 'sort': 'updated'},
    )


@pytest.mark.asyncio
async def test_make_request_success(forgejo_service):
    # Mock httpx.AsyncClient
    mock_client = AsyncMock()
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.raise_for_status = MagicMock()
    mock_response.json.return_value = {'key': 'value'}
    mock_response.headers = {'Link': 'next_link', 'Content-Type': 'application/json'}
    mock_client.__aenter__.return_value.get.return_value = mock_response

    # Patch httpx.AsyncClient
    with patch('httpx.AsyncClient', return_value=mock_client):
        # Call the method
        result, headers = await forgejo_service._make_request(
            'https://test.url', {'param': 'value'}
        )

    # Verify the result
    assert result == {'key': 'value'}
    assert headers == {'Link': 'next_link'}
    mock_response.raise_for_status.assert_called_once()


@pytest.mark.asyncio
async def test_make_request_auth_error(forgejo_service):
    # Mock httpx.AsyncClient
    mock_client = AsyncMock()
    mock_response = MagicMock()
    mock_response.status_code = 401
    mock_response.raise_for_status.side_effect = httpx.HTTPStatusError(
        '401 Unauthorized', request=MagicMock(), response=mock_response
    )
    mock_client.__aenter__.return_value.get.return_value = mock_response

    # Patch httpx.AsyncClient
    with patch('httpx.AsyncClient', return_value=mock_client):
        # Call the method and expect an exception
        with pytest.raises(Exception) as excinfo:
            await forgejo_service._make_request('https://test.url', {'param': 'value'})

    # Verify the exception
    assert 'Invalid forgejo token' in str(excinfo.value)


@pytest.mark.asyncio
async def test_make_request_other_error(forgejo_service):
    # Mock httpx.AsyncClient
    mock_client = AsyncMock()
    mock_response = MagicMock()
    mock_response.status_code = 500
    mock_response.raise_for_status.side_effect = httpx.HTTPStatusError(
        '500 Server Error', request=MagicMock(), response=mock_response
    )
    mock_client.__aenter__.return_value.get.return_value = mock_response

    # Patch httpx.AsyncClient
    with patch('httpx.AsyncClient', return_value=mock_client):
        # Call the method and expect an exception
        with pytest.raises(Exception) as excinfo:
            await forgejo_service._make_request('https://test.url', {'param': 'value'})

    # Verify the exception
    assert 'Unknown error' in str(excinfo.value)


@pytest.mark.asyncio
async def test_create_pull_request(forgejo_service):
    mock_response = {'index': 42, 'html_url': 'https://example/pr/42'}
    forgejo_service._make_request = AsyncMock(return_value=(mock_response, {}))

    data = {'owner': 'org', 'repo': 'project', 'title': 'Add feature'}
    result = await forgejo_service.create_pull_request(data.copy())

    assert result['number'] == 42
    forgejo_service._make_request.assert_awaited_once_with(
        f'{forgejo_service.BASE_URL}/repos/org/project/pulls',
        {'title': 'Add feature'},
        method=RequestMethod.POST,
    )


@pytest.mark.asyncio
async def test_request_reviewers(forgejo_service):
    forgejo_service._make_request = AsyncMock(return_value=({}, {}))

    await forgejo_service.request_reviewers('org/project', 5, ['alice'])

    forgejo_service._make_request.assert_awaited_once_with(
        f'{forgejo_service.BASE_URL}/repos/org/project/pulls/5/requested_reviewers',
        {'reviewers': ['alice']},
        method=RequestMethod.POST,
    )


@pytest.mark.asyncio
async def test_request_reviewers_empty_list(forgejo_service):
    forgejo_service._make_request = AsyncMock()

    await forgejo_service.request_reviewers('org/project', 5, [])

    forgejo_service._make_request.assert_not_called()
