import asyncio
from os import path
from typing import Optional, List

import aiohttp

from db.models import TorrentStreams
from streaming_providers.debrid_client import DebridClient
from streaming_providers.exceptions import ProviderException
from streaming_providers.parser import (
    select_file_index_from_torrent,
)


class OffCloud(DebridClient):
    BASE_URL = "https://offcloud.com"

    async def initialize_headers(self):
        pass

    async def disable_access_token(self):
        pass

    async def _handle_service_specific_errors(self, error_data: dict, status_code: int):
        if status_code == 403:
            raise ProviderException("Invalid OffCloud API key", "invalid_token.mp4")
        if status_code == 429:
            raise ProviderException(
                "OffCloud rate limit exceeded", "too_many_requests.mp4"
            )

    async def _make_request(
        self,
        method: str,
        url: str,
        params: Optional[dict] = None,
        **kwargs,
    ) -> dict | list:
        params = params or {}
        params["key"] = self.token
        full_url = self.BASE_URL + url
        return await super()._make_request(
            method=method, url=full_url, params=params, **kwargs
        )

    async def add_magnet_link(self, magnet_link: str) -> dict:
        response_data = await self._make_request(
            "POST", "/api/cloud", data={"url": magnet_link}
        )

        if "requestId" not in response_data:
            if "not_available" in response_data:
                raise ProviderException(
                    "Need premium OffCloud account to add this torrent",
                    "need_premium.mp4",
                )
            raise ProviderException(
                f"Failed to add magnet link to OffCloud {response_data}",
                "transfer_error.mp4",
            )
        return response_data

    async def add_torrent_file(
        self, torrent_file: bytes, torrent_name: Optional[str]
    ) -> dict:
        data = aiohttp.FormData()
        data.add_field(
            "file",
            torrent_file,
            filename=torrent_name,
            content_type="application/x-bittorrent",
        )
        response_data = await self._make_request("POST", "/torrent/upload", data=data)
        if response_data.get("success") is False:
            raise ProviderException(
                f"Failed to add torrent file to OffCloud {response_data}",
                "transfer_error.mp4",
            )
        return await self.add_magnet_link(response_data["url"])

    async def get_user_torrent_list(self) -> List[dict]:
        return await self._make_request("GET", "/api/cloud/history")

    async def get_torrent_info(self, request_id: str) -> dict:
        response = await self._make_request(
            "POST", "/api/cloud/status", data={"requestIds": [request_id]}
        )
        return response.get("requests", [{}])[0]

    async def get_torrent_instant_availability(self, magnet_links: List[str]) -> dict:
        response = await self._make_request(
            "POST", "/api/cache", data={"hashes": magnet_links}
        )
        return response.get("cachedItems", {})

    async def get_available_torrent(self, info_hash: str) -> Optional[dict]:
        available_torrents = await self.get_user_torrent_list()
        return next(
            (
                torrent
                for torrent in available_torrents
                if info_hash.casefold() in torrent.get("originalLink", "").casefold()
            ),
            None,
        )

    async def explore_folder_links(self, request_id: str) -> List[str]:
        return await self._make_request("GET", f"/api/cloud/explore/{request_id}")

    async def update_file_sizes(self, files_data: list[dict]):
        """
        Update file sizes for a list of files by making HEAD requests.

        Args:
            files_data (list[dict]): List of file data dictionaries containing 'link' keys

        Note:
            This method modifies the input files_data list in-place, adding 'size' keys
            where the HEAD request was successful.
        """

        async def get_file_size(file_data: dict) -> tuple[dict, Optional[int]]:
            """Helper function to get file size for a single file."""
            try:
                async with self.session.head(
                    file_data["link"],
                    timeout=aiohttp.ClientTimeout(total=5),
                    allow_redirects=True,
                ) as response:
                    if response.status == 200:
                        return file_data, int(response.headers.get("Content-Length", 0))
            except (aiohttp.ClientError, asyncio.TimeoutError):
                pass
            return file_data, 0

        # Gather all HEAD requests with proper concurrency
        tasks = [get_file_size(file_data) for file_data in files_data]
        results = await asyncio.gather(*tasks, return_exceptions=False)

        # Update file sizes in the original data
        for file_data, size in results:
            file_data["size"] = size

    async def create_download_link(
        self,
        request_id: str,
        torrent_info: dict,
        stream: TorrentStreams,
        filename: Optional[str],
        season: Optional[int],
        episode: Optional[int],
    ) -> str:
        if not torrent_info["isDirectory"]:
            return f"https://{torrent_info['server']}.offcloud.com/cloud/download/{request_id}/{torrent_info['fileName']}"

        links = await self.explore_folder_links(request_id)
        files_data = [{"name": path.basename(link), "link": link} for link in links]

        file_index = await select_file_index_from_torrent(
            torrent_info={"files": files_data},
            torrent_stream=stream,
            filename=filename,
            season=season,
            episode=episode,
            file_size_callback=self.update_file_sizes,
        )

        selected_file_url = links[file_index]
        return selected_file_url

    async def delete_torrent(self, request_id: str) -> dict:
        return await self._make_request("GET", f"/cloud/remove/{request_id}")
