"""
Mock service clients for testing automations.

This module provides mock implementations of service clients that can be
used during local development and testing without requiring actual API access.

Example:
    from torivers_sdk.context.mocks import MockGmailClient, MockCredentialProxy

    # Create mock Gmail client with test data
    gmail = MockGmailClient()
    gmail.add_mock_email(
        EmailMessage(
            id="msg-1",
            thread_id="thread-1",
            subject="Test Email",
            sender="test@example.com",
            recipients=["user@example.com"],
            snippet="Hello...",
            date=datetime.now(timezone.utc),
            labels=["INBOX"],
        )
    )

    # Create mock credential proxy with the mock client
    proxy = MockCredentialProxy(["gmail"])
    proxy.register_mock_client("gmail", gmail)

    # Use in tests
    client = proxy.get_client("gmail")
    emails = client.list_emails()
"""

from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any

from torivers_sdk.context.clients import (
    EmailDraft,
    EmailMessage,
    GmailClient,
    GoogleSheetsClient,
    ServiceClient,
    SlackChannel,
    SlackClient,
    SlackMessage,
    SpreadsheetInfo,
)
from torivers_sdk.context.credentials import CredentialProxy

# =============================================================================
# Mock Gmail Client
# =============================================================================


class MockGmailClient(GmailClient):
    """
    Mock Gmail client for testing.

    Stores emails in memory and simulates Gmail API operations.
    """

    def __init__(self) -> None:
        """Initialize mock Gmail client."""
        super().__init__(_auth_context={"mock": True})
        self._emails: list[EmailMessage] = []
        self._drafts: list[EmailDraft] = []
        self._sent_emails: list[dict[str, Any]] = []
        self._message_counter = 0

    def is_authenticated(self) -> bool:
        """Mock is always authenticated."""
        return True

    def add_mock_email(self, email: EmailMessage) -> None:
        """Add a mock email to the inbox."""
        self._emails.append(email)

    def add_mock_emails(self, emails: list[EmailMessage]) -> None:
        """Add multiple mock emails."""
        self._emails.extend(emails)

    def clear_mock_data(self) -> None:
        """Clear all mock data."""
        self._emails.clear()
        self._drafts.clear()
        self._sent_emails.clear()

    @property
    def sent_emails(self) -> list[dict[str, Any]]:
        """Get list of emails that were sent."""
        return list(self._sent_emails)

    def send_email(
        self,
        to: list[str],
        subject: str,
        body: str,
        cc: list[str] | None = None,
        bcc: list[str] | None = None,
        is_html: bool = False,
    ) -> str:
        """Send an email (stores in sent_emails list)."""
        self._message_counter += 1
        message_id = f"mock-msg-{self._message_counter}"

        self._sent_emails.append(
            {
                "id": message_id,
                "to": to,
                "subject": subject,
                "body": body,
                "cc": cc,
                "bcc": bcc,
                "is_html": is_html,
                "sent_at": datetime.now(timezone.utc).isoformat(),
            }
        )
        return message_id

    def list_emails(
        self,
        max_results: int = 10,
        query: str | None = None,
        label: str | None = None,
    ) -> list[EmailMessage]:
        """List emails (returns mock emails)."""
        result = self._emails

        if label:
            result = [e for e in result if label in e.labels]

        if query:
            query_lower = query.lower()
            result = [
                e
                for e in result
                if query_lower in e.subject.lower()
                or query_lower in e.sender.lower()
                or query_lower in e.snippet.lower()
            ]

        return result[:max_results]

    def get_email(self, message_id: str) -> EmailMessage:
        """Get a specific email by ID."""
        for email in self._emails:
            if email.id == message_id:
                return email
        raise ValueError(f"Email with ID '{message_id}' not found")

    def create_draft(self, draft: EmailDraft) -> str:
        """Create a draft."""
        self._message_counter += 1
        draft_id = f"mock-draft-{self._message_counter}"
        self._drafts.append(draft)
        return draft_id


# =============================================================================
# Mock Google Sheets Client
# =============================================================================


@dataclass
class MockSpreadsheet:
    """Mock spreadsheet data storage."""

    spreadsheet_id: str
    title: str
    sheets: dict[str, list[list[Any]]] = field(default_factory=dict)


