import copy
import json
from typing import Union
from urllib.parse import urlencode

from py_yt.core.constants import (
    requestPayload,
    searchKey,
    ResultMode,
    videoElementKey,
    channelElementKey,
    playlistElementKey,
    shelfElementKey,
    richItemKey,
)
from py_yt.core.requests import RequestCore
from py_yt.handlers.componenthandler import ComponentHandler
from py_yt.handlers.requesthandler import RequestHandler


class SearchCore(RequestCore, RequestHandler, ComponentHandler):
    response = None
    responseSource = None
    resultComponents = []

    def __init__(
        self,
        query: str,
        limit: int,
        language: str,
        region: str,
        searchPreferences: str,
        timeout: int,
    ):
        super().__init__()
        self.query = query
        self.limit = limit
        self.language = language
        self.region = region
        self.searchPreferences = searchPreferences
        self.timeout = timeout
        self.continuationKey = None

    def sync_create(self):
        self._makeRequest()
        self._parseSource()

    def _getRequestBody(self):
        """Fixes #47"""
        requestBody = copy.deepcopy(requestPayload)
        requestBody["query"] = self.query
        requestBody["client"] = {
            "hl": self.language,
            "gl": self.region,
        }
        if self.searchPreferences:
            requestBody["params"] = self.searchPreferences
        if self.continuationKey:
            requestBody["continuation"] = self.continuationKey
        self.url = (
            "https://www.youtube.com/youtubei/v1/search"
            + "?"
            + urlencode(
                {
                    "key": searchKey,
                }
            )
        )
        self.data = requestBody

    def _makeRequest(self) -> None:
        self._getRequestBody()
        request = self.syncPostRequest()
        try:
            self.response = request.text
        except:
            raise Exception("ERROR: Could not make request.")

    async def _makeAsyncRequest(self) -> None:
        self._getRequestBody()
        request = await self.asyncPostRequest()
        try:
            self.response = request.text
        except:
            raise Exception("ERROR: Could not make request.")

    def result(self, mode: int = ResultMode.dict) -> Union[str, dict]:
        """Returns the search result.

        Args:
            mode (int, optional): Sets the type of result. Defaults to ResultMode.dict.

        Returns:
            Union[str, dict]: Returns JSON or dictionary.
        """
        if mode == ResultMode.json:
            return json.dumps({"result": self.resultComponents}, indent=4)
        elif mode == ResultMode.dict:
            return {"result": self.resultComponents}

    def _next(self) -> bool:
        """Gets the subsequent search result. Call result

        Args:
            mode (int, optional): Sets the type of result. Defaults to ResultMode.dict.

        Returns:
            Union[str, dict]: Returns True if getting more results was successful.
        """
        if self.continuationKey:
            self.response = None
            self.responseSource = None
            self.resultComponents = []
            self._makeRequest()
            self._parseSource()
            self._getComponents(*self.searchMode)
            return True
        else:
            return False

    async def _nextAsync(self) -> dict:
        self.response = None
        self.responseSource = None
        self.resultComponents = []
        await self._makeAsyncRequest()
        self._parseSource()
        self._getComponents(*self.searchMode)
        return {
            "result": self.resultComponents,
        }

    def _getComponents(
        self, findVideos: bool, findChannels: bool, findPlaylists: bool
    ) -> None:
        self.resultComponents = []
        for element in self.responseSource:
            if videoElementKey in element.keys() and findVideos:
                self.resultComponents.append(self._getVideoComponent(element))
            if channelElementKey in element.keys() and findChannels:
                self.resultComponents.append(self._getChannelComponent(element))
            if playlistElementKey in element.keys() and findPlaylists:
                self.resultComponents.append(self._getPlaylistComponent(element))
            if shelfElementKey in element.keys() and findVideos:
                for shelfElement in self._getShelfComponent(element)["elements"]:
                    self.resultComponents.append(
                        self._getVideoComponent(
                            shelfElement,
                            shelfTitle=self._getShelfComponent(element)["title"],
                        )
                    )
            if richItemKey in element.keys() and findVideos:
                richItemElement = self._getValue(element, [richItemKey, "content"])
                """ Initial fallback handling for VideosSearch """
                if videoElementKey in richItemElement.keys():
                    videoComponent = self._getVideoComponent(richItemElement)
                    self.resultComponents.append(videoComponent)
            if len(self.resultComponents) >= self.limit:
                break
