"""Unit tests for navari_cli.api_client module."""

import json
from unittest.mock import AsyncMock, MagicMock, patch

import httpx
import pytest
from navari_cli.api_client import NavariAPIClient


def _mock_response(status_code, parsed=None, content=b""):
    """Create a mock response object for asyncio_detailed calls."""
    resp = MagicMock()
    resp.status_code = status_code
    resp.parsed = parsed
    resp.content = content
    resp.text = content.decode() if isinstance(content, bytes) else content
    return resp


def _mock_parsed(*dicts):
    """Create a mock parsed object with to_dict() support."""
    if len(dicts) == 1:
        m = MagicMock()
        m.to_dict.return_value = dicts[0]
        return m
    # For list endpoints (conversations, files)
    items = []
    for d in dicts:
        item = MagicMock()
        item.to_dict.return_value = d
        items.append(item)
    return items


# ============================================================================
# Constructor
# ============================================================================


class TestClientInit:
    def test_sets_base_url_and_token(self):
        client = NavariAPIClient("https://example.com", "tok-123")
        assert client.base_url == "https://example.com"
        assert client.token == "tok-123"
        assert client.verify_ssl is True

    def test_custom_verify_ssl(self):
        client = NavariAPIClient("https://example.com", "tok", verify_ssl=False)
        assert client.verify_ssl is False


# ============================================================================
# Context manager
# ============================================================================


class TestContextManager:
    async def test_async_context_manager(self):
        async with NavariAPIClient("https://example.com", "tok") as client:
            assert client.base_url == "https://example.com"


# ============================================================================
# Conversation operations
# ============================================================================


class TestConversationOps:
    @pytest.fixture()
    def client(self):
        return NavariAPIClient("https://example.com", "tok")

    async def test_create_conversation(self, client):
        parsed = _mock_parsed({"id": "c1", "title": "Test"})
        resp = _mock_response(200, parsed=parsed)
        with patch(
            "navari_cli.api_client.create_conversation.asyncio_detailed", new_callable=AsyncMock, return_value=resp
        ):
            result = await client.create_conversation("Test")
        assert result == {"id": "c1", "title": "Test"}

    async def test_create_conversation_error(self, client):
        resp = _mock_response(500)
        with patch(
            "navari_cli.api_client.create_conversation.asyncio_detailed", new_callable=AsyncMock, return_value=resp
        ):
            with pytest.raises(httpx.HTTPStatusError):
                await client.create_conversation("Test")

    async def test_list_conversations(self, client):
        items = _mock_parsed({"id": "c1"}, {"id": "c2"})
        parsed = MagicMock()
        parsed.conversations = items
        resp = _mock_response(200, parsed=parsed)
        with patch(
            "navari_cli.api_client.list_conversations.asyncio_detailed", new_callable=AsyncMock, return_value=resp
        ):
            result = await client.list_conversations()
        assert len(result) == 2
        assert result[0]["id"] == "c1"

    async def test_get_conversation_not_found(self, client):
        resp = _mock_response(404)
        with patch(
            "navari_cli.api_client.get_conversation.asyncio_detailed", new_callable=AsyncMock, return_value=resp
        ):
            with pytest.raises(FileNotFoundError, match="not found"):
                await client.get_conversation("bad-id")

    async def test_delete_conversation_success(self, client):
        resp = _mock_response(200)
        with patch(
            "navari_cli.api_client.delete_conversation.asyncio_detailed", new_callable=AsyncMock, return_value=resp
        ):
            await client.delete_conversation("c1")  # Should not raise

    async def test_delete_conversation_not_found(self, client):
        resp = _mock_response(404)
        with patch(
            "navari_cli.api_client.delete_conversation.asyncio_detailed", new_callable=AsyncMock, return_value=resp
        ):
            with pytest.raises(FileNotFoundError):
                await client.delete_conversation("bad")

    async def test_update_conversation(self, client):
        parsed = _mock_parsed({"id": "c1", "title": "Updated"})
        resp = _mock_response(200, parsed=parsed)
        with patch(
            "navari_cli.api_client.update_conversation.asyncio_detailed", new_callable=AsyncMock, return_value=resp
        ):
            result = await client.update_conversation("c1", "Updated")
        assert result["title"] == "Updated"

    async def test_update_conversation_not_found(self, client):
        resp = _mock_response(404)
        with patch(
            "navari_cli.api_client.update_conversation.asyncio_detailed", new_callable=AsyncMock, return_value=resp
        ):
            with pytest.raises(FileNotFoundError):
                await client.update_conversation("bad", "title")

    async def test_get_messages(self, client):
        messages = [{"event_kind": "processing_started"}]
        resp = _mock_response(200, content=json.dumps(messages).encode())
        with patch(
            "navari_cli.api_client.get_conversation_messages.asyncio_detailed",
            new_callable=AsyncMock,
            return_value=resp,
        ):
            result = await client.get_messages("c1")
        assert result == messages

    async def test_get_messages_not_found(self, client):
        resp = _mock_response(404)
        with patch(
            "navari_cli.api_client.get_conversation_messages.asyncio_detailed",
            new_callable=AsyncMock,
            return_value=resp,
        ):
            with pytest.raises(FileNotFoundError):
                await client.get_messages("bad")


# ============================================================================
# File operations
# ============================================================================


