"""
Tests for Mock Clients.

This module provides tests for the mock credential clients including
MockGoogleSheetsClient, MockGmailClient, MockSlackClient, and
MockCredentials factory class.
"""

from __future__ import annotations

import pytest

from torivers_sdk.testing.mocks import (
    MockCredentials,
    MockGmailClient,
    MockGoogleSheetsClient,
    MockSlackClient,
)

# =============================================================================
# MockGoogleSheetsClient Tests
# =============================================================================


class TestMockGoogleSheetsClient:
    """Test suite for MockGoogleSheetsClient."""

    def test_init_default(self) -> None:
        """Test initialization with default values."""
        sheets = MockGoogleSheetsClient()
        assert len(sheets.calls) == 0

    def test_init_with_mock_data(self) -> None:
        """Test initialization with mock data."""
        mock_data = {
            "https://docs.google.com/spreadsheets/d/123": {
                "Sheet1": [["A", "B"], ["1", "2"]]
            }
        }
        sheets = MockGoogleSheetsClient(mock_data=mock_data)
        assert len(sheets._data) == 1

    @pytest.mark.asyncio
    async def test_get_spreadsheet_data_default(self) -> None:
        """Test getting spreadsheet data returns default data."""
        sheets = MockGoogleSheetsClient()
        data = await sheets.get_spreadsheet_data("https://example.com/sheet")

        assert "Sheet1" in data
        assert data["Sheet1"][0] == ["Name", "Email", "Status"]

    @pytest.mark.asyncio
    async def test_get_spreadsheet_data_custom(self) -> None:
        """Test getting spreadsheet data with custom mock data."""
        url = "https://docs.google.com/spreadsheets/d/123"
        mock_data = {url: {"Data": [["Name", "Value"], ["Test", "100"]]}}
        sheets = MockGoogleSheetsClient(mock_data=mock_data)
        data = await sheets.get_spreadsheet_data(url)

        assert "Data" in data
        assert data["Data"][1] == ["Test", "100"]

    @pytest.mark.asyncio
    async def test_get_spreadsheet_data_specific_sheet(self) -> None:
        """Test getting data for a specific sheet."""
        url = "https://example.com/sheet"
        sheets = MockGoogleSheetsClient()
        sheets.set_spreadsheet_data(
            url, {"Sheet1": [["A", "B"]], "Sheet2": [["C", "D"]]}
        )

        data = await sheets.get_spreadsheet_data(url, sheet_name="Sheet2")

        assert "Sheet2" in data
        assert "Sheet1" not in data
        assert data["Sheet2"][0] == ["C", "D"]

    @pytest.mark.asyncio
    async def test_update_range(self) -> None:
        """Test updating a range of cells."""
        sheets = MockGoogleSheetsClient()
        url = "https://example.com/sheet"

        result = await sheets.update_range(
            url=url,
            range_notation="Sheet1!A1:B2",
            values=[["Header1", "Header2"], ["Value1", "Value2"]],
        )

        assert result["updatedRows"] == 2
        assert result["updatedColumns"] == 2
        assert result["updatedCells"] == 4
        assert sheets.update_count == 1

    @pytest.mark.asyncio
    async def test_append_rows(self) -> None:
        """Test appending rows to a sheet."""
        sheets = MockGoogleSheetsClient()
        url = "https://example.com/sheet"
        sheets.set_spreadsheet_data(url, {"Sheet1": [["Name", "Value"]]})

        result = await sheets.append_rows(
            url=url, sheet_name="Sheet1", rows=[["New1", "100"], ["New2", "200"]]
        )

        assert result["updates"]["updatedRows"] == 2

        # Verify data was appended
        data = await sheets.get_spreadsheet_data(url, sheet_name="Sheet1")
        assert len(data["Sheet1"]) == 3

    @pytest.mark.asyncio
    async def test_clear_range(self) -> None:
        """Test clearing a range of cells."""
        sheets = MockGoogleSheetsClient()
        url = "https://example.com/sheet"

        result = await sheets.clear_range(url=url, range_notation="Sheet1!A1:B10")

        assert result["clearedRange"] == "Sheet1!A1:B10"

    def test_call_recording(self) -> None:
        """Test that calls are recorded."""
        sheets = MockGoogleSheetsClient()

        import asyncio

        asyncio.run(sheets.get_spreadsheet_data("https://example.com/1"))
        asyncio.run(sheets.update_range("https://example.com/1", "A1:B1", [["X", "Y"]]))

        assert len(sheets.calls) == 2
        assert len(sheets.read_calls) == 1
        assert len(sheets.update_calls) == 1

    def test_assertions(self) -> None:
        """Test assertion helpers."""
        sheets = MockGoogleSheetsClient()

        # assert_not_called should pass
        sheets.assert_not_called()

        # Make a call
        import asyncio

        asyncio.run(sheets.get_spreadsheet_data("https://example.com/sheet"))

        # assert_called should pass
        sheets.assert_called()

        # assert_spreadsheet_read should pass
        sheets.assert_spreadsheet_read("https://example.com/sheet")

    def test_clear_calls(self) -> None:
        """Test clearing recorded calls."""
        sheets = MockGoogleSheetsClient()

        import asyncio

        asyncio.run(sheets.get_spreadsheet_data("https://example.com/sheet"))
        assert len(sheets.calls) == 1

        sheets.clear_calls()
        assert len(sheets.calls) == 0


