import hashlib

from typing import List

import requests

from mangaplus.constants import Language, Viewer, Quality, Ranking, PageType, TitleType
from mangaplus.utils import bool2str, proto2dict


class MangaPlus:
    """Python client for interacting with the MangaPlus API."""

    # @package jp.co.shueisha.mangaplus.api.ApiFactory;
    APP_VERSION = 222

    def __init__(
            self,
            lang: Language = Language.ENGLISH,
            clang: List[Language] = None,
            viewer: Viewer = Viewer.VERTICAL
    ):
        """
        Initialize the client.

        Args:
            lang (Language): Main language for API responses.
            clang (List[Language]): Additional languages to include.
            viewer (Viewer): Viewer mode (horizontal or vertical).
        """
        # @package jp.co.shueisha.mangaplus.d;
        self.lang = lang
        self.clang = clang or [Language.ENGLISH]
        self.viewer = viewer

        # @package jp.co.shueisha.mangaplus.Constants;
        self.api = 'https://jumpg-api.tokyo-cdn.com/api'
        self.secret = None

    def __request(self, **kwargs) -> dict:
        """
        Internal helper for sending HTTP requests to the MangaPlus API.
        Handles headers, parameters, and error parsing.

        Returns:
            dict: API response data.

        Raises:
            Exception: If API returns an error message.
        """
        headers = kwargs.get('headers') or {
            'Accept-Encoding': 'gzip',
            'Connection': 'Keep-Alive',
            'Host': 'jumpg-api.tokyo-cdn.com',
            'User-Agent': 'okhttp/4.12.0'
        }

        params = {
            **kwargs.get('params', {}),
            'os': 'android',
            'os_ver': 29,
            'app_ver': self.APP_VERSION
        }

        if self.secret:
            params['secret'] = self.secret

        r = requests.request(
            method=kwargs.get('method', 'GET'),
            url=kwargs['url'],
            params=params,
            headers=headers
        )
        r.raise_for_status()

        # @package jp.co.comic.jump.proto;
        data = proto2dict(r.content)
        error = data.get('error')
        if error:
            code = error['englishPopup']['subject'].replace(' ', '')
            message = error['englishPopup']['body']
            raise type(code, (Exception,), {})(message)
        else:
            return data['success']

    @property
    def __clang(self) -> str:
        """Return additional languages as a comma-separated string."""
        assert isinstance(self.clang, list), f'Expected self.clang to be a list, got {type(self.clang).__name__}'
        assert all(isinstance(l, Language) for l in self.clang), 'All elements must be instances of Language'
        return ','.join([l.value for l in self.clang])

    # Generated by https://gist.github.com/hyugogirubato/e4e602f2d135622fa5a3f889c0726ce3
    # @package jp.co.shueisha.mangaplus.api.Api;
    def addFavorited(self, title_id: int) -> dict:
        return self.__request(
            method='PUT',
            url=f'{self.api}/title_list/bookmark',
            params={'title_id': title_id})

    def banComment(self, comment_id: int) -> dict:
        return self.__request(
            method='DELETE',
            url=f'{self.api}/comment',
            params={'comment_id': comment_id})

    def deleteFavorite(self, title_id: int) -> dict:
        return self.__request(
            method='DELETE',
            url=f'{self.api}/title_list/bookmark',
            params={'title_id': title_id})

    def deletePushToken(self) -> dict:
        return self.__request(
            method='DELETE',
            url=f'{self.api}/push_token')

    def favorite(self) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/title_list/bookmark')

    def getAdTapLog(self, chapter_id: int, token: str, tap_type: str) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/ad_tap_log',
            params={'chapter_id': chapter_id, 'token': token, 'type': tap_type})

    def getAllTitlesV3(
            self,
            title_type: TitleType = TitleType.SERIALIZING,
            # tag: str
    ) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/title_list/all_v3',
            params={
                'type': title_type.value,
                'lang': self.lang.value,
                'clang': self.__clang,
                # 'tag': tag
            })

    def getComments(self, chapter_id: int) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/comment',
            params={'chapter_id': chapter_id})

    def getDownloadableImages(self) -> dict:
        # @Unchecked
        return self.__request(
            method='GET',
            url=f'{self.api}/downloadable_images')

    def getFavoriteTitles(self) -> dict:
        # @Unchecked
        return self.__request(
            method='GET',
            url=f'{self.api}/title_list/favorite_titles')

    def getFirstSubscriptionView(self) -> dict:
        # @Unchecked
        return self.__request(
            method='GET',
            url=f'{self.api}/introduce_subscription',
            params={'clang': self.__clang})

    def getFreeTitles(self) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/title_list/free_titles',
            params={'clang': self.__clang})

    def getHistory(self) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/title_list/history',
            params={'lang': self.lang.value, 'clang': self.__clang})

    def getMPCStatus(self) -> dict:
        # @Unchecked
        return self.__request(
            method='GET',
            url=f'{self.api}/mpc_status',
            params={'lang': self.lang.value})

    def getMangaData(
            self,
            chapter_id: int,
            split: bool = True,
            quality: Quality = Quality.SUPER_HIGH,
            ticket: bool = False,
            free: bool = False,
            subscription: bool = False
    ) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/manga_viewer',
            params={
                'chapter_id': chapter_id,
                'split': bool2str(split),
                'img_quality': quality.value,
                'ticket_reading': bool2str(ticket),
                'free_reading': bool2str(free),
                'subscription_reading': bool2str(subscription),
                'viewer_mode': self.viewer.value,
                'clang': self.__clang
            })

    def getPublisherNewsList(self, publisher_id: int) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/publisher_news_list',
            params={'publisher_id': publisher_id, 'lang': self.lang.value})

    def getRankingV2(self, ranking: Ranking = Ranking.HOTTEST) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/title_list/rankingV2',
            params={'lang': self.lang.value, 'type': ranking.value, 'clang': self.__clang})

    def getSearchTitles(self) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/title_list/search',
            params={'lang': self.lang.value, 'clang': self.__clang})

    def getSettings(self) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/settings_v2',
            params={'lang': self.lang.value, 'viewer_mode': self.viewer.value, 'clang': self.__clang})

    def getSubscriptionView(self) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/subscription')

    def getTicketTitles(self) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/title_list/ticket_titles')

    def getTitleDetail(self, title_id: int) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/title_detailV3',
            params={'title_id': title_id, 'lang': self.lang.value, 'clang': self.__clang})

    def getUpdates(self) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/home_v4',
            params={'lang': self.lang.value, 'viewer_mode': self.viewer.value, 'clang': self.__clang})

    def likeComment(self, comment_id: int) -> dict:
        return self.__request(
            method='PUT',
            url=f'{self.api}/comment_like',
            params={'comment_id': comment_id})

    def logBannerTap(self, banner_id: int) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/title_banner_tap_log',
            params={'banner_id': banner_id})

    def logTitleBannerTap(self, banner_id: int, title_id: int, banner_type: str, location: str, tap_type: str) -> dict:
        # @Unchecked
        return self.__request(
            method='GET',
            url=f'{self.api}/title_banner_tap_log',
            params={
                'banner_id': banner_id,
                'title_id': title_id,
                'banner_type': banner_type,
                'location': location,
                'type': tap_type
            })

    def logBrowseTab(self, tap_type: str) -> dict:
        # @Unchecked
        return self.__request(
            method='GET',
            url=f'{self.api}/browse_tab_tap_log',
            params={'tap_type': tap_type})

    def logDiscoverSegment(
            self,
            item_container_id: int,
            tap_type: str,
            chapter_id: int,
            translated_title_id: int
    ) -> dict:
        # @Unchecked
        return self.__request(
            method='GET',
            url=f'{self.api}/discover_segment_chapter_tap_log',
            params={
                'item_container_id': item_container_id,
                'tap_type': tap_type,
                'chapter_id': chapter_id,
                'translated_title_id': translated_title_id
            })

    def logDownloadImage(self, downloadable_image_id: int, access_type: str) -> dict:
        # @Unchecked
        return self.__request(
            method='GET',
            url=f'{self.api}/downloadable_image_tap_log',
            params={'downloadable_image_id': downloadable_image_id, 'access_type': access_type})

    def logInstallPageClick(
            self,
            button_click: str = 'done',
            page_type: PageType = PageType.SERVICE_AGREEMENT
    ) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/install_page_click_log',
            params={'button_click': button_click, 'page_type': page_type, 'clang': self.__clang})

    def logPopupTap(self, popup_id: str) -> dict:
        return self.__request(
            method='GET',
            url=f'{self.api}/popup_tap_log',
            params={'popup_id': popup_id})

    def postComment(self, chapter_id: int, body: str) -> dict:
        return self.__request(
            method='POST',
            url=f'{self.api}/comment',
            params={'chapter_id': chapter_id, 'body': body})

    def postPushToken(self, push_token: str) -> dict:
        return self.__request(
            method='POST',
            url=f'{self.api}/push_token',
            params={'push_token': push_token})

    def putFavoriteTitles(self, title_ids: str) -> dict:
        # @Unchecked
        return self.__request(
            method='PUT',
            url=f'{self.api}/title_list/favorite_titles',
            params={'title_ids': title_ids})

    def putLanguagePreferences(self, dlang: str) -> dict:
        # @Unchecked
        return self.__request(
            method='PUT',
            url=f'{self.api}/language_preferences',
            params={'lang': self.lang.value, 'clang': self.__clang, 'dlang': dlang})

    def register(self, device_id: str) -> dict:
        # package jp.co.shueisha.mangaplus.util.UtilKt;
        device_token = hashlib.md5(bytes(device_id, 'utf-8')).hexdigest()
        security_key = hashlib.md5(bytes(device_token + '4Kin9vGg', 'utf-8')).hexdigest()

        content = self.__request(
            method='PUT',
            url=f'{self.api}/register',
            params={'device_token': device_token, 'security_key': security_key})

        self.secret = content['registrationData']['deviceSecret']
        return content

    def setSubscription(self, purchase_data: str, signature: str) -> dict:
        return self.__request(
            method='PUT',
            url=f'{self.api}/subscription',
            params={'purchase_data': purchase_data, 'signature': signature})

    def setSubscriptionRestore(self, purchase_data: str, signature: str) -> dict:
        return self.__request(
            method='PUT',
            url=f'{self.api}/subscription_restore',
            params={'purchase_data': purchase_data, 'signature': signature})

    def unlikeComment(self, comment_id: int) -> dict:
        return self.__request(
            method='DELETE',
            url=f'{self.api}/comment_like',
            params={'comment_id': comment_id})


__all__ = ('MangaPlus',)