class TestFileOps:
    @pytest.fixture()
    def client(self):
        return NavariAPIClient("https://example.com", "tok")

    async def test_list_files(self, client):
        file_item = MagicMock()
        file_item.to_dict.return_value = {"id": "f1", "filename": "report.pdf"}
        parsed = MagicMock()
        parsed.files = [file_item]
        resp = _mock_response(200, parsed=parsed)
        with patch("navari_cli.api_client.list_files.asyncio_detailed", new_callable=AsyncMock, return_value=resp):
            result = await client.list_files("c1")
        assert result[0]["filename"] == "report.pdf"

    async def test_get_file_metadata(self, client):
        parsed = _mock_parsed({"id": "f1", "filename": "test.txt"})
        resp = _mock_response(200, parsed=parsed)
        with patch(
            "navari_cli.api_client.get_file_metadata.asyncio_detailed", new_callable=AsyncMock, return_value=resp
        ):
            result = await client.get_file_metadata("f1")
        assert result["id"] == "f1"

    async def test_get_file_metadata_not_found(self, client):
        resp = _mock_response(404)
        with patch(
            "navari_cli.api_client.get_file_metadata.asyncio_detailed", new_callable=AsyncMock, return_value=resp
        ):
            with pytest.raises(FileNotFoundError, match="not found"):
                await client.get_file_metadata("bad")

    async def test_delete_file(self, client):
        resp = _mock_response(204)
        with patch("navari_cli.api_client.delete_file.asyncio_detailed", new_callable=AsyncMock, return_value=resp):
            await client.delete_file("f1")  # Should not raise

    async def test_delete_file_not_found(self, client):
        resp = _mock_response(404)
        with patch("navari_cli.api_client.delete_file.asyncio_detailed", new_callable=AsyncMock, return_value=resp):
            with pytest.raises(FileNotFoundError):
                await client.delete_file("bad")

    async def test_download_file(self):
        mock_http = AsyncMock()
        mock_http.request = AsyncMock(return_value=MagicMock(status_code=200, content=b"filedata"))
        mock_client_obj = MagicMock()
        mock_client_obj.get_async_httpx_client.return_value = mock_http
        client = NavariAPIClient("https://example.com", "tok")
        client.client = mock_client_obj
        result = await client.download_file("f1")
        assert result == b"filedata"

    async def test_download_file_not_found(self):
        mock_http = AsyncMock()
        mock_http.request = AsyncMock(return_value=MagicMock(status_code=404))
        mock_client_obj = MagicMock()
        mock_client_obj.get_async_httpx_client.return_value = mock_http
        client = NavariAPIClient("https://example.com", "tok")
        client.client = mock_client_obj
        with pytest.raises(FileNotFoundError):
            await client.download_file("bad")


# ============================================================================
# Run operations
# ============================================================================


class TestRunOps:
    @pytest.fixture()
    def client(self):
        return NavariAPIClient("https://example.com", "tok")

    async def test_list_runs(self, client):
        parsed = MagicMock()
        parsed.to_dict.return_value = [{"conversation_id": "c1", "state": "running"}]
        resp = _mock_response(200, parsed=parsed)
        with patch("navari_cli.api_client.get_all_runs.asyncio_detailed", new_callable=AsyncMock, return_value=resp):
            result = await client.list_runs()
        assert result[0]["state"] == "running"

    async def test_list_runs_error(self, client):
        resp = _mock_response(500)
        with patch("navari_cli.api_client.get_all_runs.asyncio_detailed", new_callable=AsyncMock, return_value=resp):
            with pytest.raises(httpx.HTTPStatusError):
                await client.list_runs()

    async def test_get_run_status(self, client):
        parsed = MagicMock()
        parsed.to_dict.return_value = {"conversation_id": "c1", "state": "idle"}
        resp = _mock_response(200, parsed=parsed)
        with patch("navari_cli.api_client.get_run_status.asyncio_detailed", new_callable=AsyncMock, return_value=resp):
            result = await client.get_run_status("c1")
        assert result["state"] == "idle"

    async def test_get_run_status_not_found(self, client):
        resp = _mock_response(404)
        with patch("navari_cli.api_client.get_run_status.asyncio_detailed", new_callable=AsyncMock, return_value=resp):
            with pytest.raises(FileNotFoundError, match="No run found"):
                await client.get_run_status("bad")

    async def test_cancel_run(self, client):
        resp = _mock_response(200)
        with patch("navari_cli.api_client.cancel_run.asyncio_detailed", new_callable=AsyncMock, return_value=resp):
            await client.cancel_run("c1")  # Should not raise

    async def test_cancel_run_not_found(self, client):
        resp = _mock_response(404)
        with patch("navari_cli.api_client.cancel_run.asyncio_detailed", new_callable=AsyncMock, return_value=resp):
            with pytest.raises(FileNotFoundError, match="No active run"):
                await client.cancel_run("bad")


# ============================================================================
# User operations
# ============================================================================


class TestUserOps:
    @pytest.fixture()
    def client(self):
        return NavariAPIClient("https://example.com", "tok")

    async def test_get_user_profile(self, client):
        parsed = _mock_parsed({"user": {"username": "user@test.com"}})
        resp = _mock_response(200, parsed=parsed)
        with patch(
            "navari_cli.api_client.get_user_profile.asyncio_detailed", new_callable=AsyncMock, return_value=resp
        ):
            result = await client.get_user_profile()
        assert result["user"]["username"] == "user@test.com"

    async def test_get_user_profile_401(self, client):
        resp = _mock_response(401)
        with patch(
            "navari_cli.api_client.get_user_profile.asyncio_detailed", new_callable=AsyncMock, return_value=resp
        ):
            with pytest.raises(PermissionError, match="invalid token"):
                await client.get_user_profile()

    async def test_get_user_profile_error(self, client):
        resp = _mock_response(500)
        with patch(
            "navari_cli.api_client.get_user_profile.asyncio_detailed", new_callable=AsyncMock, return_value=resp
        ):
            with pytest.raises(httpx.HTTPStatusError):
                await client.get_user_profile()