class MockGoogleSheetsClient(GoogleSheetsClient):
    """
    Mock Google Sheets client for testing.

    Stores spreadsheet data in memory and simulates Sheets API operations.
    """

    def __init__(self) -> None:
        """Initialize mock Google Sheets client."""
        super().__init__(_auth_context={"mock": True})
        self._spreadsheets: dict[str, MockSpreadsheet] = {}

    def is_authenticated(self) -> bool:
        """Mock is always authenticated."""
        return True

    def add_mock_spreadsheet(
        self,
        spreadsheet_id: str,
        title: str,
        sheets: dict[str, list[list[Any]]] | None = None,
    ) -> None:
        """Add a mock spreadsheet."""
        self._spreadsheets[spreadsheet_id] = MockSpreadsheet(
            spreadsheet_id=spreadsheet_id,
            title=title,
            sheets=sheets or {"Sheet1": []},
        )

    def set_mock_data(
        self,
        spreadsheet_id: str,
        sheet_name: str,
        data: list[list[Any]],
    ) -> None:
        """Set data for a specific sheet."""
        if spreadsheet_id not in self._spreadsheets:
            self.add_mock_spreadsheet(spreadsheet_id, "Mock Spreadsheet")
        self._spreadsheets[spreadsheet_id].sheets[sheet_name] = data

    def clear_mock_data(self) -> None:
        """Clear all mock data."""
        self._spreadsheets.clear()

    def get_spreadsheet_info(self, spreadsheet_id: str) -> SpreadsheetInfo:
        """Get spreadsheet info."""
        if spreadsheet_id not in self._spreadsheets:
            raise ValueError(f"Spreadsheet '{spreadsheet_id}' not found")

        ss = self._spreadsheets[spreadsheet_id]
        return SpreadsheetInfo(
            spreadsheet_id=ss.spreadsheet_id,
            title=ss.title,
            sheets=list(ss.sheets.keys()),
            url=f"https://docs.google.com/spreadsheets/d/{spreadsheet_id}",
        )

    def get_range_values(
        self,
        spreadsheet_id: str,
        range: str,
        value_render_option: str = "FORMATTED_VALUE",
    ) -> list[list[Any]]:
        """Get values from a range."""
        if spreadsheet_id not in self._spreadsheets:
            raise ValueError(f"Spreadsheet '{spreadsheet_id}' not found")

        # Parse range (simplified - just returns all data from sheet)
        sheet_name = range.split("!")[0] if "!" in range else "Sheet1"
        ss = self._spreadsheets[spreadsheet_id]

        if sheet_name not in ss.sheets:
            return []

        return ss.sheets[sheet_name]

    def update_range(
        self,
        spreadsheet_id: str,
        range: str,
        values: list[list[Any]],
        value_input_option: str = "USER_ENTERED",
    ) -> int:
        """Update values in a range."""
        if spreadsheet_id not in self._spreadsheets:
            self.add_mock_spreadsheet(spreadsheet_id, "Mock Spreadsheet")

        sheet_name = range.split("!")[0] if "!" in range else "Sheet1"
        ss = self._spreadsheets[spreadsheet_id]
        ss.sheets[sheet_name] = values

        return sum(len(row) for row in values)

    def append_rows(
        self,
        spreadsheet_id: str,
        range: str,
        values: list[list[Any]],
    ) -> int:
        """Append rows to a sheet."""
        if spreadsheet_id not in self._spreadsheets:
            self.add_mock_spreadsheet(spreadsheet_id, "Mock Spreadsheet")

        sheet_name = range.split("!")[0] if "!" in range else "Sheet1"
        ss = self._spreadsheets[spreadsheet_id]

        if sheet_name not in ss.sheets:
            ss.sheets[sheet_name] = []

        ss.sheets[sheet_name].extend(values)
        return len(values)

    def clear_range(self, spreadsheet_id: str, range: str) -> None:
        """Clear values from a range."""
        if spreadsheet_id not in self._spreadsheets:
            return

        sheet_name = range.split("!")[0] if "!" in range else "Sheet1"
        ss = self._spreadsheets[spreadsheet_id]

        if sheet_name in ss.sheets:
            ss.sheets[sheet_name] = []


# =============================================================================
# Mock Slack Client
# =============================================================================


class MockSlackClient(SlackClient):
    """
    Mock Slack client for testing.

    Stores messages in memory and simulates Slack API operations.
    """

    def __init__(self) -> None:
        """Initialize mock Slack client."""
        super().__init__(_auth_context={"mock": True})
        self._channels: dict[str, SlackChannel] = {}
        self._messages: list[SlackMessage] = []
        self._reactions: list[dict[str, str]] = []
        self._message_counter = 0

    def is_authenticated(self) -> bool:
        """Mock is always authenticated."""
        return True

    def add_mock_channel(self, channel: SlackChannel) -> None:
        """Add a mock channel."""
        self._channels[channel.id] = channel
        self._channels[f"#{channel.name}"] = channel

    def clear_mock_data(self) -> None:
        """Clear all mock data."""
        self._channels.clear()
        self._messages.clear()
        self._reactions.clear()

    @property
    def sent_messages(self) -> list[SlackMessage]:
        """Get list of messages that were sent."""
        return list(self._messages)

    @property
    def added_reactions(self) -> list[dict[str, str]]:
        """Get list of reactions that were added."""
        return list(self._reactions)

    def send_message(
        self,
        channel: str,
        text: str,
        blocks: list[dict[str, Any]] | None = None,
        thread_ts: str | None = None,
    ) -> SlackMessage:
        """Send a message (stores in messages list)."""
        self._message_counter += 1
        ts = f"{self._message_counter}.{datetime.now(timezone.utc).timestamp()}"

        message = SlackMessage(
            ts=ts,
            channel=channel,
            text=text,
            user="mock-user",
            thread_ts=thread_ts,
        )
        self._messages.append(message)
        return message

    def get_channel(self, channel: str) -> SlackChannel:
        """Get channel information."""
        if channel in self._channels:
            return self._channels[channel]
        raise ValueError(f"Channel '{channel}' not found")

    def list_channels(
        self,
        exclude_archived: bool = True,
        types: str = "public_channel,private_channel",
    ) -> list[SlackChannel]:
        """List available channels."""
        # Return unique channels (avoid duplicates from ID and name keys)
        seen_ids: set[str] = set()
        result: list[SlackChannel] = []
        for channel in self._channels.values():
            if channel.id not in seen_ids:
                seen_ids.add(channel.id)
                result.append(channel)
        return result

    def update_message(
        self,
        channel: str,
        ts: str,
        text: str,
        blocks: list[dict[str, Any]] | None = None,
    ) -> SlackMessage:
        """Update an existing message."""
        for i, msg in enumerate(self._messages):
            if msg.ts == ts and msg.channel == channel:
                updated = SlackMessage(
                    ts=ts,
                    channel=channel,
                    text=text,
                    user=msg.user,
                    thread_ts=msg.thread_ts,
                )
                self._messages[i] = updated
                return updated
        raise ValueError(f"Message with ts '{ts}' not found in channel '{channel}'")

    def add_reaction(self, channel: str, ts: str, emoji: str) -> None:
        """Add a reaction to a message."""
        self._reactions.append(
            {
                "channel": channel,
                "ts": ts,
                "emoji": emoji,
            }
        )


