import asyncio
import math
import re

import requests
from imdb import Cinemagoer, IMDbDataAccessError

from db.config import settings
from db.models import Streams, TVStreams
from db.schemas import Stream, UserData
from streaming_providers.alldebrid.utils import (
    update_ad_cache_status,
    fetch_downloaded_info_hashes_from_ad,
)
from streaming_providers.debridlink.utils import (
    update_dl_cache_status,
    fetch_downloaded_info_hashes_from_dl,
)
from streaming_providers.offcloud.utils import (
    update_oc_cache_status,
    fetch_downloaded_info_hashes_from_oc,
)
from streaming_providers.pikpak.utils import (
    update_pikpak_cache_status,
    fetch_downloaded_info_hashes_from_pikpak,
)
from streaming_providers.realdebrid.utils import (
    update_rd_cache_status,
    fetch_downloaded_info_hashes_from_rd,
)
from streaming_providers.seedr.utils import (
    update_seedr_cache_status,
    fetch_downloaded_info_hashes_from_seedr,
)
from streaming_providers.torbox.utils import (
    update_torbox_cache_status,
    fetch_downloaded_info_hashes_from_torbox,
)

ia = Cinemagoer()


async def filter_and_sort_streams(
    streams: list[Streams], user_data: UserData
) -> list[Streams]:
    # Filter streams by selected catalogs and resolutions
    filtered_streams = [
        stream
        for stream in streams
        if any(catalog in stream.catalog for catalog in user_data.selected_catalogs)
        and stream.resolution in user_data.selected_resolutions
    ]

    if not filtered_streams:
        return []

    # Define provider-specific cache update functions
    cache_update_functions = {
        "alldebrid": update_ad_cache_status,
        "debridlink": update_dl_cache_status,
        "offcloud": update_oc_cache_status,
        "pikpak": update_pikpak_cache_status,
        "realdebrid": update_rd_cache_status,
        "seedr": update_seedr_cache_status,
        "torbox": update_torbox_cache_status,
    }

    # Update cache status based on provider
    if user_data.streaming_provider:
        if cache_update_function := cache_update_functions.get(
            user_data.streaming_provider.service
        ):
            if asyncio.iscoroutinefunction(cache_update_function):
                await cache_update_function(streams, user_data)
            else:
                await asyncio.to_thread(cache_update_function, streams, user_data)

    # Sort streams by cache status, creation date, and size
    return sorted(
        filtered_streams, key=lambda x: (x.cached, x.size, x.created_at), reverse=True
    )


async def parse_stream_data(
    streams: list[Streams],
    user_data: UserData,
    secret_str: str,
    season: int = None,
    episode: int = None,
) -> list[Stream]:
    stream_list = []

    streams = await filter_and_sort_streams(streams, user_data)

    for stream_data in streams:
        quality_detail = " - ".join(
            filter(
                None,
                [
                    stream_data.quality,
                    stream_data.resolution,
                    stream_data.codec,
                    stream_data.audio,
                ],
            )
        )

        episode_data = stream_data.get_episode(season, episode)

        if user_data.streaming_provider:
            streaming_provider = user_data.streaming_provider.service.title()
            if stream_data.cached:
                streaming_provider += " ⚡️"
            else:
                streaming_provider += " ⏳"
        else:
            streaming_provider = "Torrent ⏳"

        description_parts = [
            quality_detail,
            convert_bytes_to_readable(
                episode_data.size if episode_data else stream_data.size
            ),
            " + ".join(stream_data.languages),
            stream_data.source,
        ]
        description = ", ".join(filter(lambda x: bool(x), description_parts))

        stream_details = {
            "name": f"MediaFusion {streaming_provider}",
            "description": description,
            "infoHash": stream_data.id,
            "fileIdx": episode_data.file_index
            if episode_data
            else stream_data.file_index,
            "behaviorHints": {"bingeGroup": f"MediaFusion-{quality_detail}"},
        }

        if user_data.streaming_provider:
            base_proxy_url = f"{settings.host_url}/{secret_str}/streaming_provider?info_hash={stream_data.id}"
            if episode_data:
                base_proxy_url += f"&season={season}&episode={episode}"
            stream_details["url"] = base_proxy_url
            stream_details.pop("infoHash")
            stream_details.pop("fileIdx")
            stream_details["behaviorHints"]["notWebReady"] = True

        stream_list.append(Stream(**stream_details))

    return stream_list


