"""
    Main Api implementation.
"""
import datetime
from itertools import cycle
from typing import Optional, List, Union

import pytz
from pytz import timezone
import requests
import datetime
import time

from requests.auth import HTTPBasicAuth
from requests.models import Response
from requests_oauthlib.oauth2_session import OAuth2Session

from ytvs.error import ErrorCode, ErrorMessage
from ytvs.exception import PyYouTubeException
from ytvs.models import (
    AccessToken,
    UserProfile,
    ActivityListResponse,
    CaptionListResponse,
    ChannelListResponse,
    ChannelSectionResponse,
    PlaylistListResponse,
    PlaylistItemListResponse,
    VideoListResponse,
    CommentThreadListResponse,
    CommentListResponse,
    VideoCategoryListResponse,
    SearchListResponse,
    SubscriptionListResponse,
    I18nRegionListResponse,
    I18nLanguageListResponse,
    MemberListResponse,
    MembershipsLevelListResponse,
    VideoAbuseReportReasonListResponse,
)
from pyyoutube.utils.params_checker import enf_comma_separated, enf_parts


class OfficialApi(object):
    """
    Example usage:
        To create an instance of pyyoutube.Api class:

            >>> import pyyoutube
            >>> api = pyyoutube.OfficialApi(api_key="your api key")

        To get one channel info:

            >>> res = api.get_channel_info(channel_id="UC_x5XG1OV2P6uZZ5FSM9Ttw")
            >>> print(res.items[0])

        Now this api provide methods as follows:
            >>> api.get_authorization_url()
            >>> api.generate_access_token()
            >>> api.refresh_token()
            >>> api.get_channel_info()
            >>> api.get_playlist_by_id()
            >>> api.get_playlists()
            >>> api.get_playlist_item_by_id()
            >>> api.get_playlist_items()
            >>> api.get_video_by_id()
            >>> api.get_videos_by_chart()
            >>> api.get_videos_by_myrating()
            >>> api.get_comment_thread_by_id()
            >>> api.get_comment_threads()
            >>> api.get_comment_by_id()
            >>> api.get_comments()
            >>> api.get_video_categories()
            >>> api.get_subscription_by_id()
            >>> api.get_subscription_by_channel()
            >>> api.get_subscription_by_me()
            >>> api.get_activities_by_channel()
            >>> api.get_activities_by_me()
            >>> api.get_captions_by_video()
            >>> api.get_channel_sections_by_id()
            >>> api.get_channel_sections_by_channel()
            >>> api.get_i18n_regions()
            >>> api.get_i18n_languages()
            >>> api.get_video_abuse_report_reason()
            >>> api.search()
            >>> api.search_by_keywords()
            >>> api.search_by_developer()
            >>> api.search_by_mine()
            >>> api.search_by_related_video()
    """

    BASE_URL = "https://www.googleapis.com/youtube/v3/"
    AUTHORIZATION_URL = "https://accounts.google.com/o/oauth2/v2/auth"
    EXCHANGE_ACCESS_TOKEN_URL = "https://oauth2.googleapis.com/token"
    USER_INFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo"

    DEFAULT_REDIRECT_URI = "https://localhost/"

    DEFAULT_SCOPE = [
        "https://www.googleapis.com/auth/youtube",
        "https://www.googleapis.com/auth/userinfo.profile",
    ]

    DEFAULT_STATE = "PyYouTube"
    DEFAULT_TIMEOUT = 10
    DEFAULT_QUOTA = 10000  # this quota reset at 00:00:00(GMT-7) every day.

    def __init__(
        self,
        client_id: Optional[str] = None,
        client_secret: Optional[str] = None,
        #api_key: Optional[str] = None,
        api_keys: Optional[List[str]] = None,
        quota_reset_time='00:00',  # 쿼터 리셋 시간 (태평양 표준시 기준 자정)
        cool_down_period=3600,  # API 키 만료 후 대기 시간 (초 단위)
        access_token: Optional[str] = None,
        timeout: Optional[int] = None,
        proxies: Optional[dict] = None,
    ) -> None:
        """
        This Api provide two method to work. Use api key or use access token.

        Args:
            client_id(str, optional):
                Your google app's ID.
            client_secret (str, optional):
                Your google app's secret.
            api_key(str, optional):
                The api key which you create from google api console.
            access_token(str, optional):
                If you not provide api key, you can do authorization to get an access token.
                If all api key and access token provided. Use access token first.
            timeout(int, optional):
                The request timeout.
            proxies(dict, optional):
                If you want use proxy, need point this param.
                param style like requests lib style.
                Refer https://2.python-requests.org//en/latest/user/advanced/#proxies

        Returns:
            YouTube Api instance.
        """
        self._client_id = client_id
        self._client_secret = client_secret
        self._access_token = access_token
        self._refresh_token = None  # This keep current user's refresh token.
        self._timeout = timeout
        self.session = requests.Session()
        self.proxies = proxies
        self.api_keys = api_keys
        self.current_key_index = 0  # 현재 사용 중인 API 키 인덱스
        self.api_key_usage = {key: {"expired":False, "reset_time": None} for key in api_keys}  # API 키 사용량 기록
        self.quota_reset_time = quota_reset_time
        self.cool_down_period = cool_down_period
        self.last_quota_reset = None
        self.next_key_switch_time = None  # 다음 API 키로 전환할 시간

        if not (
            (self._client_id and self._client_secret)
            or self.api_keys
            or self._access_token
        ):
            raise PyYouTubeException(
                ErrorMessage(
                    status_code=ErrorCode.MISSING_PARAMS,
                    message="Must specify either client key info or api key.",
                )
            )

        if self._timeout is None:
            self._timeout = self.DEFAULT_TIMEOUT

    def _update_key_usage(self):
        """태평양 표준시 자정에 모든 키의 만료 상태를 초기화합니다."""
        pst_now = datetime.datetime.now(pytz.timezone("Asia/Seoul"))
        for key, usage in self.api_key_usage.items():
            if usage["reset_time"] is None or pst_now >= usage["reset_time"]:
                usage["expired"] = False
                next_day = pst_now + datetime.timedelta(days=1)
                reset_time = datetime.datetime(year=next_day.year, month=next_day.month, day=next_day.day, hour=0, minute=0, second=0, tzinfo=pytz.timezone("America/Los_Angeles"))
                usage["reset_time"] = reset_time


    def _check_if_all_key_is_expired(self):
        is_all_expired = True
        for key, value in self.api_key_usage:
            is_expired = value['expired']
            if is_expired is False:
                all_expired = False
                break
        return is_all_expired




    def _get_next_key(self):
        """다음 사용할 API 키를 반환합니다. 모든 키가 만료되었다면 대기합니다."""
        original_index = self.current_key_index
        while True:
            current_key = self.api_keys[self.current_key_index]
            if not self.api_key_usage[current_key]["expired"]:
                return current_key
            self.current_key_index = (self.current_key_index + 1) % len(self.current_key_index)
            if self.current_key_index == original_index:  # 모든 키가 만료된 경우
                print("All API keys are expired. Waiting...")
                time.sleep(self.cool_down_period)
                self._update_key_usage()  # 만료 상태 업데이트 시도

    def _get_oauth_session(
        self,
        redirect_uri: Optional[str] = None,
        scope: Optional[List[str]] = None,
        **kwargs,
    ) -> OAuth2Session:
        """
        Build a request session for OAuth.

        Args:
            redirect_uri(str, optional)
                Determines how Google's authorization server sends a response to your app.
                If not provide will use default https://localhost/
            scope (list, optional)
                The scope you want give permission.
                If you not provide, will use default scope.
            kwargs(dict, optional)
                Some other params you want provide.

        Returns:
            OAuth2 Session
        """
        if redirect_uri is None:
            redirect_uri = self.DEFAULT_REDIRECT_URI

        if scope is None:
            scope = self.DEFAULT_SCOPE

        return OAuth2Session(
            client_id=self._client_id,
            scope=scope,
            redirect_uri=redirect_uri,
            state=self.DEFAULT_STATE,
            **kwargs,
        )

    def get_authorization_url(
        self,
        redirect_uri: Optional[str] = None,
        scope: Optional[List[str]] = None,
        **kwargs,
    ) -> (str, str):
        """
        Build authorization url to do authorize.

        Args:
            redirect_uri(str, optional)
                Determines how Google's authorization server sends a response to your app.
                If not provide will use default https://localhost/
            scope (list, optional)
                The scope you want give permission.
                If you not provide, will use default scope.
            kwargs(dict, optional)
                Some other params you want provide.

        Returns:
            The uri you can open on browser to do authorize.
        """
        oauth_session = self._get_oauth_session(
            redirect_uri=redirect_uri,
            scope=scope,
            **kwargs,
        )
        authorization_url, state = oauth_session.authorization_url(
            self.AUTHORIZATION_URL,
            access_type="offline",
            prompt="select_account",
            **kwargs,
        )

        return authorization_url, state

    def generate_access_token(
        self,
        authorization_response: str,
        redirect_uri: Optional[str] = None,
        scope: Optional[List[str]] = None,
        return_json: bool = False,
        **kwargs,
    ) -> Union[dict, AccessToken]:
        """
        Use the google auth response to get access token

        Args:
            authorization_response (str)
                The response url which google redirect.
            redirect_uri(str, optional)
                Determines how Google's authorization server sends a response to your app.
                If not provide will use default https://localhost/
            scope (list, optional)
                The scope you want give permission.
                If you not provide, will use default scope.
            return_json(bool, optional)
                The return data type. If you set True JSON data will be returned.
                False will return pyyoutube.AccessToken
            kwargs(dict, optional)
                Some other params you want provide.
        Return:
            Retrieved access token's info, pyyoutube.AccessToken instance.
        """

        oauth_session = self._get_oauth_session(
            redirect_uri=redirect_uri,
            scope=scope,
            **kwargs,
        )
        token = oauth_session.fetch_token(
            self.EXCHANGE_ACCESS_TOKEN_URL,
            client_secret=self._client_secret,
            authorization_response=authorization_response,
            proxies=self.proxies,
        )
        self._access_token = oauth_session.access_token
        self._refresh_token = oauth_session.token["refresh_token"]
        if return_json:
            return token
        else:
            return AccessToken.from_dict(token)

    def refresh_token(
        self, refresh_token: Optional[str] = None, return_json: bool = False
    ) -> Union[dict, AccessToken]:
        """
        Refresh token by api return refresh token.

        Args:
            refresh_token (str)
                The refresh token which the api returns.
            return_json (bool, optional):
                If True JSON data will be returned, instead of pyyoutube.AccessToken
        Return:
            Retrieved new access token's info,  pyyoutube.AccessToken instance.
        """

        refresh_token = refresh_token if refresh_token else self._refresh_token

        if refresh_token is None:
            raise PyYouTubeException(
                ErrorMessage(
                    status_code=ErrorCode.MISSING_PARAMS,
                    message=f"Must provide the refresh token or api has been authorized.",
                )
            )

        oauth_session = OAuth2Session(client_id=self._client_id)
        auth = HTTPBasicAuth(self._client_id, self._client_secret)
        new_token = oauth_session.refresh_token(
            self.EXCHANGE_ACCESS_TOKEN_URL,
            refresh_token=refresh_token,
            auth=auth,
        )
        self._access_token = oauth_session.access_token
        if return_json:
            return new_token
        else:
            return AccessToken.from_dict(new_token)

    @staticmethod
    def _parse_response(response: Response) -> dict:
        """
        Parse response data and check whether errors exists.

        Args:
            response (Response)
                The response which the request return.
        Return:
             response's data
        """
        data = response.json()
        if "error" in data:
            raise PyYouTubeException(response)
        return data

    @staticmethod
    def _parse_data(data: Optional[dict]) -> Union[dict, list]:
        """
        Parse resp data.

        Args:
            data (dict)
                The response data by response.json()
        Return:
             response's items
        """
        items = data["items"]
        return items

    def _request(
        self, resource, method=None, args=None, post_args=None, enforce_auth=True
    ) -> Response:
        """
        Main request sender.

        Args:
            resource(str)
                Resource field is which type data you want to retrieve.
                Such as channels，videos and so on.
            method(str, optional)
                The method this request to send request.
                Default is 'GET'
            args(dict, optional)
                The url params for this request.
            post_args(dict, optional)
                The Post params for this request.
            enforce_auth(bool, optional)
                Whether use google credentials
        Returns:
            response
        """
        if method is None:
            method = "GET"

        if args is None:
            args = dict()

        if post_args is not None:
            method = "POST"

        key = None
        access_token = None

        self._update_key_usage()  # 필요한 경우 쿼터 초기화
        api_key = self.api_keys[self.current_key_index]

        if api_key is not None:
            key = "key"
            access_token = api_key
        if self._access_token is not None:
            key = "access_token"
            access_token = self._access_token
        if access_token is None and enforce_auth:
            raise PyYouTubeException(
                ErrorMessage(
                    status_code=ErrorCode.MISSING_PARAMS,
                    message="You must provide your credentials.",
                )
            )

        if enforce_auth:
            if method == "POST" and key not in post_args:
                post_args[key] = access_token
            elif method == "GET" and key not in args:
                args[key] = access_token

        try:
            response = self.session.request(
                method=method,
                url=self.BASE_URL + resource,
                timeout=self._timeout,
                params=args,
                data=post_args,
                proxies=self.proxies,
            )
        except requests.HTTPError as e:
            if e.response.status_code == 403:  # 쿼터 초과 에러 처리
                # 현재 API 키의 쿼터가 초과되었다고 표시하고 다음 키로 전환하는 로직 추가
                self.api_key_usage[api_key] = {
                    'expired': True,
                    'reset_time': datetime.datetime.now()
                }
                is_all_key_expired = self._check_if_all_key_is_expired()
                if is_all_key_expired is True:
                    raise Exception("All key is expired ")
                else:
                    return self._request(resource, method, args, post_args, enforce_auth)
            else:
                raise PyYouTubeException(
                    ErrorMessage(status_code=ErrorCode.HTTP_ERROR, message=e.args[0])
                )
        else:
            return response

    def get_profile(
        self, access_token: Optional[str] = None, return_json: Optional[bool] = False
    ) -> Union[dict, UserProfile]:
        """
        Get token user info.

        Args:
            access_token(str, optional)
                user access token. If not provide, use api instance access token
            return_json(bool, optional)
                The return data type. If you set True JSON data will be returned.
                False will return pyyoutube.UserProfile

        Returns:
            The data for you given access token's user info.
        """
        if access_token is None:
            access_token = self._access_token
        if access_token is None:
            raise PyYouTubeException(
                ErrorMessage(
                    status_code=ErrorCode.MISSING_PARAMS,
                    message=f"Must provide the access token or api has been authorized.",
                )
            )
        try:
            response = self.session.get(
                self.USER_INFO_URL,
                params={"access_token": access_token},
                timeout=self._timeout,
                proxies=self.proxies,
            )
        except requests.HTTPError as e:
            raise PyYouTubeException(
                ErrorMessage(status_code=ErrorCode.HTTP_ERROR, message=e.args[0])
            )
        data = self._parse_response(response)
        if return_json:
            return data
        else:
            return UserProfile.from_dict(data)

    def paged_by_page_token(
        self,
        resource: str,
        args: dict,
        count: Optional[int] = None,
    ):
        """
        Response paged by response's page token. If not provide response token

        Args:
            resource (str):
                The resource string need to retrieve data.
            args (dict)
                The args for api.
            count (int, optional):
                The count for result items you want to get.
                If provide this with None, will retrieve all items.
                Note:
                    The all items maybe too much. Notice your app's cost.
        Returns:
            Data api origin response.
        """
        res_data: Optional[dict] = None
        current_items: List[dict] = []
        page_token: Optional[str] = None
        now_items_count: int = 0

        while True:
            if page_token is not None:
                args["pageToken"] = page_token

            resp = self._request(resource=resource, method="GET", args=args)
            data = self._parse_response(resp)  # origin response
            # set page token
            page_token = data.get("nextPageToken")
            prev_page_token = data.get("prevPageToken")

            # parse results.
            items = self._parse_data(data)
            current_items.extend(items)
            now_items_count += len(items)
            if res_data is None:
                res_data = data
            # first check the count if satisfies.
            if count is not None:
                if now_items_count >= count:
                    current_items = current_items[:count]
                    break
            # if have no page token, mean no more data.
            if page_token is None:
                break
        res_data["items"] = current_items

        # use last request page token
        res_data["nextPageToken"] = page_token
        res_data["prevPageToken"] = prev_page_token
        return res_data

    def get_activities_by_channel(
        self,
        *,
        channel_id: str,
        parts: Optional[Union[str, list, tuple, set]] = None,
        before: Optional[str] = None,
        after: Optional[str] = None,
        region_code: Optional[str] = None,
        count: Optional[int] = 20,
        limit: int = 20,
        page_token: Optional[str] = None,
        return_json: bool = False,
    ):
        """
        Retrieve given channel's activities data.

        Args:
            channel_id (str):
                The id for channel which you want to get activities data.
            parts ((str,list,tuple,set) optional):
                The resource parts for activities you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            before (str, optional):
                Set this will only return the activities occurred before this timestamp.
                This need specified in ISO 8601 (YYYY-MM-DDThh:mm:ss.sZ) format.
            after (str, optional):
                Set this will only return the activities occurred after this timestamp.
                This need specified in ISO 8601 (YYYY-MM-DDThh:mm:ss.sZ) format.
            region_code (str, optional):
                Set this will only return the activities for the specified country.
                This need specified with an ISO 3166-1 alpha-2 country code.
            count (int, optional):
                The count will retrieve activities data.
                Default is 20.
                If provide this with None, will retrieve all activities.
            limit (int, optional):
                The maximum number of items each request retrieve.
                For activities, this should not be more than 50.
                Default is 20.
            page_token (str, optional):
                The token of the page of activities result to retrieve.
                You can use this retrieve point result page directly.
                And you should know about the page result set for YouTube.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.ActivityListResponse instance.

        Returns:
            ActivityListResponse or original data.
        """

        if count is None:
            limit = 50  # for activities the max limit for per request is 50
        else:
            limit = min(count, limit)

        args = {
            "channelId": channel_id,
            "part": enf_parts(resource="activities", value=parts),
            "maxResults": limit,
        }

        if before:
            args["publishedBefore"] = before
        if after:
            args["publishedAfter"] = after
        if region_code:
            args["regionCode"] = region_code

        if page_token is not None:
            args["pageToken"] = page_token

        res_data = self.paged_by_page_token(
            resource="activities", args=args, count=count
        )

        if return_json:
            return res_data
        else:
            return ActivityListResponse.from_dict(res_data)

    def get_activities_by_me(
        self,
        *,
        parts: Optional[Union[str, list, tuple, set]] = None,
        before: Optional[str] = None,
        after: Optional[str] = None,
        region_code: Optional[str] = None,
        count: Optional[int] = 20,
        limit: int = 20,
        page_token: Optional[str] = None,
        return_json: bool = False,
    ):
        """
        Retrieve authorized user's activities.

        Note:
            This need you do authorize first.

        Args:
            parts ((str,list,tuple,set) optional):
                The resource parts for activities you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            before (str, optional):
                Set this will only return the activities occurred before this timestamp.
                This need specified in ISO 8601 (YYYY-MM-DDThh:mm:ss.sZ) format.
            after (str, optional):
                Set this will only return the activities occurred after this timestamp.
                This need specified in ISO 8601 (YYYY-MM-DDThh:mm:ss.sZ) format.
            region_code (str, optional):
                Set this will only return the activities for the specified country.
                This need specified with an ISO 3166-1 alpha-2 country code.
            count (int, optional):
                The count will retrieve activities data.
                Default is 20.
                If provide this with None, will retrieve all activities.
            limit (int, optional):
                The maximum number of items each request retrieve.
                For activities, this should not be more than 50.
                Default is 20.
            page_token (str, optional):
                The token of the page of activities result to retrieve.
                You can use this retrieve point result page directly.
                And you should know about the page result set for YouTube.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.ActivityListResponse instance.

        Returns:
            ActivityListResponse or original data.
        """

        if count is None:
            limit = 50  # for activities the max limit for per request is 50
        else:
            limit = min(count, limit)

        args = {
            "mine": True,
            "part": enf_parts(resource="activities", value=parts),
            "maxResults": limit,
        }

        if before:
            args["publishedBefore"] = before
        if after:
            args["publishedAfter"] = after
        if region_code:
            args["regionCode"] = region_code

        if page_token is not None:
            args["pageToken"] = page_token

        res_data = self.paged_by_page_token(
            resource="activities", args=args, count=count
        )

        if return_json:
            return res_data
        else:
            return ActivityListResponse.from_dict(res_data)

    def get_captions_by_video(
        self,
        *,
        video_id: str,
        parts: Optional[Union[str, list, tuple, set]] = None,
        caption_id: Optional[Union[str, list, tuple, set]] = None,
        return_json: bool = False,
    ):
        """
        Retrieve authorized user's video's caption data.

        Note:
            This need you do authorize first.

        Args:
            video_id (str):
                The id for video which you want to get caption.
            parts ((str,list,tuple,set) optional):
                The resource parts for caption you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            caption_id ((str,list,tuple,set)):
                The id for caption that you want to get data.
                You can pass this with single id str,comma-separated id str, or list, tuple, set of id str.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.CaptionListResponse instance.
        Returns:
            CaptionListResponse or original data.
        """

        args = {
            "videoId": video_id,
            "part": enf_parts("captions", parts),
        }

        if caption_id is not None:
            args["id"] = enf_comma_separated("caption_id", caption_id)

        resp = self._request(resource="captions", method="GET", args=args)
        data = self._parse_response(resp)

        if return_json:
            return data
        else:
            return CaptionListResponse.from_dict(data)

    def get_channel_info(
        self,
        # *,
        channel_id: Optional[Union[str, list, tuple, set]] = None,
        for_username: Optional[str] = None,
        mine: Optional[bool] = None,
        parts: Optional[Union[str, list, tuple, set]] = None,
        hl: str = "en_US",
        return_json: Optional[bool] = False
    ):
        """
        Retrieve channel data from YouTube Data API.

        Note:
            1. Don't know why, but now you couldn't get channel list by given an guide category.
               You can only get list by parameters mine,forUsername,id.
               Refer: https://developers.google.com/youtube/v3/guides/implementation/channels
            2. The origin maxResult param not work for these filter method.

        Args:
            channel_id ((str,list,tuple,set), optional):
                The id or comma-separated id string for youtube channel which you want to get.
                You can also pass this with an id list, tuple, set.
            for_username (str, optional):
                The name for YouTube username which you want to get.
                Note: This name may the old youtube version's channel's user's username, Not the the channel name.
                Refer: https://developers.google.com/youtube/v3/guides/working_with_channel_ids
            mine (bool, optional):
                If you have give the authorization. Will return your channels.
                Must provide the access token.
            parts (str, optional):
                Comma-separated list of one or more channel resource properties.
                If not provided. will use default public properties.
            hl (str, optional):
                If provide this. Will return channel's language localized info.
                This value need https://developers.google.com/youtube/v3/docs/i18nLanguages.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.ChannelListResponse instance.
        Returns:
            ChannelListResponse instance or original data.
        """

        args = {
            "part": enf_parts(resource="channels", value=parts),
            "hl": hl,
        }
        if for_username is not None:
            args["forUsername"] = for_username
        elif channel_id is not None:
            args["id"] = enf_comma_separated("channel_id", channel_id)
        elif mine is not None:
            args["mine"] = mine
        else:
            raise PyYouTubeException(
                ErrorMessage(
                    status_code=ErrorCode.MISSING_PARAMS,
                    message=f"Specify at least one of channel_id,channel_name or mine",
                )
            )

        resp = self._request(resource="channels", method="GET", args=args)

        data = self._parse_response(resp)
        if return_json:
            return data
        else:
            return ChannelListResponse.from_dict(data)

    def get_channel_sections_by_id(
        self,
        *,
        section_id: Union[str, list, tuple, set],
        parts: Optional[Union[str, list, tuple, set]] = None,
        return_json: Optional[bool] = False,
    ) -> Union[ChannelSectionResponse, dict]:
        """
        Retrieve channel section info by his ids(s).

        Args:
            section_id:
                The id(s) for channel sections.
                You can pass this with single id str, comma-separated id str, or a list,tuple,set of ids.
            parts:
                The resource parts for channel section you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            return_json:
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.ChannelSectionResponse instance.
        Returns:
            ChannelSectionResponse or original data.
        """

        args = {
            "id": enf_comma_separated(field="section_id", value=section_id),
            "part": enf_parts(resource="channelSections", value=parts),
        }

        resp = self._request(resource="channelSections", args=args)
        data = self._parse_response(resp)

        if return_json:
            return data
        else:
            return ChannelSectionResponse.from_dict(data)

    def get_channel_sections_by_channel(
        self,
        *,
        channel_id: Optional[str] = None,
        mine: bool = False,
        parts: Optional[Union[str, list, tuple, set]] = None,
        return_json: Optional[bool] = False,
    ) -> Union[ChannelSectionResponse, dict]:
        """
        Retrieve channel sections by channel id.

        Args:
            channel_id:
                The id for channel which you want to get channel sections.
            mine:
                If you want to get your channel's sections, set this with True.
                And this need your authorization.
            parts:
                The resource parts for channel section you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            return_json:
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.ChannelSectionResponse instance.
        Returns:
            ChannelSectionResponse or original data.
        """

        args = {
            "part": enf_parts(resource="channelSections", value=parts),
        }

        if mine:
            args["mine"] = mine
        else:
            args["channelId"] = channel_id

        resp = self._request(resource="channelSections", args=args)
        data = self._parse_response(resp)

        if return_json:
            return data
        else:
            return ChannelSectionResponse.from_dict(data)

    def get_comment_by_id(
        self,
        *,
        comment_id: Union[str, list, tuple, set],
        parts: Optional[Union[str, list, tuple, set]] = None,
        text_format: Optional[str] = "html",
        return_json: Optional[bool] = False,
    ):
        """
        Retrieve comment info by given comment id str.

        Args:
            comment_id (str, optional):
                The id for comment that you want to retrieve data.
                You can pass this with single id str, comma-separated id str, or a list,tuple,set of ids.
            parts ((str,list,tuple,set), optional):
                The resource parts for you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            text_format (str, optional):
                Comments left by users format style.
                Acceptable values are: html, plainText.
                Default is html.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.CommentListResponse instance.

        Returns:
            CommentListResponse or original data
        """

        args = {
            "id": enf_comma_separated(field="comment_id", value=comment_id),
            "part": enf_parts(resource="comments", value=parts),
            "textFormat": text_format,
        }

        resp = self._request(resource="comments", method="GET", args=args)
        data = self._parse_response(resp)

        if return_json:
            return data
        else:
            return CommentListResponse.from_dict(data)

    def get_comments(
        self,
        *,
        parent_id: str,
        parts: Optional[Union[str, list, tuple, set]] = None,
        text_format: Optional[str] = "html",
        count: Optional[int] = 20,
        limit: Optional[int] = 20,
        page_token: Optional[str] = None,
        return_json: Optional[bool] = False,
    ):
        """
        Retrieve comments info by given parent id.
        Note: YouTube currently supports replies only for top-level comments.
        However, replies to replies may be supported in the future.

        Args:
            parent_id (str):
                Provide the ID of the comment for which replies should be retrieved.
            parts ((str,list,tuple,set), optional):
                The resource parts for you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            text_format (str, optional):
                Comments left by users format style.
                Acceptable values are: html, plainText.
                Default is html.
            count (int, optional):
                The count will retrieve videos data.
                Default is 20.
                If provide this with None, will retrieve all comments.
            limit (int, optional):
                The maximum number of items each request retrieve.
                For comments, this should not be more than 100.
                Default is 20.
            page_token(str, optional):
                The token of the page of comments result to retrieve.
                You can use this retrieve point result page directly.
                And you should know about the the result set for YouTube.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.CommentListResponse instance.
        Returns:
            CommentListResponse or original data
        """

        if count is None:
            limit = 100  # for comments the max limit for per request is 100
        else:
            limit = min(count, limit)

        args = {
            "parentId": parent_id,
            "part": enf_parts(resource="comments", value=parts),
            "textFormat": text_format,
            "maxResults": limit,
        }

        if page_token is not None:
            args["pageToken"] = page_token

        res_data = self.paged_by_page_token(resource="comments", args=args, count=count)
        if return_json:
            return res_data
        else:
            return CommentListResponse.from_dict(res_data)

    def get_comment_thread_by_id(
        self,
        *,
        comment_thread_id: Union[str, list, tuple, set],
        parts: Optional[Union[str, list, tuple, set]] = None,
        text_format: Optional[str] = "html",
        return_json: Optional[bool] = False,
    ):
        """
        Retrieve the comment thread info by given id.

        Args:
            comment_thread_id ((str,list,tuple,set)):
                The id for comment thread that you want to retrieve data.
                You can pass this with single id str, comma-separated id str, or a list,tuple,set of ids.
            parts ((str,list,tuple,set), optional):
                The resource parts for you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            text_format (str, optional):
                Comments left by users format style.
                Acceptable values are: html, plainText.
                Default is html.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.CommentThreadListResponse instance.
        Returns:
            CommentThreadListResponse or original data
        """

        args = {
            "id": enf_comma_separated("comment_thread_id", comment_thread_id),
            "part": enf_parts(resource="commentThreads", value=parts),
            "textFormat": text_format,
        }

        resp = self._request(resource="commentThreads", method="GET", args=args)
        data = self._parse_response(resp)

        if return_json:
            return data
        else:
            return CommentThreadListResponse.from_dict(data)

    def get_comment_threads(
        self,
        *,
        all_to_channel_id: Optional[str] = None,
        channel_id: Optional[str] = None,
        video_id: Optional[str] = None,
        parts: Optional[Union[str, list, tuple, set]] = None,
        moderation_status: Optional[str] = None,
        order: Optional[str] = None,
        search_terms: Optional[str] = None,
        text_format: Optional[str] = "html",
        count: Optional[int] = 20,
        limit: Optional[int] = 20,
        page_token: Optional[str] = None,
        return_json: Optional[bool] = False,
    ):
        """
        Retrieve the comment threads info by given filter condition.

        Args:
            all_to_channel_id (str, optional):
                If you provide this with a channel id, will return all comment threads associated with the channel.
                The response can include comments about the channel or about the channel's videos.
            channel_id (str, optional):
                If you provide this with a channel id, will return the comment threads associated with the channel.
                But the response not include comments about the channel's videos.
            video_id  (str, optional):
                If you provide this with a video id, will return the comment threads associated with the video.
            parts ((str,list,tuple,set), optional)
                The resource parts for you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            moderation_status (str, optional):
                This parameter must used with authorization.
                If you provide this. the response will return comment threads match this filter only.
                Acceptable values are:
                    - heldForReview: Retrieve comment threads that are awaiting review by a moderator.
                    - likelySpam: Retrieve comment threads classified as likely to be spam.
                    - published: Retrieve threads of published comments. this is default for all.
                See more: https://developers.google.com/youtube/v3/docs/commentThreads/list#parameters
            order (str, optional):
                Order parameter specifies the order in which the API response should list comment threads.
                Acceptable values are:
                    - time: Comment threads are ordered by time. This is the default behavior.
                    - relevance: Comment threads are ordered by relevance.
            search_terms (str, optional):
                The searchTerms parameter instructs the API to limit the API response to only contain comments
                that contain the specified search terms.
            text_format (str, optional):
                Comments left by users format style.
                Acceptable values are: html, plainText.
                Default is html.
            count (int, optional):
                The count will retrieve comment threads data.
                Default is 20.
                If provide this with None, will retrieve all comment threads.
            limit (int, optional):
                The maximum number of items each request retrieve.
                For comment threads, this should not be more than 100.
                Default is 20.
            page_token(str, optional):
                The token of the page of commentThreads result to retrieve.
                You can use this retrieve point result page directly.
                And you should know about the the result set for YouTube.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.CommentThreadListResponse instance.

        Returns:
            CommentThreadListResponse or original data
        """

        if count is None:
            limit = 100  # for commentThreads the max limit for per request is 100
        else:
            limit = min(count, limit)

        args = {
            "part": enf_parts(resource="commentThreads", value=parts),
            "maxResults": limit,
            "textFormat": text_format,
        }

        if all_to_channel_id:
            args["allThreadsRelatedToChannelId"] = (all_to_channel_id,)
        elif channel_id:
            args["channelId"] = channel_id
        elif video_id:
            args["videoId"] = video_id
        else:
            raise PyYouTubeException(
                ErrorMessage(
                    status_code=ErrorCode.MISSING_PARAMS,
                    message=f"Specify at least one of all_to_channel_id, channel_id or video_id",
                )
            )

        if moderation_status:
            args["moderationStatus"] = moderation_status
        if order:
            args["order"] = order
        if search_terms:
            args["searchTerms"] = search_terms

        if page_token is not None:
            args["pageToken"] = page_token

        res_data = self.paged_by_page_token(
            resource="commentThreads", args=args, count=count
        )
        if return_json:
            return res_data
        else:
            return CommentThreadListResponse.from_dict(res_data)

    def get_i18n_languages(
        self,
        *,
        parts: Optional[Union[str, list, tuple, set]] = None,
        hl: Optional[str] = "en_US",
        return_json: Optional[bool] = False,
    ) -> Union[I18nLanguageListResponse, dict]:
        """
        Returns a list of application languages that the YouTube website supports.

        Args:
            parts:
                The resource parts for i18n language you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            hl:
                If provide this. Will return i18n language's language localized info.
                This value need https://developers.google.com/youtube/v3/docs/i18nLanguages.
            return_json:
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.I18nLanguageListResponse instance.

        Returns:
            I18nLanguageListResponse or original data.
        """

        args = {"hl": hl, "part": enf_parts(resource="i18nLanguages", value=parts)}

        resp = self._request(resource="i18nLanguages", args=args)
        data = self._parse_response(resp)

        if return_json:
            return data
        else:
            return I18nLanguageListResponse.from_dict(data)

    def get_i18n_regions(
        self,
        *,
        parts: Optional[Union[str, list, tuple, set]] = None,
        hl: Optional[str] = "en_US",
        return_json: Optional[bool] = False,
    ) -> Union[I18nRegionListResponse, dict]:
        """
        Retrieve all available regions.

        Args:
            parts:
                The resource parts for i18n region you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            hl:
                If provide this. Will return i18n region's language localized info.
                This value need https://developers.google.com/youtube/v3/docs/i18nLanguages.
            return_json:
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.I18nRegionListResponse instance.
        Returns:
            I18nRegionListResponse or origin data
        """

        args = {"hl": hl, "part": enf_parts(resource="i18nRegions", value=parts)}

        resp = self._request(resource="i18nRegions", args=args)
        data = self._parse_response(resp)

        if return_json:
            return data
        else:
            return I18nRegionListResponse.from_dict(data)

    def get_members(
        self,
        *,
        parts: Optional[Union[str, list, tuple, set]] = None,
        mode: Optional[str] = "all_current",
        count: Optional[int] = 5,
        limit: Optional[int] = 5,
        page_token: Optional[str] = None,
        has_access_to_level: Optional[str] = None,
        filter_by_member_channel_id: Optional[Union[str, list, tuple, set]] = None,
        return_json: Optional[bool] = False,
    ) -> Union[MemberListResponse, dict]:
        """
        Retrieve a list of members for a channel.

        Args:
            parts ((str,list,tuple,set) optional):
                The resource parts for member you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            mode:
                The mode parameter indicates which members will be included in the API response.
                Set the parameter value to one of the following values:
                    - all_current (default): List current members, from newest to oldest. When this value is used,
                        the end of the list is reached when the API response does not contain a nextPageToken.
                    - updates : List only members that joined or upgraded since the previous API call.
                        Note: The first call starts a new stream of updates but does not actually return any members.
                        To start retrieving the membership updates, you need to poll the endpoint using the
                        nextPageToken at your desired frequency.
                        Note that when this value is used, the API response always contains a nextPageToken.
            count (int, optional):
                The count will retrieve videos data.
                Default is 5.
            limit (int, optional):
                The maximum number of items each request retrieve.
                For members, this should not be more than 1000.
                Default is 5.
            page_token (str, optional):
                The token of the page of search result to retrieve.
                You can use this retrieve point result page directly.
                And you should know about the the result set for YouTube.
            has_access_to_level (str, optional):
                The hasAccessToLevel parameter value is a level ID that specifies the minimum level
                that members in the result set should have.
            filter_by_member_channel_id ((str,list,tuple,set) optional):
                A list of channel IDs that can be used to check the membership status of specific users.
                A maximum of 100 channels can be specified per call.
            return_json (bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.MemberListResponse instance.
        Returns:
            MemberListResponse or original data
        """

        if count is None:
            limit = 1000
        else:
            limit = min(count, limit)

        args = {
            "part": enf_parts(resource="members", value=parts),
            "maxResults": limit,
        }

        if mode:
            args["mode"] = mode

        if page_token is not None:
            args["pageToken"] = page_token

        if has_access_to_level:
            args["hasAccessToLevel"] = has_access_to_level

        if filter_by_member_channel_id:
            args["filterByMemberChannelId"] = enf_parts(
                resource="filterByMemberChannelId",
                value=filter_by_member_channel_id,
                check=False,
            )

        res_data = self.paged_by_page_token(
            resource="members",
            args=args,
            count=count,
        )
        if return_json:
            return res_data
        else:
            return MemberListResponse.from_dict(res_data)

    def get_membership_levels(
        self,
        *,
        parts: Optional[Union[str, list, tuple, set]] = None,
        return_json: Optional[bool] = False,
    ) -> Union[MembershipsLevelListResponse, dict]:
        """
        Retrieve membership levels for a channel

        Notes:
            This requires your authorization.

        Args:
            parts ((str,list,tuple,set) optional):
                The resource parts for membership level you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            return_json (bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.MembershipsLevelListResponse instance.

        Returns:
            MembershipsLevelListResponse or original data
        """

        args = {
            "part": enf_parts(resource="membershipsLevels", value=parts),
        }

        resp = self._request(resource="membershipsLevels", args=args)
        data = self._parse_response(resp)

        if return_json:
            return data
        else:
            return MembershipsLevelListResponse.from_dict(data)

    def get_playlist_item_by_id(
        self,
        *,
        playlist_item_id: Union[str, list, tuple, set],
        parts: Optional[Union[str, list, tuple, set]] = None,
        return_json: Optional[bool] = False,
    ):
        """
        Retrieve playlist Items info by your given id

        Args:
            playlist_item_id ((str,list,tuple,set)):
                The id for playlist item that you want to retrieve info.
                You can pass this with single id str, comma-separated id str.
                Or a list,tuple,set of ids.
            parts ((str,list,tuple,set) optional):
                The resource parts for you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.PlayListItemApiResponse instance.
        Returns:
            PlaylistItemListResponse or original data
        """

        args = {
            "id": enf_comma_separated("playlist_item_id", playlist_item_id),
            "part": enf_parts(resource="playlistItems", value=parts),
        }

        resp = self._request(resource="playlistItems", method="GET", args=args)
        data = self._parse_response(resp)

        if return_json:
            return data
        else:
            return PlaylistItemListResponse.from_dict(data)

    def get_playlist_items(
        self,
        *,
        playlist_id: str,
        parts: Optional[Union[str, list, tuple, set]] = None,
        video_id: Optional[str] = None,
        count: Optional[int] = 5,
        limit: Optional[int] = 5,
        page_token: Optional[str] = None,
        return_json: Optional[bool] = False,
    ):
        """
        Retrieve playlist Items info by your given playlist id

        Args:
            playlist_id (str):
                The id for playlist that you want to retrieve items data.
            parts ((str,list,tuple,set) optional):
                The resource parts for you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            video_id (str, Optional):
                Specifies that the request should return only the playlist items that contain the specified video.
            count (int, optional):
                The count will retrieve playlist items data.
                Default is 5.
                If provide this with None, will retrieve all playlist items.
            limit (int, optional):
                The maximum number of items each request retrieve.
                For playlistItem, this should not be more than 50.
                Default is 5
            page_token(str, optional):
                The token of the page of playlist items result to retrieve.
                You can use this retrieve point result page directly.
                And you should know about the the result set for YouTube.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.PlayListItemApiResponse instance.
        Returns:
            PlaylistItemListResponse or original data
        """

        if count is None:
            limit = 50  # for playlistItems the max limit for per request is 50
        else:
            limit = min(count, limit)

        args = {
            "playlistId": playlist_id,
            "part": enf_parts(resource="playlistItems", value=parts),
            "maxResults": limit,
        }
        if video_id is not None:
            args["videoId"] = video_id

        if page_token is not None:
            args["pageToken"] = page_token

        res_data = self.paged_by_page_token(
            resource="playlistItems", args=args, count=count
        )
        if return_json:
            return res_data
        else:
            return PlaylistItemListResponse.from_dict(res_data)

    def get_playlist_by_id(
        self,
        *,
        playlist_id: Union[str, list, tuple, set],
        parts: Optional[Union[str, list, tuple, set]] = None,
        hl: Optional[str] = "en_US",
        return_json: Optional[bool] = False,
    ):
        """
        Retrieve playlist data by given playlist id.

        Args:
            playlist_id ((str,list,tuple,set)):
                The id for playlist that you want to retrieve data.
                You can pass this with single id str,comma-separated id str, or list, tuple, set of id str.
            parts (str, optional):
                Comma-separated list of one or more playlist resource properties.
                You can also pass this with list, tuple, set of part str.
                If not provided. will use default public properties.
            hl (str, optional):
                If provide this. Will return playlist's language localized info.
                This value need https://developers.google.com/youtube/v3/docs/i18nLanguages.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.PlaylistListResponse instance
        Returns:
            PlaylistListResponse or original data
        """
        args = {
            "id": enf_comma_separated("playlist_id", playlist_id),
            "part": enf_parts(resource="playlists", value=parts),
            "hl": hl,
        }

        resp = self._request(resource="playlists", method="GET", args=args)
        data = self._parse_response(resp)

        if return_json:
            return data
        else:
            return PlaylistListResponse.from_dict(data)

    def get_playlists(
        self,
        *,
        channel_id: Optional[str] = None,
        mine: Optional[bool] = None,
        parts: Optional[Union[str, list, tuple, set]] = None,
        count: Optional[int] = 5,
        limit: Optional[int] = 5,
        hl: Optional[str] = "en_US",
        page_token: Optional[str] = None,
        return_json: Optional[bool] = False,
    ):
        """
        Retrieve channel playlists info from youtube data api.

        Args:
            channel_id (str, optional):
                If provide channel id, this will return pointed channel's playlist info.
            mine (bool, optional):
                If you have given the authorization. Will return your playlists.
                Must provide the access token.
            parts (str, optional):
                Comma-separated list of one or more playlist resource properties.
                You can also pass this with list, tuple, set of part str.
                If not provided. will use default public properties.
            count (int, optional):
                The count will retrieve playlist data.
                Default is 5.
                If provide this with None, will retrieve all playlists.
            limit (int, optional):
                The maximum number of items each request to retrieve.
                For playlist, this should not be more than 50.
                Default is 5
            hl (str, optional):
                If provide this. Will return playlist's language localized info.
                This value need https://developers.google.com/youtube/v3/docs/i18nLanguages.
            page_token(str, optional):
                The token of the page of playlists result to retrieve.
                You can use this retrieve point result page directly.
                And you should know about the the result set for YouTube.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.PlaylistListResponse instance.
        Returns:
            PlaylistListResponse or original data
        """

        if count is None:
            limit = 50  # for playlists the max limit for per request is 50
        else:
            limit = min(count, limit)

        args = {
            "part": enf_parts(resource="playlists", value=parts),
            "hl": hl,
            "maxResults": limit,
        }

        if channel_id is not None:
            args["channelId"] = channel_id
        elif mine is not None:
            args["mine"] = mine
        else:
            raise PyYouTubeException(
                ErrorMessage(
                    status_code=ErrorCode.MISSING_PARAMS,
                    message=f"Specify at least one of channel_id,playlist_id or mine",
                )
            )

        if page_token is not None:
            args["pageToken"] = page_token

        res_data = self.paged_by_page_token(
            resource="playlists", args=args, count=count
        )
        if return_json:
            return res_data
        else:
            return PlaylistListResponse.from_dict(res_data)

    def search(
        self,
        *,
        parts: Optional[Union[str, list, tuple, set]] = None,
        for_developer: Optional[bool] = None,
        for_mine: Optional[bool] = None,
        related_to_video_id: Optional[str] = None,
        channel_id: Optional[str] = None,
        channel_type: Optional[str] = None,
        event_type: Optional[str] = None,
        location: Optional[str] = None,
        location_radius: Optional[str] = None,
        count: Optional[int] = 10,
        limit: Optional[int] = 10,
        order: Optional[str] = None,
        published_after: Optional[str] = None,
        published_before: Optional[str] = None,
        q: Optional[str] = None,
        region_code: Optional[str] = None,
        relevance_language: Optional[str] = None,
        safe_search: Optional[str] = None,
        topic_id: Optional[str] = None,
        search_type: Optional[Union[str, list, tuple, set]] = None,
        video_caption: Optional[str] = None,
        video_category_id: Optional[str] = None,
        video_definition: Optional[str] = None,
        video_dimension: Optional[str] = None,
        video_duration: Optional[str] = None,
        video_embeddable: Optional[str] = None,
        video_license: Optional[str] = None,
        video_paid_product_placement: Optional[str] = None,
        video_syndicated: Optional[str] = None,
        video_type: Optional[str] = None,
        page_token: Optional[str] = None,
        return_json: Optional[bool] = False,
    ) -> Union[SearchListResponse, dict]:
        """
        Main search api implementation.
        You can find all parameters description at https://developers.google.com/youtube/v3/docs/search/list#parameters

        Returns:
            SearchListResponse or original data
        """
        parts = enf_parts(resource="search", value=parts)
        if search_type is None:
            search_type = "video,channel,playlist"
        else:
            search_type = enf_comma_separated(field="search_type", value=search_type)

        args = {
            "part": parts,
            "maxResults": min(limit, count),
        }
        if for_developer:
            args["forDeveloper"] = for_developer
        if for_mine:
            args["forMine"] = for_mine
        if related_to_video_id:
            args["relatedToVideoId"] = related_to_video_id
        if channel_id:
            args["channelId"] = channel_id
        if channel_type:
            args["channelType"] = channel_type
        if event_type:
            args["eventType"] = event_type
        if location:
            args["location"] = location
        if location_radius:
            args["locationRadius"] = location_radius
        if order:
            args["order"] = order
        if published_after:
            args["publishedAfter"] = published_after
        if published_before:
            args["publishedBefore"] = published_before
        if q:
            args["q"] = q
        if region_code:
            args["regionCode"] = region_code
        if relevance_language:
            args["relevanceLanguage"] = relevance_language
        if safe_search:
            args["safeSearch"] = safe_search
        if topic_id:
            args["topicId"] = topic_id
        if search_type:
            args["type"] = search_type
        if video_caption:
            args["videoCaption"] = video_caption
        if video_category_id:
            args["videoCategoryId"] = video_category_id
        if video_definition:
            args["videoDefinition"] = video_definition
        if video_dimension:
            args["videoDimension"] = video_dimension
        if video_duration:
            args["videoDuration"] = video_duration
        if video_embeddable:
            args["videoEmbeddable"] = video_embeddable
        if video_license:
            args["videoLicense"] = video_license
        if video_paid_product_placement:
            args["videoPaidProductPlacement"] = video_paid_product_placement
        if video_syndicated:
            args["videoSyndicated"] = video_syndicated
        if video_type:
            args["videoType"] = video_type
        if page_token:
            args["pageToken"] = page_token

        res_data = self.paged_by_page_token(resource="search", args=args, count=count)

        if return_json:
            return res_data
        else:
            return SearchListResponse.from_dict(res_data)

    def search_by_keywords(
        self,
        *,
        q: Optional[str],
        parts: Optional[Union[str, list, tuple, set]] = None,
        search_type: Optional[Union[str, list, tuple, set]] = None,
        count: Optional[int] = 25,
        limit: Optional[int] = 25,
        page_token: Optional[str] = None,
        return_json: Optional[bool] = False,
        **kwargs: Optional[dict],
    ) -> Union[SearchListResponse, dict]:
        """
        This is simplest usage for search api. You can only passed the keywords to retrieve data from YouTube.
        And the result will include videos,playlists and channels.

        Note: A call to this method has a quota cost of 100 units.

        Args:
            q (str):
                Your keywords can also use the Boolean NOT (-) and OR (|) operators to exclude videos or
                to find videos that are associated with one of several search terms. For example,
                to search for videos matching either "boating" or "sailing",
                set the q parameter value to boating|sailing. Similarly,
                to search for videos matching either "boating" or "sailing" but not "fishing",
                set the q parameter value to boating|sailing -fishing.
                Note that the pipe character must be URL-escaped when it is sent in your API request.
                The URL-escaped value for the pipe character is %7C.
            parts ((str,list,tuple,set) optional):
                The resource parts for you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            search_type ((str,list,tuple,set), optional):
                Parameter restricts a search query to only retrieve a particular type of resource.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
                The default value is video,channel,playlist
                Acceptable values are:
                    - channel
                    - playlist
                    - video
            count (int, optional):
                The count will retrieve videos data.
                Default is 25.
            limit (int, optional):
                The maximum number of items each request retrieve.
                For search, this should not be more than 50.
                Default is 25.
            page_token (str, optional):
                The token of the page of search result to retrieve.
                You can use this retrieve point result page directly.
                And you should know about the the result set for YouTube.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.SearchListResponse instance.
            kwargs:
                If you want use this pass more args. You can use this.

        Returns:
            SearchListResponse or original data
        """
        return self.search(
            parts=parts,
            q=q,
            search_type=search_type,
            count=count,
            limit=limit,
            page_token=page_token,
            return_json=return_json,
            **kwargs,
        )

    def search_by_developer(
        self,
        *,
        parts: Optional[Union[str, list, tuple, set]],
        q: Optional[str] = None,
        count: Optional[int] = 25,
        limit: Optional[int] = 25,
        page_token: Optional[str] = None,
        return_json: Optional[bool] = False,
        **kwargs,
    ) -> Union[SearchListResponse, dict]:
        """
        Parameter restricts the search to only retrieve videos uploaded via the developer's application or website.

        Args:
            parts:
                The resource parts for you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            q:
                Your keywords can also use the Boolean NOT (-) and OR (|) operators to exclude videos or
                to find videos that are associated with one of several search terms. For example,
                to search for videos matching either "boating" or "sailing",
                set the q parameter value to boating|sailing. Similarly,
                to search for videos matching either "boating" or "sailing" but not "fishing",
                set the q parameter value to boating|sailing -fishing.
                Note that the pipe character must be URL-escaped when it is sent in your API request.
                The URL-escaped value for the pipe character is %7C.
            count:
                The count will retrieve videos data.
                Default is 25.
            limit:
                The maximum number of items each request retrieve.
                For search, this should not be more than 50.
                Default is 25.
            page_token:
                The token of the page of search result to retrieve.
                You can use this retrieve point result page directly.
                And you should know about the the result set for YouTube.
            return_json:
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.SearchListResponse instance.
            kwargs:
                If you want use this pass more args. You can use this.

        Returns:
            SearchListResponse or original data
        """
        return self.search(
            for_developer=True,
            search_type="video",
            parts=parts,
            q=q,
            count=count,
            limit=limit,
            page_token=page_token,
            return_json=return_json,
            **kwargs,
        )

    def search_by_mine(
        self,
        *,
        parts: Optional[Union[str, list, tuple, set]],
        q: Optional[str] = None,
        count: Optional[int] = 25,
        limit: Optional[int] = 25,
        page_token: Optional[str] = None,
        return_json: Optional[bool] = False,
        **kwargs,
    ) -> Union[SearchListResponse, dict]:
        """
        Parameter restricts the search to only retrieve videos owned by the authenticated user.

        Note:
            This methods can not use following parameters:
            video_definition, video_dimension, video_duration, video_license,
            video_embeddable, video_syndicated, video_type.
        Args:
            q:
                Your keywords can also use the Boolean NOT (-) and OR (|) operators to exclude videos or
                to find videos that are associated with one of several search terms. For example,
                to search for videos matching either "boating" or "sailing",
                set the q parameter value to boating|sailing. Similarly,
                to search for videos matching either "boating" or "sailing" but not "fishing",
                set the q parameter value to boating|sailing -fishing.
                Note that the pipe character must be URL-escaped when it is sent in your API request.
                The URL-escaped value for the pipe character is %7C.
            parts ((str,list,tuple,set) optional):
                The resource parts for you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            count (int, optional):
                The count will retrieve videos data.
                Default is 25.
            limit (int, optional):
                The maximum number of items each request retrieve.
                For search, this should not be more than 50.
                Default is 25.
            page_token (str, optional):
                The token of the page of search result to retrieve.
                You can use this retrieve point result page directly.
                And you should know about the the result set for YouTube.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.SearchListResponse instance.
            kwargs:
                If you want use this pass more args. You can use this.

        Returns:
            SearchListResponse or original data
        """
        return self.search(
            for_mine=True,
            search_type="video",
            parts=parts,
            q=q,
            count=count,
            limit=limit,
            page_token=page_token,
            return_json=return_json,
            **kwargs,
        )

    def search_by_related_video(
        self,
        *,
        related_to_video_id: str,
        parts: Optional[Union[str, list, tuple, set]] = None,
        region_code: Optional[str] = None,
        relevance_language: Optional[str] = None,
        safe_search: Optional[str] = None,
        count: Optional[int] = 25,
        limit: Optional[int] = 25,
        page_token: Optional[str] = None,
        return_json: Optional[bool] = False,
    ) -> Union[SearchListResponse, dict]:
        """
        Retrieve a list of videos related to that video.

        Args:
            related_to_video_id:
                 A YouTube video ID which result associated with.
            parts:
                The resource parts for you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            region_code:
                Parameter instructs the API to return search results for videos
                that can be viewed in the specified country.
            relevance_language:
                Parameter instructs the API to return search results that are most relevant to the specified language.
            safe_search:
                Parameter indicates whether the search results should include restricted content
                as well as standard content.
                Acceptable values are:
                    - moderate – YouTube will filter some content from search results and, at the least,
                      will filter content that is restricted in your locale. Based on their content,
                      search results could be removed from search results or demoted in search results.
                      This is the default parameter value.
                    - none – YouTube will not filter the search result set.
                    - strict – YouTube will try to exclude all restricted content from the search result set.
                      Based on their content, search results could be removed from search results or
                      demoted in search results.
            count:
                The count will retrieve videos data.
                Default is 25.
            limit:
                The maximum number of items each request retrieve.
                For search, this should not be more than 50.
                Default is 25.
            page_token:
                The token of the page of search result to retrieve.
                You can use this retrieve point result page directly.
                And you should know about the the result set for YouTube.
            return_json:
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.SearchListResponse instance.
        Returns:
            If you want use this pass more args. You can use this.
        """

        return self.search(
            parts=parts,
            related_to_video_id=related_to_video_id,
            search_type="video",
            region_code=region_code,
            relevance_language=relevance_language,
            safe_search=safe_search,
            count=count,
            limit=limit,
            page_token=page_token,
            return_json=return_json,
        )

    def get_subscription_by_id(
        self,
        *,
        subscription_id: Union[str, list, tuple, set],
        parts: Optional[Union[str, list, tuple, set]] = None,
        return_json: Optional[bool] = False,
    ):
        """
        Retrieve subscriptions by given subscription id(s).

        Note:
            This need authorized access token. or you will get no data.

        Args:
            subscription_id ((str,list,tuple,set)):
                The id for subscription that you want to retrieve data.
                You can pass this with single id str, comma-separated id str, or a list,tuple,set of ids.
            parts ((str,list,tuple,set), optional):
                The resource parts for you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.SubscriptionListResponse instance.
        Returns:
            SubscriptionListResponse or original data.
        """

        args = {
            "id": enf_comma_separated(field="subscription_id", value=subscription_id),
            "part": enf_parts(resource="subscriptions", value=parts),
        }

        resp = self._request(resource="subscriptions", method="GET", args=args)
        data = self._parse_response(resp)

        if return_json:
            return data
        else:
            return SubscriptionListResponse.from_dict(data)

    def get_subscription_by_channel(
        self,
        *,
        channel_id: str,
        parts: Optional[Union[str, list, tuple, set]] = None,
        for_channel_id: Optional[Union[str, list, tuple, set]] = None,
        order: Optional[str] = "relevance",
        count: Optional[int] = 20,
        limit: Optional[int] = 20,
        page_token: Optional[str] = None,
        return_json: Optional[bool] = False,
    ):
        """
        Retrieve the specified channel's subscriptions.

        Note:
             The API returns a 403 (Forbidden) HTTP response code if the specified channel
             does not publicly expose its subscriptions and the request is not authorized
             by the channel's owner.

        Args:
            channel_id (str):
                The id for channel which you want to get subscriptions.
            parts ((str,list,tuple,set) optional):
                The resource parts for subscription you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            for_channel_id ((str,list,tuple,set) optional):
                The parameter specifies a comma-separated list of channel IDs.
                and will then only contain subscriptions matching those channels.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of channel ids.
            order (str, optional):
                The parameter specifies the method that will be used to sort resources in the API response.
                Acceptable values are:
                    alphabetical – Sort alphabetically.
                    relevance – Sort by relevance.
                    unread – Sort by order of activity.
                Default is relevance
            count (int, optional):
                The count will retrieve subscriptions data.
                Default is 20.
                If provide this with None, will retrieve all subscriptions.
            limit (int, optional):
                The maximum number of items each request retrieve.
                For comment threads, this should not be more than 50.
                Default is 20.
            page_token(str, optional):
                The token of the page of subscriptions result to retrieve.
                You can use this retrieve point result page directly.
                And you should know about the the result set for YouTube.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.SubscriptionListResponse instance.
        Returns:
            SubscriptionListResponse or original data.
        """

        if count is None:
            limit = 50  # for subscriptions the max limit for per request is 50
        else:
            limit = min(count, limit)

        args = {
            "channelId": channel_id,
            "part": enf_parts(resource="subscriptions", value=parts),
            "order": order,
            "maxResults": limit,
        }

        if for_channel_id is not None:
            args["forChannelId"] = enf_comma_separated(
                field="for_channel_id", value=for_channel_id
            )

        if page_token is not None:
            args["pageToken"] = page_token

        res_data = self.paged_by_page_token(
            resource="subscriptions", args=args, count=count
        )
        if return_json:
            return res_data
        else:
            return SubscriptionListResponse.from_dict(res_data)

    def get_subscription_by_me(
        self,
        *,
        mine: Optional[bool] = None,
        recent_subscriber: Optional[bool] = None,
        subscriber: Optional[bool] = None,
        parts: Optional[Union[str, list, tuple, set]] = None,
        for_channel_id: Optional[Union[str, list, tuple, set]] = None,
        order: Optional[str] = "relevance",
        count: Optional[int] = 20,
        limit: Optional[int] = 20,
        page_token: Optional[str] = None,
        return_json: Optional[bool] = False,
    ):
        """
        Retrieve your subscriptions.

        Note:
            This can only used in a properly authorized request.
            And for me test the parameter `recent_subscriber` and `subscriber` maybe not working.
            Use the `mine` first.

        Args:
            mine (bool, optional):
                Set this parameter's value to True to retrieve a feed of the authenticated user's subscriptions.
            recent_subscriber (bool, optional):
                Set this parameter's value to true to retrieve a feed of the subscribers of the authenticated user
                in reverse chronological order (newest first).
                And this can only get most recent 1000 subscribers.
            subscriber (bool, optional):
                Set this parameter's value to true to retrieve a feed of the subscribers of
                the authenticated user in no particular order.
            parts ((str,list,tuple,set) optional):
                The resource parts for subscription you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            for_channel_id ((str,list,tuple,set) optional):
                The parameter specifies a comma-separated list of channel IDs.
                and will then only contain subscriptions matching those channels.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of channel ids.
            order (str, optional):
                The parameter specifies the method that will be used to sort resources in the API response.
                Acceptable values are:
                    alphabetical – Sort alphabetically.
                    relevance – Sort by relevance.
                    unread – Sort by order of activity.
                Default is relevance
            count (int, optional):
                The count will retrieve subscriptions data.
                Default is 20.
                If provide this with None, will retrieve all subscriptions.
            limit (int, optional):
                The maximum number of items each request retrieve.
                For subscriptions, this should not be more than 50.
                Default is 20.
            page_token(str, optional):
                The token of the page of subscriptions result to retrieve.
                You can use this retrieve point result page directly.
                And you should know about the the result set for YouTube.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.SubscriptionListResponse instance.

        Returns:
            SubscriptionListResponse or original data.
        """

        if count is None:
            limit = 50  # for subscriptions the max limit for per request is 50
        else:
            limit = min(count, limit)

        args = {
            "part": enf_parts(resource="subscriptions", value=parts),
            "order": order,
            "maxResults": limit,
        }

        if mine is not None:
            args["mine"] = mine
        elif recent_subscriber is not None:
            args["myRecentSubscribers"] = recent_subscriber
        elif subscriber is not None:
            args["mySubscribers"] = subscriber
        else:
            raise PyYouTubeException(
                ErrorMessage(
                    status_code=ErrorCode.MISSING_PARAMS,
                    message=f"Must specify at least one of mine,recent_subscriber,subscriber.",
                )
            )

        if for_channel_id is not None:
            args["forChannelId"] = enf_comma_separated(
                field="for_channel_id", value=for_channel_id
            )

        if page_token is not None:
            args["pageToken"] = page_token

        res_data = self.paged_by_page_token(
            resource="subscriptions", args=args, count=count
        )
        if return_json:
            return res_data
        else:
            return SubscriptionListResponse.from_dict(res_data)

    def get_video_abuse_report_reason(
        self,
        *,
        parts: Optional[Union[str, list, tuple, set]] = None,
        hl: Optional[str] = "en_US",
        return_json: Optional[bool] = False,
    ) -> Union[VideoAbuseReportReasonListResponse, dict]:
        """
        Retrieve a list of reasons that can be used to report abusive videos.

        Notes:
            This requires your authorization.

        Args:
            parts:
                The resource parts for abuse reason you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            hl:
                If provide this. Will return report reason's language localized info.
                This value need https://developers.google.com/youtube/v3/docs/i18nLanguages.
            return_json:
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.VideoAbuseReportReasonListResponse instance.
        Returns:
            VideoAbuseReportReasonListResponse or original data.
        """

        args = {
            "part": enf_parts(resource="videoAbuseReportReasons", value=parts),
            "hl": hl,
        }

        resp = self._request(resource="videoAbuseReportReasons", args=args)
        data = self._parse_response(resp)

        if return_json:
            return data
        else:
            return VideoAbuseReportReasonListResponse.from_dict(data)

    def get_video_categories(
        self,
        *,
        category_id: Optional[Union[str, list, tuple, set]] = None,
        region_code: Optional[str] = None,
        parts: Optional[Union[str, list, tuple, set]] = None,
        hl: Optional[str] = "en_US",
        return_json: Optional[bool] = False,
    ):
        """
        Retrieve video categories by category id or region code.

        Args:
            category_id ((str,list,tuple,set), optional):
                The id for video category thread that you want to retrieve data.
                You can pass this with single id str, comma-separated id str, or a list,tuple,set of ids.
            region_code (str, optional):
                The region code that you want to retrieve guide categories.
                The parameter value is an ISO 3166-1 alpha-2 country code.
                Refer: https://www.iso.org/iso-3166-country-codes.html
            parts ((str,list,tuple,set) optional):
                The resource parts for you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            hl (str, optional):
                If provide this. Will return video category's language localized info.
                This value need https://developers.google.com/youtube/v3/docs/i18nLanguages.
                Default is en_US.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.VideoCategoryListResponse instance.
        Returns:
            VideoCategoryListResponse or original data
        """
        args = {
            "part": enf_parts(resource="videoCategories", value=parts),
            "hl": hl,
        }

        if category_id is not None:
            args["id"] = enf_comma_separated(field="category_id", value=category_id)
        elif region_code is not None:
            args["regionCode"] = region_code
        else:
            raise PyYouTubeException(
                ErrorMessage(
                    status_code=ErrorCode.MISSING_PARAMS,
                    message="Specify at least one of category_id or region_code",
                )
            )

        resp = self._request(resource="videoCategories", method="GET", args=args)
        data = self._parse_response(resp)

        if return_json:
            return data
        else:
            return VideoCategoryListResponse.from_dict(data)

    def get_video_by_id(
        self,
        *,
        video_id: Union[str, list, tuple, set],
        parts: Optional[Union[str, list, tuple, set]] = None,
        hl: Optional[str] = "en_US",
        max_height: Optional[int] = None,
        max_width: Optional[int] = None,
        return_json: Optional[bool] = False,
        proxy:Optional[str]=None
    ):
        """
        Retrieve video data by given video id.

        Args:
            video_id ((str,list,tuple,set)):
                The id for video that you want to retrieve data.
                You can pass this with single id str, comma-separated id str, or a list,tuple,set of ids.
            parts ((str,list,tuple,set), optional):
                The resource parts for you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            hl (str, optional):
                If provide this. Will return video's language localized info.
                This value need https://developers.google.com/youtube/v3/docs/i18nLanguages.
            max_height (int, optional):
                Specifies the maximum height of the embedded player returned in the player.embedHtml property.
                Acceptable values are 72 to 8192, inclusive.
            max_width (int, optional):
                Specifies the maximum width of the embedded player returned in the player.embedHtml property.
                Acceptable values are 72 to 8192, inclusive.
                If provide max_height at the same time. This will may be shorter than max_height.
                For more https://developers.google.com/youtube/v3/docs/videos/list#parameters.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.VideoListResponse instance.

        Returns:
            VideoListResponse or original data
        """

        args = {
            "id": enf_comma_separated(field="video_id", value=video_id),
            "part": enf_parts(resource="videos", value=parts),
            "hl": hl,
        }
        if max_height is not None:
            args["maxHeight"] = max_height
        if max_width is not None:
            args["maxWidth"] = max_width

        resp = self._request(resource="videos", method="GET", args=args)
        data = self._parse_response(resp)

        if return_json:
            return data
        else:
            return VideoListResponse.from_dict(data)

    def get_videos_by_chart(
        self,
        *,
        chart: str,
        parts: Optional[Union[str, list, tuple, set]] = None,
        hl: Optional[str] = "en_US",
        max_height: Optional[int] = None,
        max_width: Optional[int] = None,
        region_code: Optional[str] = None,
        category_id: Optional[str] = "0",
        count: Optional[int] = 5,
        limit: Optional[int] = 5,
        page_token: Optional[str] = None,
        return_json: Optional[bool] = False,
    ):
        """
        Retrieve a list of YouTube's most popular videos.

        Args:
            chart (str):
                The chart string for you want to retrieve data.
                Acceptable values are: mostPopular
            parts ((str,list,tuple,set), optional):
                The resource parts for you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            hl (str, optional):
                If provide this. Will return playlist's language localized info.
                This value need https://developers.google.com/youtube/v3/docs/i18nLanguages.
            max_height (int, optional):
                Specifies the maximum height of the embedded player returned in the player.embedHtml property.
                Acceptable values are 72 to 8192, inclusive.
            max_width (int, optional):
                Specifies the maximum width of the embedded player returned in the player.embedHtml property.
                Acceptable values are 72 to 8192, inclusive.
                If provide max_height at the same time. This will may be shorter than max_height.
                For more https://developers.google.com/youtube/v3/docs/videos/list#parameters.
            region_code (str, optional):
                This parameter instructs the API to select a video chart available in the specified region.
                Value is an ISO 3166-1 alpha-2 country code.
            category_id (str, optional):
                The id for video category that you want to filter.
                Default is 0.
            count (int, optional):
                The count will retrieve videos data.
                Default is 5.
                If provide this with None, will retrieve all videos.
            limit (int, optional):
                The maximum number of items each request retrieve.
                For videos, this should not be more than 50.
                Default is 5.
            page_token(str, optional):
                The token of the page of videos result to retrieve.
                You can use this retrieve point result page directly.
                And you should know about the the result set for YouTube.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.PlaylistListResponse instance.

        Returns:
            VideoListResponse or original data
        """

        if count is None:
            limit = 50  # for videos the max limit for per request is 50
        else:
            limit = min(count, limit)

        args = {
            "chart": chart,
            "part": enf_parts(resource="videos", value=parts),
            "hl": hl,
            "maxResults": limit,
            "videoCategoryId": category_id,
        }
        if max_height is not None:
            args["maxHeight"] = max_height
        if max_width is not None:
            args["maxWidth"] = max_width
        if region_code:
            args["regionCode"] = region_code

        if page_token is not None:
            args["pageToken"] = page_token

        res_data = self.paged_by_page_token(resource="videos", args=args, count=count)
        if return_json:
            return res_data
        else:
            return VideoListResponse.from_dict(res_data)

    def get_videos_by_myrating(
        self,
        *,
        rating: str,
        parts: Optional[Union[str, list, tuple, set]] = None,
        hl: Optional[str] = "en_US",
        max_height: Optional[int] = None,
        max_width: Optional[int] = None,
        count: Optional[int] = 5,
        limit: Optional[int] = 5,
        page_token: Optional[str] = None,
        return_json: Optional[bool] = False,
    ):
        """
        Retrieve video data by my ration.

        Args:
            rating (str):
                The rating string for you to retrieve data.
                Acceptable values are: dislike, like
            parts ((str,list,tuple,set), optional):
                The resource parts for you want to retrieve.
                If not provide, use default public parts.
                You can pass this with single part str, comma-separated parts str or a list,tuple,set of parts.
            hl (str, optional):
                If provide this. Will return video's language localized info.
                This value need https://developers.google.com/youtube/v3/docs/i18nLanguages.
            max_height (int, optional):
                Specifies the maximum height of the embedded player returned in the player.embedHtml property.
                Acceptable values are 72 to 8192, inclusive.
            max_width (int, optional):
                Specifies the maximum width of the embedded player returned in the player.embedHtml property.
                Acceptable values are 72 to 8192, inclusive.
                If provide max_height at the same time. This will may be shorter than max_height.
                For more https://developers.google.com/youtube/v3/docs/videos/list#parameters.
            count (int, optional):
                The count will retrieve videos data.
                Default is 5.
                If provide this with None, will retrieve all videos.
            limit (int, optional):
                The maximum number of items each request retrieve.
                For videos, this should not be more than 50.
                Default is 5.
            page_token(str, optional):
                The token of the page of videos result to retrieve.
                You can use this retrieve point result page directly.
                And you should know about the the result set for YouTube.
            return_json(bool, optional):
                The return data type. If you set True JSON data will be returned.
                False will return a pyyoutube.VideoListResponse instance.
        Returns:
            VideoListResponse or original data
        """

        if self._access_token is None:
            raise PyYouTubeException(
                ErrorMessage(
                    status_code=ErrorCode.NEED_AUTHORIZATION,
                    message="This method can only used with authorization",
                )
            )

        if count is None:
            limit = 50  # for videos the max limit for per request is 50
        else:
            limit = min(count, limit)

        args = {
            "myRating": rating,
            "part": enf_parts(resource="videos", value=parts),
            "hl": hl,
            "maxResults": limit,
        }

        if max_height is not None:
            args["maxHeight"] = max_height
        if max_width is not None:
            args["maxWidth"] = max_width

        if page_token is not None:
            args["pageToken"] = page_token

        res_data = self.paged_by_page_token(resource="videos", args=args, count=count)
        if return_json:
            return res_data
        else:
            return VideoListResponse.from_dict(res_data)