# =============================================================================
# MockGmailClient Tests
# =============================================================================


class TestMockGmailClient:
    """Test suite for MockGmailClient."""

    def test_init_default(self) -> None:
        """Test initialization with default values."""
        gmail = MockGmailClient()
        assert gmail.inbox_count == 0
        assert gmail.sent_count == 0

    def test_init_with_messages(self) -> None:
        """Test initialization with mock messages."""
        messages = [{"id": "msg-1", "subject": "Test", "from": "sender@example.com"}]
        gmail = MockGmailClient(mock_messages=messages)
        assert len(gmail._messages) == 1

    def test_add_message(self) -> None:
        """Test adding a message to inbox."""
        gmail = MockGmailClient()
        msg_id = gmail.add_message(
            {
                "subject": "Test Subject",
                "from": "sender@example.com",
                "body": "Test body",
            }
        )

        assert msg_id.startswith("msg-")
        assert gmail.inbox_count == 1

    def test_add_messages(self) -> None:
        """Test adding multiple messages."""
        gmail = MockGmailClient()
        ids = gmail.add_messages(
            [
                {"subject": "Test 1", "from": "a@example.com"},
                {"subject": "Test 2", "from": "b@example.com"},
            ]
        )

        assert len(ids) == 2
        assert gmail.inbox_count == 2

    @pytest.mark.asyncio
    async def test_list_messages(self) -> None:
        """Test listing inbox messages."""
        gmail = MockGmailClient()
        gmail.add_messages(
            [
                {"subject": "Test 1", "from": "a@example.com", "labels": ["INBOX"]},
                {"subject": "Test 2", "from": "b@example.com", "labels": ["INBOX"]},
            ]
        )

        messages = await gmail.list_messages()
        assert len(messages) == 2

    @pytest.mark.asyncio
    async def test_list_messages_with_query(self) -> None:
        """Test listing messages with search query."""
        gmail = MockGmailClient()
        gmail.add_messages(
            [
                {"subject": "Important Meeting", "from": "boss@example.com"},
                {"subject": "Newsletter", "from": "news@example.com"},
            ]
        )

        messages = await gmail.list_messages(query="important")
        assert len(messages) == 1
        assert messages[0]["subject"] == "Important Meeting"

    @pytest.mark.asyncio
    async def test_list_messages_with_labels(self) -> None:
        """Test listing messages filtered by labels."""
        gmail = MockGmailClient()
        gmail.add_messages(
            [
                {"subject": "Test 1", "labels": ["INBOX", "IMPORTANT"]},
                {"subject": "Test 2", "labels": ["INBOX"]},
                {"subject": "Test 3", "labels": ["SENT"]},
            ]
        )

        messages = await gmail.list_messages(label_ids=["IMPORTANT"])
        assert len(messages) == 1
        assert messages[0]["subject"] == "Test 1"

    @pytest.mark.asyncio
    async def test_get_message(self) -> None:
        """Test getting a specific message."""
        gmail = MockGmailClient()
        msg_id = gmail.add_message({"subject": "Test", "body": "Hello"})

        message = await gmail.get_message(msg_id)
        assert message is not None
        assert message["subject"] == "Test"

    @pytest.mark.asyncio
    async def test_get_message_not_found(self) -> None:
        """Test getting a message that doesn't exist."""
        gmail = MockGmailClient()
        message = await gmail.get_message("nonexistent")
        assert message is None

    @pytest.mark.asyncio
    async def test_send_email(self) -> None:
        """Test sending an email."""
        gmail = MockGmailClient(user_email="me@example.com")

        result = await gmail.send_email(
            to="recipient@example.com", subject="Hello", body="This is a test email."
        )

        assert result["id"].startswith("sent-")
        assert gmail.sent_count == 1

        last_sent = gmail.get_last_sent()
        assert last_sent["to"] == "recipient@example.com"
        assert last_sent["subject"] == "Hello"
        assert last_sent["from"] == "me@example.com"

    @pytest.mark.asyncio
    async def test_send_email_multiple_recipients(self) -> None:
        """Test sending an email to multiple recipients."""
        gmail = MockGmailClient()

        await gmail.send_email(
            to=["a@example.com", "b@example.com"],
            subject="Team Update",
            body="Hello team!",
        )

        last_sent = gmail.get_last_sent()
        assert "a@example.com" in last_sent["to"]
        assert "b@example.com" in last_sent["to"]

    @pytest.mark.asyncio
    async def test_create_draft(self) -> None:
        """Test creating a draft email."""
        gmail = MockGmailClient()

        result = await gmail.create_draft(
            to="recipient@example.com", subject="Draft", body="Draft content"
        )

        assert result["id"].startswith("draft-")

    @pytest.mark.asyncio
    async def test_delete_message(self) -> None:
        """Test deleting a message."""
        gmail = MockGmailClient()
        msg_id = gmail.add_message({"subject": "To Delete"})

        result = await gmail.delete_message(msg_id)
        assert result is True
        assert gmail.inbox_count == 0

    @pytest.mark.asyncio
    async def test_modify_labels(self) -> None:
        """Test modifying message labels."""
        gmail = MockGmailClient()
        msg_id = gmail.add_message({"subject": "Test", "labels": ["INBOX"]})

        result = await gmail.modify_labels(
            msg_id, add_labels=["IMPORTANT"], remove_labels=["INBOX"]
        )

        assert "IMPORTANT" in result["labels"]
        assert "INBOX" not in result["labels"]

    def test_assertions(self) -> None:
        """Test assertion helpers."""
        gmail = MockGmailClient()

        # assert_no_emails_sent should pass
        gmail.assert_no_emails_sent()

        # Send an email
        import asyncio

        asyncio.run(
            gmail.send_email(
                to="test@example.com", subject="Test Subject", body="Test body"
            )
        )

        # assert_email_sent should pass
        gmail.assert_email_sent()
        gmail.assert_email_sent(to="test@example.com")
        gmail.assert_email_sent(subject="Test Subject")