def convert_bytes_to_readable(size_bytes: int) -> str:
    """
    Convert a size in bytes into a more human-readable format.
    """
    if size_bytes == 0:
        return "0B"
    size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
    i = int(math.floor(math.log(size_bytes, 1024)))
    p = math.pow(1024, i)
    s = round(size_bytes / p, 2)
    return f"{s} {size_name[i]}"


def get_catalogs(catalog: str, languages: list[str]) -> list[str]:
    base_catalogs = ["hdrip", "tcrip", "dubbed", "series"]
    base_catalog = catalog.split("_")[1]

    if base_catalog not in base_catalogs:
        return [catalog]

    # Generate the catalog for each supported language
    return [f"{lang.lower()}_{base_catalog}" for lang in languages]


def search_imdb(title: str, year: int, retry: int = 5) -> dict:
    try:
        result = ia.search_movie(f"{title} {year}")
    except IMDbDataAccessError:
        return search_imdb(title, year, retry - 1) if retry > 0 else {}
    for movie in result:
        if movie.get("year") == year and movie.get("title").lower() in title.lower():
            imdb_id = f"tt{movie.movieID}"
            poster = f"https://live.metahub.space/poster/small/{imdb_id}/img"
            if requests.get(poster).status_code == 200:
                return {
                    "imdb_id": imdb_id,
                    "poster": poster.replace("small", "medium"),
                    "background": f"https://live.metahub.space/background/medium/{imdb_id}/img",
                }
            poster = movie.get("full-size cover url")
            return {
                "imdb_id": imdb_id,
                "poster": poster,
                "background": poster,
            }
    return {}


def parse_tv_stream_data(stream: list[TVStreams]) -> list[Stream]:
    stream_list = []
    for stream in stream:
        if stream.behaviorHints.get("is_redirect", False):
            response = requests.get(
                stream.url,
                headers=stream.behaviorHints["proxyHeaders"]["request"],
                allow_redirects=False,
            )
            if response.status_code == 302:
                stream.url = response.headers["Location"]
        stream_list.append(
            Stream(
                name="MediaFusion",
                description=f"{stream.name}, {stream.source}",
                url=stream.url,
                ytId=stream.ytId,
                behaviorHints=stream.behaviorHints,
            )
        )

    return stream_list


async def fetch_downloaded_info_hashes(user_data: UserData) -> list[str]:
    fetch_downloaded_info_hashes_functions = {
        "alldebrid": fetch_downloaded_info_hashes_from_ad,
        "debridlink": fetch_downloaded_info_hashes_from_dl,
        "offcloud": fetch_downloaded_info_hashes_from_oc,
        "pikpak": fetch_downloaded_info_hashes_from_pikpak,
        "realdebrid": fetch_downloaded_info_hashes_from_rd,
        "seedr": fetch_downloaded_info_hashes_from_seedr,
        "torbox": fetch_downloaded_info_hashes_from_torbox,
    }

    if fetch_downloaded_info_hashes_function := fetch_downloaded_info_hashes_functions.get(
        user_data.streaming_provider.service
    ):
        if asyncio.iscoroutinefunction(fetch_downloaded_info_hashes_function):
            downloaded_info_hashes = await fetch_downloaded_info_hashes_function(
                user_data
            )
        else:
            downloaded_info_hashes = await asyncio.to_thread(
                fetch_downloaded_info_hashes_function, user_data
            )

        return downloaded_info_hashes

    return []
