"""API для работы с пользователями."""

from datetime import date, datetime
from typing import Optional, TYPE_CHECKING

from .models import User, KeyPair, UserConfig, PaginatedResponse
from .exceptions import NotFoundError, APIError

if TYPE_CHECKING:
    from .client import AmnezioClient


class UsersAPI:
    """API для работы с пользователями."""
    
    def __init__(self, client: "AmnezioClient"):
        self.client = client

    @staticmethod
    def _extract_error_message(response) -> str:
        """Достает detail из JSON-ошибки, иначе возвращает raw text."""
        try:
            data = response.json()
            if isinstance(data, dict):
                detail = data.get("detail")
                if detail is not None:
                    return str(detail)
        except Exception:
            pass
        return response.text
    
    async def list(
        self,
        profile_id: Optional[int] = None,
        tag: Optional[str] = None,
        q: Optional[str] = None,
        page: int = 1,
        per_page: int = 50,
    ) -> PaginatedResponse:
        """
        Получить список пользователей.
        
        Args:
            profile_id: Фильтр по профилю
            tag: Фильтр по тегу
            q: Поиск по sub_id, telegram_id, tag, описанию
            page: Номер страницы
            per_page: Количество записей на странице
        
        Returns:
            Пагинированный список пользователей
        """
        params = {"page": page, "per_page": per_page}
        
        if profile_id is not None:
            params["profile_id"] = profile_id
        if tag is not None:
            params["tag"] = tag
        if q is not None:
            params["q"] = q
        
        response = await self.client.request("GET", "/api/users", params=params)
        
        if response.status_code != 200:
            raise APIError(response.status_code, response.text)
        
        data = response.json()
        data["items"] = [User(**item) for item in data["items"]]
        
        return PaginatedResponse(**data)
    
    async def create(
        self,
        sub_id: str,
        private_key: Optional[str] = None,
        public_key: Optional[str] = None,
        preshared_key: Optional[str] = None,
        paid_for: Optional[datetime] = None,
        telegram_id: Optional[int] = None,
        tag: Optional[str] = None,
        description: Optional[str] = None,
        profile_id: Optional[int] = None,
    ) -> User:
        """
        Создать пользователя.
        
        Args:
            sub_id: ID подписки
            private_key: Приватный ключ (опционально, если передан public_key)
            public_key: Публичный ключ (опционально, вычисляется из private_key)
            preshared_key: Preshared key (опционально)
            paid_for: Дата окончания подписки
            telegram_id: ID в Telegram
            tag: Тег для группировки
            description: Описание
            profile_id: ID профиля
        
        Returns:
            Созданный пользователь
        """
        data = {"sub_id": sub_id}
        
        if private_key:
            data["private_key"] = private_key
        if public_key:
            data["public_key"] = public_key
        if preshared_key:
            data["preshared_key"] = preshared_key
        if paid_for:
            data["paid_for"] = paid_for.isoformat()
        if telegram_id is not None:
            data["telegram_id"] = telegram_id
        if tag:
            data["tag"] = tag
        if description:
            data["description"] = description
        if profile_id:
            data["profile_id"] = profile_id
        
        response = await self.client.request("POST", "/api/users", json=data)
        
        if response.status_code == 422:
            raise APIError(422, "Необходимо указать private_key или public_key")
        
        if response.status_code != 200:
            raise APIError(response.status_code, response.text)
        
        return User(**response.json())
    
    async def get(self, user_id: int) -> User:
        """
        Получить пользователя по ID.
        
        Args:
            user_id: ID пользователя
        
        Returns:
            Пользователь
        """
        response = await self.client.request("GET", f"/api/users/{user_id}")
        
        if response.status_code == 404:
            raise NotFoundError(f"Пользователь {user_id} не найден")
        
        if response.status_code != 200:
            raise APIError(response.status_code, response.text)
        
        return User(**response.json())
    
    async def update(
        self,
        user_id: int,
        sub_id: Optional[str] = None,
        public_key: Optional[str] = None,
        preshared_key: Optional[str] = None,
        paid_for: Optional[datetime] = None,
        telegram_id: Optional[int] = None,
        tag: Optional[str] = None,
        description: Optional[str] = None,
        profile_id: Optional[int] = None,
        status: Optional[str] = None,
    ) -> User:
        """
        Обновить пользователя.
        
        Args:
            user_id: ID пользователя
            sub_id: ID подписки
            public_key: Публичный ключ
            preshared_key: Preshared key
            paid_for: Дата окончания подписки
            telegram_id: ID в Telegram
            tag: Тег
            description: Описание
            profile_id: ID профиля
            status: Статус (active/disabled)
        
        Returns:
            Обновленный пользователь
        """
        data = {}
        
        if sub_id is not None:
            data["sub_id"] = sub_id
        if public_key is not None:
            data["public_key"] = public_key
        if preshared_key is not None:
            data["preshared_key"] = preshared_key
        if paid_for is not None:
            data["paid_for"] = paid_for.isoformat()
        if telegram_id is not None:
            data["telegram_id"] = telegram_id
        if tag is not None:
            data["tag"] = tag
        if description is not None:
            data["description"] = description
        if profile_id is not None:
            data["profile_id"] = profile_id
        if status is not None:
            data["status"] = status
        
        response = await self.client.request("PUT", f"/api/users/{user_id}", json=data)
        
        if response.status_code == 404:
            raise NotFoundError(f"Пользователь {user_id} не найден")
        
        if response.status_code != 200:
            raise APIError(response.status_code, response.text)
        
        return User(**response.json())
    
    async def delete(self, user_id: int) -> dict:
        """
        Удалить пользователя.
        
        Args:
            user_id: ID пользователя
        
        Returns:
            Статус операции
        """
        response = await self.client.request("DELETE", f"/api/users/{user_id}")
        
        if response.status_code == 404:
            raise NotFoundError(f"Пользователь {user_id} не найден")
        
        if response.status_code != 200:
            raise APIError(response.status_code, response.text)
        
        return response.json()
    
    async def get_config(self, user_id: int) -> UserConfig:
        """
        Получить конфиг WireGuard для пользователя.
        
        Args:
            user_id: ID пользователя
        
        Returns:
            Конфиг пользователя
        """
        response = await self.client.request("GET", f"/api/users/{user_id}/config")
        
        if response.status_code == 404:
            raise NotFoundError(f"Пользователь {user_id} не найден")
        
        if response.status_code == 403:
            raise APIError(403, self._extract_error_message(response))
        
        if response.status_code != 200:
            raise APIError(response.status_code, response.text)
        
        return UserConfig(**response.json())

    async def get_by_sub_id(self, sub_id: str) -> User | None:
        """
        Получить пользователя по sub_id.

        Args:
            sub_id: ID подписки (строка)

        Returns:
            Пользователь или None, если не найден
        """
        response = await self.client.request("GET", f"/api/users/by-sub-id/{sub_id}")

        if response.status_code == 404:
            return None

        if response.status_code != 200:
            raise APIError(response.status_code, response.text)

        return User(**response.json())
    
    async def get_qrcode(self, user_id: int) -> bytes:
        """
        Получить QR-код с конфигом пользователя.
        
        Args:
            user_id: ID пользователя
        
        Returns:
            PNG изображение QR-кода
        """
        response = await self.client.request("GET", f"/api/users/{user_id}/qrcode")
        
        if response.status_code == 404:
            raise NotFoundError(f"Пользователь {user_id} не найден")
        
        if response.status_code == 403:
            raise APIError(403, self._extract_error_message(response))
        
        if response.status_code != 200:
            raise APIError(response.status_code, response.text)
        
        return response.content
    
    async def enable(self, user_id: int) -> User:
        """
        Включить пользователя (установить status=active).
        
        Args:
            user_id: ID пользователя
        
        Returns:
            Обновленный пользователь
        """
        return await self.update(user_id, status="active")
    
    async def disable(self, user_id: int) -> User:
        """
        Отключить пользователя (установить status=disabled).
        
        Args:
            user_id: ID пользователя
        
        Returns:
            Обновленный пользователь
        """
        return await self.update(user_id, status="disabled")
    
    async def renew_subscription(self, user_id: int, paid_for: datetime) -> User:
        """
        Продлить подписку пользователя.
        
        Args:
            user_id: ID пользователя
            paid_for: Новая дата окончания подписки
        
        Returns:
            Обновленный пользователь
        """
        return await self.update(user_id, paid_for=paid_for, status="active")

    async def renew_subscription_by_sub_id(self, sub_id: str, paid_for: datetime) -> User:
        """
        Продлить подписку по sub_id.

        Args:
            sub_id: ID подписки
            paid_for: Новая дата окончания подписки

        Returns:
            Обновленный пользователь
        """
        response = await self.client.request(
            "POST",
            f"/api/users/by-sub-id/{sub_id}/renew",
            json={"paid_for": paid_for.isoformat()},
        )

        if response.status_code == 404:
            raise NotFoundError(f"Пользователь с sub_id={sub_id} не найден")

        if response.status_code != 200:
            raise APIError(response.status_code, response.text)

        return User(**response.json())

    async def enable_by_sub_id(self, sub_id: str) -> User:
        """
        Включить пользователя по sub_id.

        Args:
            sub_id: ID подписки

        Returns:
            Обновленный пользователь
        """
        response = await self.client.request(
            "POST",
            f"/api/users/by-sub-id/{sub_id}/enable",
        )

        if response.status_code == 404:
            raise NotFoundError(f"Пользователь с sub_id={sub_id} не найден")

        if response.status_code != 200:
            raise APIError(response.status_code, response.text)

        return User(**response.json())

    async def disable_by_sub_id(self, sub_id: str) -> User:
        """
        Отключить пользователя по sub_id.

        Args:
            sub_id: ID подписки

        Returns:
            Обновленный пользователь
        """
        response = await self.client.request(
            "POST",
            f"/api/users/by-sub-id/{sub_id}/disable",
        )

        if response.status_code == 404:
            raise NotFoundError(f"Пользователь с sub_id={sub_id} не найден")

        if response.status_code != 200:
            raise APIError(response.status_code, response.text)

        return User(**response.json())

    async def delete_by_sub_id(self, sub_id: str) -> dict:
        """
        Удалить пользователя по sub_id.

        Args:
            sub_id: ID подписки

        Returns:
            Статус операции
        """
        response = await self.client.request(
            "DELETE",
            f"/api/users/by-sub-id/{sub_id}",
        )

        if response.status_code == 404:
            raise NotFoundError(f"Пользователь с sub_id={sub_id} не найден")

        if response.status_code != 200:
            raise APIError(response.status_code, response.text)

        return response.json()

    async def get_config_by_sub_id(self, sub_id: str) -> UserConfig:
        """
        Получить конфиг пользователя по sub_id.

        Args:
            sub_id: ID подписки

        Returns:
            Конфиг пользователя
        """
        response = await self.client.request(
            "GET",
            f"/api/users/by-sub-id/{sub_id}/config",
        )

        if response.status_code == 404:
            raise NotFoundError(f"Пользователь с sub_id={sub_id} не найден")

        if response.status_code == 403:
            raise APIError(403, self._extract_error_message(response))

        if response.status_code != 200:
            raise APIError(response.status_code, response.text)

        return UserConfig(**response.json())
    
    async def generate_keypair(self) -> KeyPair:
        """
        Сгенерировать новую пару ключей WireGuard.
        
        Returns:
            Пара ключей (private, public, preshared)
        """
        response = await self.client.request("POST", "/api/users/generate-keypair")
        
        if response.status_code != 200:
            raise APIError(response.status_code, response.text)
        
        return KeyPair(**response.json())
