"""Test fixtures. Every test uses a temporary SQLite database and a fake gateway.

No network access, real bot token, source group or destination channel is used.
"""

from __future__ import annotations

import itertools
from collections.abc import AsyncIterator
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

import pytest
import pytest_asyncio
from aiogram.exceptions import (
    TelegramBadRequest,
    TelegramNetworkError,
    TelegramRetryAfter,
    TelegramServerError,
)

from app.config import Settings
from app.context import AppContext, build_context
from app.gateways.base import BotIdentity, ChatInfo, ChatMemberInfo, MessageRef
from app.services.media_service import IncomingMedia


@dataclass
class FakeGateway:
    """Deterministic Telegram gateway for service-level tests."""

    bot_id: int = 999_000_001
    bot_username: str = "smoke_bot"
    chats: dict[int, ChatInfo] = field(default_factory=dict)
    memberships: dict[tuple[int, int], str] = field(default_factory=dict)
    member_errors: set[int] = field(default_factory=set)
    bot_admin: set[int] = field(default_factory=set)
    sent_videos: list[dict[str, Any]] = field(default_factory=list)
    sent_messages: list[dict[str, Any]] = field(default_factory=list)
    deleted: list[tuple[int, int]] = field(default_factory=list)
    send_attempts: int = 0
    send_error: BaseException | None = None
    send_errors: list[BaseException] = field(default_factory=list)
    delete_error: BaseException | None = None
    chat_error: BaseException | None = None
    _message_ids: itertools.count = field(default_factory=lambda: itertools.count(1000))

    async def get_me(self) -> BotIdentity:
        return BotIdentity(id=self.bot_id, username=self.bot_username)

    async def get_chat(self, chat_id: int) -> ChatInfo:
        if self.chat_error is not None:
            raise self.chat_error
        if chat_id in self.chats:
            return self.chats[chat_id]
        return ChatInfo(chat_id=chat_id, title=f"Chat {chat_id}", username=None, chat_type="channel")

    async def get_chat_member(self, chat_id: int, user_id: int) -> ChatMemberInfo:
        if chat_id in self.member_errors:
            raise TelegramBadRequest(None, "membership API failed")
        if (chat_id, user_id) in self.memberships:
            status = self.memberships[(chat_id, user_id)]
            return ChatMemberInfo(status=status, is_member=status == "member")
        if user_id == self.bot_id and chat_id in self.bot_admin:
            return ChatMemberInfo(status="administrator")
        return ChatMemberInfo(status="left")

    async def send_video(
        self,
        chat_id: int,
        file_id: str,
        *,
        caption: str | None,
        thumbnail_file_id: str | None = None,
        protect_content: bool = False,
        buttons: list[tuple[str, str]] | None = None,
    ) -> MessageRef:
        self.send_attempts += 1
        if self.send_errors:
            raise self.send_errors.pop(0)
        if self.send_error is not None:
            raise self.send_error
        message_id = next(self._message_ids)
        self.sent_videos.append(
            {
                "chat_id": chat_id,
                "file_id": file_id,
                "caption": caption,
                "thumbnail_file_id": thumbnail_file_id,
                "protect_content": protect_content,
                "buttons": buttons,
                "message_id": message_id,
            }
        )
        return MessageRef(chat_id=chat_id, message_id=message_id)

    async def send_message(
        self,
        chat_id: int,
        text: str,
        *,
        protect_content: bool = False,
        buttons: list[tuple[str, str]] | None = None,
    ) -> MessageRef:
        message_id = next(self._message_ids)
        self.sent_messages.append(
            {
                "chat_id": chat_id,
                "text": text,
                "protect_content": protect_content,
                "buttons": buttons,
                "message_id": message_id,
            }
        )
        return MessageRef(chat_id=chat_id, message_id=message_id)

    async def delete_message(self, chat_id: int, message_id: int) -> None:
        if self.delete_error is not None:
            raise self.delete_error
        self.deleted.append((chat_id, message_id))


def make_settings(tmp_path: Path, **overrides: Any) -> Settings:
    values: dict[str, Any] = {
        "bot_token": "123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghi",
        "admin_ids": [111_000_001, 222_000_002],
        "bot_username": "smoke_bot",
        "database_url": f"sqlite+aiosqlite:///{(tmp_path / 'test.db').as_posix()}",
        "auto_create_schema": True,
        "timezone": "Asia/Tehran",
        "data_dir": tmp_path / "data",
        "log_file": tmp_path / "logs" / "test.log",
        "retry_base_delay_seconds": 0,
        "delivery_rate_limit_requests": 100,
        "delivery_min_interval_seconds": 0,
        "job_max_attempts": 3,
        "scheduler_poll_seconds": 1,
    }
    values.update(overrides)
    return Settings(**values)


@pytest_asyncio.fixture
async def ctx(tmp_path: Path) -> AsyncIterator[AppContext]:
    gateway = FakeGateway()
    context = await build_context(make_settings(tmp_path), gateway)
    try:
        yield context
    finally:
        await context.close()


@pytest_asyncio.fixture
async def gateway(ctx: AppContext) -> FakeGateway:
    return ctx.gateway  # type: ignore[return-value]


@pytest.fixture
def incoming() -> IncomingMedia:
    return IncomingMedia(
        chat_id=-100_100_100,
        message_id=10,
        chat_type="supergroup",
        chat_title="Source",
        file_id="file-10",
        file_unique_id="unique-10",
        media_type="video",
        mime_type="video/mp4",
        file_name="clip.mp4",
        file_size=1024,
        duration_seconds=12.5,
        width=1920,
        height=1080,
        thumbnail_file_id="thumb-10",
        caption="Original caption",
    )


def network_error(message: str = "timeout") -> TelegramNetworkError:
    return TelegramNetworkError(None, message)


def server_error(message: str = "server error") -> TelegramServerError:
    return TelegramServerError(None, message)


def retry_after(seconds: int = 1) -> TelegramRetryAfter:
    return TelegramRetryAfter(None, "too many requests", seconds)


def bad_request(message: str = "bad request") -> TelegramBadRequest:
    return TelegramBadRequest(None, message)