# =============================================================================
# Mock Credential Proxy
# =============================================================================


class MockCredentialProxy(CredentialProxy):
    """
    Mock credential proxy for testing.

    Allows registering mock service clients for use in tests.

    Example:
        proxy = MockCredentialProxy(["gmail", "slack"])

        # Register mock clients
        proxy.register_mock_client("gmail", MockGmailClient())
        proxy.register_mock_client("slack", MockSlackClient())

        # Use in automation
        gmail = proxy.get_client("gmail")
        gmail.send_email(to=["test@example.com"], subject="Test", body="Hello")
    """

    def __init__(self, allowed_services: list[str]) -> None:
        """
        Initialize mock credential proxy.

        Args:
            allowed_services: List of allowed service names
        """
        super().__init__(allowed_services, _internal_context={"mock": True})

    def register_mock_client(self, service: str, client: ServiceClient) -> None:
        """
        Register a mock client for a service.

        Args:
            service: Service name
            client: Mock client instance

        Raises:
            CredentialNotAllowedError: If service not in allowed list
        """
        self._register_client(service, client)

    def unregister_mock_client(self, service: str) -> None:
        """
        Unregister a mock client.

        Args:
            service: Service name to unregister
        """
        self._unregister_client(service)

    def clear_all_mock_clients(self) -> None:
        """Clear all registered mock clients."""
        self._clear_all_clients()

    @classmethod
    def with_gmail(
        cls, allowed_services: list[str] | None = None
    ) -> "MockCredentialProxy":
        """
        Create a mock proxy with a Gmail client pre-registered.

        Args:
            allowed_services: Additional allowed services (gmail is added automatically)

        Returns:
            MockCredentialProxy with MockGmailClient registered
        """
        services = list(allowed_services or [])
        if "gmail" not in services:
            services.append("gmail")

        proxy = cls(services)
        proxy.register_mock_client("gmail", MockGmailClient())
        return proxy

    @classmethod
    def with_google_sheets(
        cls, allowed_services: list[str] | None = None
    ) -> "MockCredentialProxy":
        """
        Create a mock proxy with a Google Sheets client pre-registered.

        Args:
            allowed_services: Additional allowed services

        Returns:
            MockCredentialProxy with MockGoogleSheetsClient registered
        """
        services = list(allowed_services or [])
        if "google_sheets" not in services:
            services.append("google_sheets")

        proxy = cls(services)
        proxy.register_mock_client("google_sheets", MockGoogleSheetsClient())
        return proxy

    @classmethod
    def with_slack(
        cls, allowed_services: list[str] | None = None
    ) -> "MockCredentialProxy":
        """
        Create a mock proxy with a Slack client pre-registered.

        Args:
            allowed_services: Additional allowed services

        Returns:
            MockCredentialProxy with MockSlackClient registered
        """
        services = list(allowed_services or [])
        if "slack" not in services:
            services.append("slack")

        proxy = cls(services)
        proxy.register_mock_client("slack", MockSlackClient())
        return proxy

    @classmethod
    def with_all_services(cls) -> "MockCredentialProxy":
        """
        Create a mock proxy with all service clients pre-registered.

        Returns:
            MockCredentialProxy with all mock clients registered
        """
        proxy = cls(["gmail", "google_sheets", "slack"])
        proxy.register_mock_client("gmail", MockGmailClient())
        proxy.register_mock_client("google_sheets", MockGoogleSheetsClient())
        proxy.register_mock_client("slack", MockSlackClient())
        return proxy
