# platform/mock.py
from __future__ import annotations

import asyncio
import random
import uuid
from collections.abc import AsyncIterator
from datetime import UTC, datetime
from typing import Any, Literal

import structlog

from sdk.interface import (
    AgentEvent,
    Attachment,
    MessageChunk,
    MessageResponse,
    User,
    UserSettings,
    WebhookReceiver,
)

logger = structlog.get_logger(__name__)


DEFAULT_SKILLS = {
    "web-search": True,
    "fetch-url": True,
    "email": False,
    "browser": False,
    "image-gen": False,
    "files": True,
}

DEFAULT_SAFETY = {
    "email-send": True,
    "file-delete": True,
    "social-post": True,
}

DEFAULT_SOUL = {"name": "Лямбда", "instructions": ""}

DEFAULT_PLAN = {
    "name": "Beta",
    "tokens_used": 0,
    "tokens_limit": 1000,
}


class MockPlatformClient:
    """
    Заглушка SDK платформы Lambda.

    Реализует PlatformClient Protocol. При подключении реального SDK
    заменяется только этот файл — core/ и адаптеры не трогаются.

    attachment_mode — симулирует разные варианты передачи файлов:
        "url"    — платформа получает URL, скачивает сама (текущий план)
        "binary" — бинарные данные в теле (резерв)
        "s3"     — pre-signed S3 URL (резерв)

    Webhook: зарегистрируй WebhookReceiver через register_webhook_receiver(),
    вызови simulate_agent_event() чтобы имитировать входящее уведомление.
    """

    def __init__(
        self,
        attachment_mode: Literal["url", "binary", "s3"] = "url",
    ) -> None:
        self.attachment_mode = attachment_mode
        self._users: dict[str, dict] = {}
        self._messages: dict[str, list] = {}  # "{user_id}:{chat_id}" → messages
        self._settings: dict[str, dict] = {}
        self._webhook_receiver: WebhookReceiver | None = None
        logger.info("MockPlatformClient initialized", attachment_mode=attachment_mode)

    # ------------------------------------------------------------------ users

    async def get_or_create_user(
        self,
        external_id: str,
        platform: str,
        display_name: str | None = None,
    ) -> User:
        await self._latency()
        key = f"{platform}:{external_id}"
        is_new = key not in self._users
        if is_new:
            self._users[key] = {
                "user_id": f"usr-{platform}-{external_id}",
                "external_id": external_id,
                "platform": platform,
                "display_name": display_name,
                "created_at": "2025-01-01T00:00:00Z",
                "is_new": True,
            }
        data = {**self._users[key], "is_new": is_new}
        return User(**data)

    # --------------------------------------------------------------- messages

    async def send_message(
        self,
        user_id: str,
        chat_id: str,
        text: str,
        attachments: list[Attachment] | None = None,
    ) -> MessageResponse:
        await self._latency(200, 600)
        message_id, response, tokens = self._build_response(user_id, chat_id, text, attachments)
        logger.info("send_message", user_id=user_id, chat_id=chat_id, message_id=message_id)
        return MessageResponse(
            message_id=message_id,
            response=response,
            tokens_used=tokens,
            finished=True,
        )

    async def stream_message(
        self,
        user_id: str,
        chat_id: str,
        text: str,
        attachments: list[Attachment] | None = None,
    ) -> AsyncIterator[MessageChunk]:
        """
        Сейчас: один чанк с полным ответом.
        При реальном SDK: заменить на SSE/WebSocket итератор.
        Адаптеры переписывать не нужно.
        """
        await self._latency(200, 600)
        message_id, response, tokens = self._build_response(user_id, chat_id, text, attachments)
        logger.info("stream_message", user_id=user_id, chat_id=chat_id, message_id=message_id)
        yield MessageChunk(
            message_id=message_id,
            delta=response,
            finished=True,
            tokens_used=tokens,
        )

    # --------------------------------------------------------------- settings

    async def get_settings(self, user_id: str) -> UserSettings:
        await self._latency()
        stored = self._settings.get(user_id, {})
        return UserSettings(
            skills={**DEFAULT_SKILLS, **stored.get("skills", {})},
            connectors=stored.get("connectors", {}),
            soul={**DEFAULT_SOUL, **stored.get("soul", {})},
            safety={**DEFAULT_SAFETY, **stored.get("safety", {})},
            plan={**DEFAULT_PLAN, **stored.get("plan", {})},
        )

    async def update_settings(self, user_id: str, action: Any) -> None:
        await self._latency()
        settings = self._settings.setdefault(user_id, {})

        if action.action == "toggle_skill":
            skills = settings.setdefault("skills", DEFAULT_SKILLS.copy())
            skills[action.payload["skill"]] = action.payload.get("enabled", True)
        elif action.action == "set_soul":
            soul = settings.setdefault("soul", DEFAULT_SOUL.copy())
            soul[action.payload["field"]] = action.payload["value"]
        elif action.action == "set_safety":
            safety = settings.setdefault("safety", DEFAULT_SAFETY.copy())
            safety[action.payload["trigger"]] = action.payload.get("enabled", True)

        logger.info("Settings updated", user_id=user_id, action=action.action)

    # --------------------------------------------------------------- webhooks

    def register_webhook_receiver(self, receiver: WebhookReceiver) -> None:
        """Бот регистрирует свой обработчик входящих событий от платформы."""
        self._webhook_receiver = receiver
        logger.info("WebhookReceiver registered")

    async def simulate_agent_event(
        self,
        user_id: str,
        chat_id: str,
        event_type: Literal["task_done", "task_error", "task_progress"] = "task_done",
        payload: dict | None = None,
    ) -> None:
        """Имитирует входящий webhook от платформы. Используется в тестах и ручном QA."""
        if self._webhook_receiver is None:
            logger.warning("simulate_agent_event: no WebhookReceiver registered")
            return
        event = AgentEvent(
            event_id=str(uuid.uuid4()),
            user_id=user_id,
            chat_id=chat_id,
            event_type=event_type,
            payload=payload or {"message": "[MOCK] Долгая задача выполнена"},
        )
        await self._webhook_receiver.on_agent_event(event)

    # ------------------------------------------------------------------ utils

    def get_stats(self) -> dict:
        return {
            "total_users": len(self._users),
            "total_messages": sum(len(msgs) for msgs in self._messages.values()),
            "attachment_mode": self.attachment_mode,
        }

    def _build_response(
        self,
        user_id: str,
        chat_id: str,
        text: str,
        attachments: list[Attachment] | None,
    ) -> tuple[str, str, int]:
        key = f"{user_id}:{chat_id}"
        if key not in self._messages:
            self._messages[key] = []

        message_id = str(uuid.uuid4())
        preview = text[:50] + ("..." if len(text) > 50 else "")
        attachment_note = ""
        if attachments:
            names = [a.filename or a.mime_type for a in attachments]
            attachment_note = f" [вложения: {', '.join(names)}]"
        response = f"[MOCK] Ответ на: «{preview}»{attachment_note}"
        tokens = len(text.split()) * 2

        self._messages[key].append(
            {
                "message_id": message_id,
                "user_text": text,
                "response": response,
                "tokens_used": tokens,
                "finished": True,
                "created_at": datetime.now(UTC).isoformat(),
            }
        )
        return message_id, response, tokens

    async def _latency(self, min_ms: int = 10, max_ms: int = 80) -> None:
        await asyncio.sleep(random.randint(min_ms, max_ms) / 1000)
