from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional

from asyncpg import Record
from smb.common.pgswim import PoolType, SwimEngine

from maps_adv.geosmb.tuner.server.lib.exceptions import UnknownBizId
from maps_adv.geosmb.tuner.server.lib.enums import PermissionFlag

from . import sqls


class BaseDataManager(ABC):
    __slots__ = ()

    @abstractmethod
    async def fetch_settings(self, biz_id: int) -> Optional[dict]:
        raise NotImplementedError

    @abstractmethod
    async def create_default_settings(
        self, biz_id: int, emails: List[str], phone: Optional[int]
    ) -> dict:
        raise NotImplementedError

    @abstractmethod
    async def update_settings(
        self,
        biz_id: int,
        emails: List[str],
        phone: Optional[int],
        notifications: Dict[str, bool],
        sms_notifications: Optional[Dict[str, bool]] = None,
        booking: Optional[dict] = None,
        requests: Optional[dict] = None,
    ) -> dict:
        raise NotImplementedError

    @abstractmethod
    async def check_settings_exist(self, biz_id: int) -> bool:
        raise NotImplementedError

    @abstractmethod
    async def fetch_settings_v2(self, biz_id: int) -> Optional[dict]:
        raise NotImplementedError

    @abstractmethod
    async def update_settings_v2(
        self,
        biz_id: int,
        contacts: Dict[str, List],
        notifications: Dict[str, bool],
        sms_notifications: Optional[Dict[str, bool]] = None,
        booking: Optional[dict] = None,
        requests: Optional[dict] = None,
    ) -> dict:
        raise NotImplementedError

    @abstractmethod
    async def fetch_permissions(self, biz_id: int) -> dict:
        raise NotImplementedError

    @abstractmethod
    async def check_permission(
        self,
        biz_id: int,
        passport_uid: int,
        flag: PermissionFlag
    ) -> bool:
        raise NotImplementedError

    @abstractmethod
    async def update_permission(
        self,
        biz_id: int,
        passport_uid: int,
        flags: List[PermissionFlag]
    ) -> None:
        raise NotImplementedError


