from typing import List, TypedDict
from psycopg.types.json import Json

import orjson

from src.internals.cache.decorator import cache
from src.internals.cache.redis import get_conn
from src.internals.database.database import get_cursor, query_rowcount_db
from src.internals.serializers import safe_dumper, safe_loader
from src.types.account import Notification


@cache("account_notifications")
def count_account_notifications(account_id: int) -> int:
    """
    TODO: fix `psycopg.ProgrammingError: no results to fetch` error
    """
    try:
        args_dict = {"account_id": account_id}

        cursor = get_cursor()
        query = """
            SELECT
                COUNT(*) AS notifications_count
            FROM notifications
            WHERE account_id = %(account_id)s
        """
        cursor.execute(query, args_dict)
        result = cursor.fetchone()
        notifications_count = result["notifications_count"]
        return notifications_count
    except Exception:
        return 0


@cache("new_notifications")
def count_new_notifications(account_id: int) -> int:
    """
    TODO: fix `psycopg.ProgrammingError: no results to fetch` error
    """
    try:
        args_dict = dict(account_id=account_id)

        cursor = get_cursor()
        query = """
            SELECT
                COUNT(*) as new_notifications_count
            FROM notifications
            WHERE
                account_id = %(account_id)s
                AND is_seen = FALSE
        """
        cursor.execute(query, args_dict)
        # doing this to avoid `psycopg.ProgrammingError: no results to fetch` error
        if not cursor.rowcount:
            return 0
        result = cursor.fetchone()
        new_notifications_count: int = result["new_notifications_count"]
        return new_notifications_count
    except Exception:
        return 0


def set_notifications_as_seen(notification_ids: List[int]) -> bool:
    query = """
        UPDATE notifications
        SET is_seen = TRUE
        WHERE id = ANY (%s)
    """
    updated_count = query_rowcount_db(query, (notification_ids,))
    return bool(updated_count)


def get_account_notifications(account_id: int, reload: bool = False) -> List[Notification]:
    redis = get_conn()
    key = f"notifications_for_account:{account_id}"
    notifications = redis.get(key)
    result = None

    if notifications is None or reload:
        args_dict = dict(account_id=account_id)

        cursor = get_cursor()
        query = """
            SELECT id, account_id, type, created_at, is_seen, extra_info
            FROM notifications
            WHERE account_id = %(account_id)s
            ORDER BY
                created_at DESC
        """
        cursor.execute(query, args_dict)
        result = cursor.fetchall()
        redis.set(key, safe_dumper(result), ex=60)
    else:
        result = safe_loader(notifications)
    # TODO: fix this mess
    notifications = [Notification.init_from_dict(notification) for notification in result]
    return notifications


class TDNotificationInit(TypedDict):
    account_id: str
    type: int
    extra_info: dict


def send_notifications(notification_inits: list[TDNotificationInit]) -> bool:
    cursor = get_cursor()

    params = dict(
        notification_inits=Json(notification_inits)
    )
    insert_query = """
        WITH notification_inits AS (
            SELECT
                notification_init.account_id,
                notification_init.type,
                notification_init.extra_info
            FROM
                json_to_recordset(%(notification_inits)s) AS notification_init(
                    account_id int,
                    type smallint,
                    extra_info jsonb
                )
        )
        INSERT INTO notifications
            (
                account_id,
                type,
                extra_info
            )
        SELECT
            account_id,
            type,
            extra_info
        FROM
            notification_inits
        """
    cursor.execute(insert_query, params)
    account_ids = [
        init["account_id"]
        for init
        in notification_inits
    ]
    for account_id in account_ids:
        redis = get_conn()
        redis.delete(f"account_notifications:{account_id}")
        redis.delete(f"new_notifications:{account_id}")

    return True
