import datetime
from typing import Optional, TypedDict, Literal, Dict

from src.config import Configuration
from src.internals.cache.redis import get_conn
from src.internals.database.database import cached_query, get_cursor, get_pool
from src.internals.serializers import unsafe_dumper, unsafe_loader
from src.internals.serializers.artist import (
    deserialize_artist,
    deserialize_artists,
    serialize_artist,
    serialize_artists,
)
from src.utils.utils import clear_web_cache_for_creator_links

class TDArtist(TypedDict):
    id: str
    name: str
    service: str
    indexed: str
    updated: str
    public_id: str
    relation_id: int


class TDArtistWithFavs(TDArtist):
    count: int


def get_top_artists_by_faves(offset, count, reload=False) -> list[TDArtistWithFavs]:
    key = f"top_artists:{offset}:{count}"
    params = dict(offset=offset, limit=count)
    query = """
        SELECT
            artists.id,
            artists.name,
            artists.service,
            artists.indexed,
            artists.updated,
            artists.public_id,
            artists.relation_id,
            fc.favorite_count AS count
        FROM
            lookup AS artists
            INNER JOIN
            favorite_counts AS fc
            ON
                artists.id = fc.artist_id
                AND
                artists.service = fc.service
        WHERE
            (artists.id, artists.service) NOT IN (
                SELECT
                    id,
                    service
                FROM
                    dnp
            )
        ORDER BY
            count DESC
        OFFSET %(offset)s
        LIMIT %(limit)s
    """

    return cached_query(
        query,
        key,
        params,
        serialize_artists,
        deserialize_artists,
        reload,
        ex=int(datetime.timedelta(hours=24).total_seconds()),
        lock_enabled=True,
    )


def get_random_artist_keys(count, reload=False):
    key = f"random_artist_keys:{count}"
    query = "SELECT id, service FROM lookup ORDER BY random() LIMIT %s"
    return cached_query(query, key, (count,), unsafe_dumper, unsafe_loader, reload, lock_enabled=True)


def get_artist(service: str, artist_id: str, reload: bool = False) -> TDArtist:
    key = f"artist:{service}:{artist_id}"
    params = dict(artist_id=artist_id, service=service)
    id_filter = (
        "(id = %(artist_id)s or public_id = %(artist_id)s)"
        if service == ("onlyfans", "fansly", "candfans", "patreon", "fanbox", "boosty")
        else "id = %(artist_id)s"
    )
    query = f"""
        SELECT
            id,
            name,
            service,
            indexed,
            updated,
            public_id,
            relation_id
        FROM
            lookup
        WHERE
            {id_filter}
            AND service = %(service)s
            AND (id, service) NOT IN (
                SELECT
                    id,
                    service
                FROM dnp
            );
    """
    return cached_query(
        query,
        key,
        params,
        serialize_artist,
        deserialize_artist,
        reload,
        True,
        86400,
        Configuration().redis["default_ttl"],
    )


def get_artists_by_update_time(offset, limit, reload=False) -> list[TDArtist]:
    key = f"artists_by_update_time:{offset}:{limit}"
    params = dict(offset=offset, limit=limit)
    query = """
        SELECT
            id,
            name,
            service,
            indexed,
            updated,
            public_id,
            relation_id
        FROM
            lookup
        WHERE
            (id, service) NOT IN (
                SELECT
                    id,
                    service
                FROM
                    dnp
            )
        ORDER BY
            updated DESC
        OFFSET %(offset)s
        LIMIT %(limit)s
    """

    return cached_query(query, key, params, serialize_artists, deserialize_artists, reload)


def get_fancards_by_artist(artist_id, reload=False):
    key = f"fancards:{artist_id}"
    query = "select * from fanbox_fancards left join files on file_id = files.id where user_id = %s and file_id is not null order by added desc"
    return cached_query(query, key, (artist_id,), reload=reload)


def create_unapproved_link_request(from_artist, to_artist, user_id, reason: Optional[str]):
    query = """
    INSERT INTO unapproved_link_requests (from_service, from_id, to_service, to_id, requester_id, reason)
    VALUES (%s, %s, %s, %s, %s, %s)
    ON CONFLICT DO NOTHING
    """
    cur = get_cursor()
    cur.execute(
        query,
        (from_artist["service"], from_artist["id"], to_artist["service"], to_artist["id"], user_id, reason or None),
    )


class TDUnapprovedLink(TypedDict):
    id: int
    from_service: str
    from_id: str
    to_service: str
    to_id: str
    reason: str
    requester_id: int
    status: Literal["pending", "approved", "rejected"]
    from_creator: Dict
    to_creator: Dict
    requester: Dict