class DataManager(BaseDataManager):
    __slots__ = ["_db"]

    _db: SwimEngine

    def __init__(self, db: SwimEngine):
        self._db = db

    async def fetch_settings(self, biz_id: int) -> Optional[dict]:
        row = await self._fetch_settings(biz_id)
        return self._format_settings(row)

    async def create_default_settings(
        self, biz_id: int, emails: List[str], phone: Optional[int]
    ) -> dict:
        default_email_notification_settings = dict(
            order_created=True,
            order_cancelled=True,
            order_changed=True,
            certificate_notifications=True,
            request_created=True,
        )
        default_sms_notification_settings = dict(
            request_created=True,
        )

        async with self._db.acquire(PoolType.master) as con:
            row = await con.fetchrow(
                sqls.insert_settings,
                biz_id,
                emails if emails else [],
                phone,
                default_email_notification_settings,
                default_sms_notification_settings,
                True,  # booking_enabled
                30,  # booking_slot_interval in minutes
                False,  # booking_export_to_ya_services
                True,  # requests_enabled
                "Отправить заявку",  # requests_button_text
            )

            return self._format_settings(row)

    async def update_settings(self, biz_id: int, **kwargs) -> dict:
        row = await self._update_settings(biz_id, **kwargs)
        return self._format_settings(row)

    async def fetch_settings_v2(self, biz_id: int) -> Optional[dict]:
        row = await self._fetch_settings(biz_id)
        return self._format_settings_v2(row)

    async def create_default_settings_v2(
        self, biz_id: int, contacts: Dict[str, Any] = {}
    ) -> dict:
        default_email_notification_settings = dict(
            order_created=True,
            order_cancelled=True,
            order_changed=True,
            certificate_notifications=True,
            request_created=True,
        )
        default_sms_notification_settings = dict(
            request_created=True,
        )

        if contacts:
            contacts['phones'] = contacts.get("phones") or []
            contacts['emails'] = contacts.get("emails") or []
            contacts['telegram_logins'] = contacts.get("telegram_logins") or []
        else:
            contacts = dict(emails=[], phones=[], telegram_logins=[])

        async with self._db.acquire(PoolType.master) as con:
            row = await con.fetchrow(
                sqls.insert_settings_v2,
                biz_id,
                contacts,
                default_email_notification_settings,
                default_sms_notification_settings,
                True,  # booking_enabled
                30,  # booking_slot_interval in minutes
                False,  # booking_export_to_ya_services
                True,  # requests_enabled
                "Отправить заявку",  # requests_button_text
            )

            return self._format_settings_v2(row)

    async def update_settings_v2(self, biz_id: int, **kwargs) -> dict:
        row = await self._update_settings(biz_id, **kwargs)
        return self._format_settings_v2(row)

    async def check_settings_exist(self, biz_id: int) -> bool:
        async with self._db.acquire(PoolType.replica) as con:
            return await con.fetchval(
                "SELECT EXISTS(SELECT * FROM settings_data WHERE biz_id = $1)", biz_id
            )

    async def fetch_permissions(self, biz_id: int) -> dict:
        async with self._db.acquire(PoolType.replica) as con:
            rows = await con.fetch(sqls.fetch_permissions, biz_id)

        return [dict(row) for row in rows]

    async def check_permission(
        self, biz_id: int, passport_uid: int, flag: PermissionFlag
    ) -> bool:
        async with self._db.acquire(PoolType.replica) as con:
            return await con.fetchval(
                sqls.check_permission_for_user, biz_id, passport_uid, flag
            )

    async def update_permission(
        self, biz_id: int, passport_uid: int, flags: List[PermissionFlag]
    ) -> dict:
        async with self._db.acquire(PoolType.master) as con:
            row = await con.fetchrow(
                sqls.update_permission_for_user,
                biz_id,
                passport_uid,
                flags
            )

        return dict(row)

    async def fetch_telegram_users(self, user_logins: List[str]) -> List[dict]:
        async with self._db.acquire(PoolType.replica) as con:
            rows = await con.fetch(
                sqls.fetch_telegram_users, user_logins
            )

        return [dict(row) for row in rows]

    async def check_telegram_user(
        self, user_login: str
    ) -> bool:
        async with self._db.acquire(PoolType.replica) as con:
            return await con.fetchval(
                sqls.check_telegram_user, user_login
            )

    async def update_telegram_user(self, user_id: int, user_login: str) -> dict:
        async with self._db.acquire(PoolType.master) as con:
            row = await con.fetchrow(
                sqls.update_telegram_user,
                user_id,
                user_login
            )

        return dict(row)

    async def delete_telegram_user(self, user_id: int) -> None:
        async with self._db.acquire(PoolType.master) as con:
            await con.fetchrow(
                sqls.delete_telegram_user,
                user_id
            )

    async def _fetch_settings(self, biz_id: int) -> Record:
        async with self._db.acquire(PoolType.replica) as con:
            row = await con.fetchrow(sqls.fetch_settings, biz_id)

        if not row:
            raise UnknownBizId(f"Settings for biz_id {biz_id} not found.")

        return row

    async def _update_settings(self, biz_id: int, **kwargs) -> Record:
        _booking = kwargs.pop("booking", None)
        if _booking:
            kwargs["booking_enabled"] = _booking["enabled"]
            kwargs["booking_slot_interval"] = _booking["slot_interval"]
            kwargs["booking_export_to_ya_services"] = _booking["online_settings"][
                "export_to_ya_services"
            ]
        _requests = kwargs.pop("requests", None)
        if _requests:
            kwargs["requests_enabled"] = _requests["enabled"]
            kwargs["requests_button_text"] = _requests["button_text"]

        _sql_update = ",".join(
            (
                f"{key} = {key} || ${i}"
                if key in ("notifications", "sms_notifications")
                else f"{key} = ${i}"
            )
            for i, key in enumerate(kwargs.keys(), 2)
        )
        async with self._db.acquire(PoolType.master) as con:
            row = await con.fetchrow(
                f"UPDATE settings_data SET {_sql_update} WHERE biz_id = $1 RETURNING *",
                biz_id,
                *kwargs.values(),
            )

        return row

    @staticmethod
    def _format_settings(row: Record) -> dict:
        return {
            "emails": row["emails"],
            "phone": row["phone"],
            "notifications": row["notifications"],
            "sms_notifications": row["sms_notifications"],
            "booking": {
                "enabled": row["booking_enabled"],
                "slot_interval": row["booking_slot_interval"],
                "online_settings": {
                    "export_to_ya_services": row["booking_export_to_ya_services"]
                },
            },
            "requests": {
                "enabled": row["requests_enabled"],
                "button_text": row["requests_button_text"],
            },
            "general_schedule_id": row["general_schedule_id"],
        }

    @staticmethod
    def _format_settings_v2(row: Record) -> dict:
        return {
            "contacts": row["contacts"],
            "notifications": row["notifications"],
            "sms_notifications": row["sms_notifications"],
            "booking": {
                "enabled": row["booking_enabled"],
                "slot_interval": row["booking_slot_interval"],
                "online_settings": {
                    "export_to_ya_services": row["booking_export_to_ya_services"]
                },
            },
            "requests": {
                "enabled": row["requests_enabled"],
                "button_text": row["requests_button_text"],
            },
            "general_schedule_id": row["general_schedule_id"],
        }