# =============================================================================
# MockSlackClient Tests
# =============================================================================


class TestMockSlackClient:
    """Test suite for MockSlackClient."""

    def test_init_default(self) -> None:
        """Test initialization with default values."""
        slack = MockSlackClient()
        assert slack.message_count == 0
        assert len(slack._channels) == 0

    def test_add_channel(self) -> None:
        """Test adding a channel."""
        slack = MockSlackClient()
        channel_id = slack.add_channel("general")

        assert channel_id.startswith("C")
        assert "general" in slack._channels

    def test_add_channel_with_id(self) -> None:
        """Test adding a channel with custom ID."""
        slack = MockSlackClient()
        channel_id = slack.add_channel("general", channel_id="C123456")

        assert channel_id == "C123456"

    def test_add_user(self) -> None:
        """Test adding a user."""
        slack = MockSlackClient()
        slack.add_user("U123", "John Doe", email="john@example.com")

        assert "U123" in slack._users
        assert slack._users["U123"]["name"] == "John Doe"

    @pytest.mark.asyncio
    async def test_post_message(self) -> None:
        """Test posting a message to a channel."""
        slack = MockSlackClient()
        slack.add_channel("general")

        result = await slack.post_message(channel="general", text="Hello, World!")

        assert result["ok"] is True
        assert result["channel"] == "general"
        assert slack.message_count == 1

    @pytest.mark.asyncio
    async def test_post_message_with_blocks(self) -> None:
        """Test posting a message with Block Kit blocks."""
        slack = MockSlackClient()
        slack.add_channel("general")

        blocks = [{"type": "section", "text": {"type": "mrkdwn", "text": "*Hello*"}}]

        result = await slack.post_message(
            channel="general", text="Hello", blocks=blocks
        )

        assert result["ok"] is True
        messages = slack.get_channel_messages("general")
        assert messages[0]["blocks"] == blocks

    @pytest.mark.asyncio
    async def test_post_message_thread_reply(self) -> None:
        """Test posting a thread reply."""
        slack = MockSlackClient()
        slack.add_channel("general")

        # Post original message
        original = await slack.post_message(channel="general", text="Original")

        # Post reply
        await slack.post_message(
            channel="general", text="Reply", thread_ts=original["ts"]
        )

        messages = slack.get_channel_messages("general")
        assert messages[1]["thread_ts"] == original["ts"]

    @pytest.mark.asyncio
    async def test_update_message(self) -> None:
        """Test updating an existing message."""
        slack = MockSlackClient()
        slack.add_channel("general")

        original = await slack.post_message(channel="general", text="Original")

        result = await slack.update_message(
            channel="general", ts=original["ts"], text="Updated"
        )

        assert result["ok"] is True
        messages = slack.get_channel_messages("general")
        assert messages[0]["text"] == "Updated"

    @pytest.mark.asyncio
    async def test_delete_message(self) -> None:
        """Test deleting a message."""
        slack = MockSlackClient()
        slack.add_channel("general")

        msg = await slack.post_message(channel="general", text="To delete")
        assert slack.message_count == 1

        result = await slack.delete_message(channel="general", ts=msg["ts"])

        assert result["ok"] is True
        assert slack.message_count == 0

    @pytest.mark.asyncio
    async def test_add_reaction(self) -> None:
        """Test adding a reaction to a message."""
        slack = MockSlackClient()
        slack.add_channel("general")

        msg = await slack.post_message(channel="general", text="React to me")

        result = await slack.add_reaction(
            channel="general", ts=msg["ts"], emoji="thumbsup"
        )

        assert result["ok"] is True
        messages = slack.get_channel_messages("general")
        assert messages[0]["reactions"][0]["name"] == "thumbsup"

    @pytest.mark.asyncio
    async def test_list_channels(self) -> None:
        """Test listing channels."""
        slack = MockSlackClient()
        slack.add_channel("general")
        slack.add_channel("random")
        slack.add_channel("private", is_private=True)

        channels = await slack.list_channels()
        assert len(channels) == 3

    @pytest.mark.asyncio
    async def test_get_user_info(self) -> None:
        """Test getting user information."""
        slack = MockSlackClient()
        slack.add_user("U123", "John Doe", email="john@example.com")

        user = await slack.get_user_info("U123")
        assert user is not None
        assert user["name"] == "John Doe"
        assert user["email"] == "john@example.com"

    @pytest.mark.asyncio
    async def test_get_user_info_not_found(self) -> None:
        """Test getting user info for nonexistent user."""
        slack = MockSlackClient()
        user = await slack.get_user_info("nonexistent")
        assert user is None

    @pytest.mark.asyncio
    async def test_upload_file(self) -> None:
        """Test uploading a file."""
        slack = MockSlackClient()
        slack.add_channel("general")

        result = await slack.upload_file(
            channels="general",
            content=b"File content",
            filename="test.txt",
            title="Test File",
        )

        assert result["ok"] is True
        assert result["file"]["name"] == "test.txt"

    def test_get_channel_messages(self) -> None:
        """Test getting messages from a channel."""
        slack = MockSlackClient()
        slack.add_channel("general")

        import asyncio

        asyncio.run(slack.post_message(channel="general", text="Message 1"))
        asyncio.run(slack.post_message(channel="general", text="Message 2"))

        messages = slack.get_channel_messages("general")
        assert len(messages) == 2

    def test_assertions(self) -> None:
        """Test assertion helpers."""
        slack = MockSlackClient()
        slack.add_channel("general")

        # assert_no_messages should pass
        slack.assert_no_messages()

        # Post a message
        import asyncio

        asyncio.run(slack.post_message(channel="general", text="Hello, World!"))

        # assert_message_posted should pass
        slack.assert_message_posted()
        slack.assert_message_posted(channel="general")
        slack.assert_message_posted(channel="general", text_contains="Hello")

    def test_clear_messages(self) -> None:
        """Test clearing messages."""
        slack = MockSlackClient()
        slack.add_channel("general")

        import asyncio

        asyncio.run(slack.post_message(channel="general", text="Test"))
        assert slack.message_count == 1

        slack.clear_messages()
        assert slack.message_count == 0

    def test_get_last_message(self) -> None:
        """Test getting the last posted message."""
        slack = MockSlackClient()
        slack.add_channel("general")

        import asyncio

        asyncio.run(slack.post_message(channel="general", text="First"))
        asyncio.run(slack.post_message(channel="general", text="Last"))

        last = slack.get_last_message()
        assert last is not None
        assert last["text"] == "Last"

        last_general = slack.get_last_message(channel="general")
        assert last_general["text"] == "Last"