def get_unapproved_links_with_artists() -> list[TDUnapprovedLink]:
    query = """
    SELECT
        unapproved_link_requests.* ,
        row_to_json(
            from_creator.*
        ) AS from_creator,
        row_to_json(
            to_creator.*
        ) AS to_creator,
        row_to_json(
            requester.*
        ) AS requester
    FROM
        unapproved_link_requests
        JOIN
        lookup AS from_creator
        ON
            from_service = from_creator.service
            AND
            from_id = from_creator.id
        JOIN
        lookup AS to_creator
        ON
            to_service = to_creator.service
            AND
            to_id = to_creator.id
        JOIN
        account AS requester
        ON
            requester_id = requester.id
    WHERE
        status = 'pending'
    ORDER BY
        unapproved_link_requests.id ASC
    """
    cur = get_cursor()
    cur.execute(query)
    return cur.fetchall()


def reject_unapproved_link_request(request_id: int):
    query = """
    UPDATE unapproved_link_requests
    SET status = 'rejected'
    WHERE id = %s
    """
    cur = get_cursor()
    cur.execute(query, (request_id,))


def approve_unapproved_link_request(request_id: int):
    # move from the not same to the diffrent one
    query1 = """
    UPDATE unapproved_link_requests
    SET status = 'approved'
    WHERE id = %s
     OR (from_service, to_service, from_id, to_id) in (SELECT from_service, to_service, from_id, to_id from unapproved_link_requests WHERE id = %s)
     OR (from_service, to_service, from_id, to_id) in (SELECT to_service, from_service, to_id, from_id from unapproved_link_requests WHERE id = %s)
    Returning from_service, to_service, from_id, to_id
    """
    query2 = """
    WITH next_relation_table AS (
        SELECT nextval('lookup_relation_id_seq') as next_id
    )
    UPDATE lookup creators
    SET relation_id = coalesce(from_creator.relation_id, to_creator.relation_id, (SELECT next_id FROM next_relation_table))
    FROM lookup from_creator, lookup to_creator, unapproved_link_requests link
    WHERE
        (
            (creators.id = link.from_id and creators.service = link.from_service)
            OR
            (creators.id = link.to_id and creators.service = link.to_service)
        ) AND
        link.id = %s AND
        from_creator.service = link.from_service AND
        from_creator.id = link.from_id AND
        to_creator.service = link.to_service AND
        to_creator.id = link.to_id
    """
    cursor = get_cursor()
    cursor.execute("BEGIN;")
    cursor.execute(query1, (request_id, request_id, request_id))
    update_result = cursor.fetchone()
    if not update_result:
        raise Exception("Failed to change status of request")
    cursor.execute(query2, (request_id,))
    cursor.execute("COMMIT;")
    redis = get_conn()
    redis.delete(f"linked_accounts:{update_result['from_service']}:{update_result['from_id']}")
    redis.delete(f"linked_accounts:{update_result['to_service']}:{update_result['to_id']}")
    redis.delete(f"artist:{update_result['from_service']}:{update_result['from_id']}")
    redis.delete(f"artist:{update_result['to_service']}:{update_result['to_id']}")
    clear_web_cache_for_creator_links(update_result["from_service"], update_result["from_id"])
    clear_web_cache_for_creator_links(update_result["to_service"], update_result["to_id"])


def delete_creator_link(service: str, creator_id: str):
    query = """
    UPDATE lookup
    SET relation_id = NULL
    WHERE service = %s AND id = %s
    """
    cur = get_cursor()
    cur.execute(query, (service, creator_id))


def get_linked_creators(service: str, creator_id: str):
    key = f"linked_accounts:{service}:{creator_id}"
    query = """
    SELECT * FROM lookup l1
    JOIN lookup l2 ON l1.relation_id = l2.relation_id
    WHERE l1.service = %s AND l1.id = %s
    AND l1.ctid != l2.ctid
    ORDER BY l1.id, l1.service, l2.id, l2.service
    """
    return cached_query(query, key, (service, creator_id), reload=True)


def nload_query_artists(artist_ids: list[tuple[str, str]], dict_result=False):
    # todo remake this like the nload for posts
    if not artist_ids:
        return []
    keys = [f"artist:{service}:{artist_id}" for service, artist_id in artist_ids]
    redis = get_conn()
    cached = (deserialize_artist(artist) for artist in (artist_str for artist_str in redis.mget(keys) if artist_str))
    in_cache = {(artist["service"], artist["id"]): artist for artist in cached if artist}
    for service, artist_id in artist_ids:
        if in_cache.get((service, artist_id)) is None:
            in_cache[(service, artist_id)] = get_artist(service, artist_id) or {}
    if dict_result:
        return in_cache

    artists = []
    for service, artist_id in artist_ids:
        artists.append(in_cache.get((service, artist_id)))
    return artists