# =============================================================================
# MockCredentials Factory Tests
# =============================================================================


class TestMockCredentials:
    """Test suite for MockCredentials factory class."""

    def test_google_sheets_factory(self) -> None:
        """Test creating Google Sheets mock via factory."""
        sheets = MockCredentials.google_sheets()
        assert isinstance(sheets, MockGoogleSheetsClient)

    def test_google_sheets_factory_with_data(self) -> None:
        """Test creating Google Sheets mock with initial data."""
        mock_data = {"url": {"Sheet1": [["A", "B"]]}}
        sheets = MockCredentials.google_sheets(mock_data=mock_data)
        assert sheets._data == mock_data

    def test_gmail_factory(self) -> None:
        """Test creating Gmail mock via factory."""
        gmail = MockCredentials.gmail()
        assert isinstance(gmail, MockGmailClient)

    def test_gmail_factory_with_email(self) -> None:
        """Test creating Gmail mock with custom email."""
        gmail = MockCredentials.gmail(user_email="custom@example.com")
        assert gmail._user_email == "custom@example.com"

    def test_slack_factory(self) -> None:
        """Test creating Slack mock via factory."""
        slack = MockCredentials.slack()
        assert isinstance(slack, MockSlackClient)

    def test_slack_factory_with_workspace(self) -> None:
        """Test creating Slack mock with custom workspace."""
        slack = MockCredentials.slack(workspace_name="my-workspace")
        assert slack._workspace_name == "my-workspace"

    def test_llm_factory(self) -> None:
        """Test creating LLM mock via factory."""
        from torivers_sdk.testing.mocks import MockLLMClient

        llm = MockCredentials.llm()
        assert isinstance(llm, MockLLMClient)

    def test_llm_factory_with_validation(self) -> None:
        """Test creating LLM mock with model validation."""
        llm = MockCredentials.llm(validate_models=True)
        assert llm._validate_models is True

    def test_storage_factory(self) -> None:
        """Test creating Storage mock via factory."""
        from torivers_sdk.testing.mocks import MockStorageClient

        storage = MockCredentials.storage()
        assert isinstance(storage, MockStorageClient)

    def test_storage_factory_with_limits(self) -> None:
        """Test creating Storage mock with limits enforcement."""
        storage = MockCredentials.storage(enforce_limits=True)
        assert storage._enforce_limits is True

    def test_http_factory(self) -> None:
        """Test creating HTTP mock via factory."""
        from torivers_sdk.testing.mocks import MockHttpClient

        http = MockCredentials.http()
        assert isinstance(http, MockHttpClient)

    def test_http_factory_with_validation(self) -> None:
        """Test creating HTTP mock with URL validation."""
        http = MockCredentials.http(validate_urls=True)
        assert http._validate_urls is True
