import dataclasses as dc
import json
from typing import List, Optional

import requests


class DataclassJSONEncoder(json.JSONEncoder):
    def default(self, o):
        if dc.is_dataclass(o):
            return dc.asdict(o)
        return super().default(o)


@dc.dataclass
class WebsiteAuthData(object):
    cookie_jar: requests.cookies.RequestsCookieJar
    access_token: str
    region: str


@dc.dataclass(frozen=True)
class BlizzardGame:
    uid: str
    name: str
    family: str


@dc.dataclass(frozen=True)
class ClassicGame(BlizzardGame):
    registry_path: Optional[str] = None
    registry_installation_key: Optional[str] = None
    exe: Optional[str] = None
    bundle_id: Optional[str] = None


@dc.dataclass
class RegionalGameInfo:
    uid: str
    try_for_free: bool


@dc.dataclass
class ConfigGameInfo(object):
    uid: str
    uninstall_tag: Optional[str]
    last_played: Optional[str]


@dc.dataclass
class ProductDbInfo(object):
    uninstall_tag: str
    ngdp: str = ''
    install_path: str = ''
    version: str = ''
    playable: bool = False
    installed: bool = False


class Singleton(type):
    _instances = {}  # type: ignore

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class _Blizzard(object, metaclass=Singleton):
    TITLE_ID_MAP = {
        21297: RegionalGameInfo('s1', True),
        21298: RegionalGameInfo('s2', True),
        5730135: RegionalGameInfo('wow', True),
        5272175: RegionalGameInfo('prometheus', False),
        22323: RegionalGameInfo('w3', False),
        1146311730: RegionalGameInfo('destiny2', False),
        1465140039: RegionalGameInfo('hs_beta', True),
        1214607983: RegionalGameInfo('heroes', True),
        17459: RegionalGameInfo('diablo3', True),
        4613486: RegionalGameInfo('fenris', True),
        1447645266: RegionalGameInfo('viper', False),
        1329875278: RegionalGameInfo('odin', True),
        1279351378: RegionalGameInfo('lazarus', False),
        1514493267: RegionalGameInfo('zeus', False),
        1381257807: RegionalGameInfo('rtro', False),
        1464615513: RegionalGameInfo('wlby', False),
        5198665: RegionalGameInfo('osi', False),
        1179603525: RegionalGameInfo('fore', False)
    }
    TITLE_ID_MAP_CN = {
        **TITLE_ID_MAP,
        17459: RegionalGameInfo('d3cn', False)
    }
    BATTLENET_GAMES = [
        BlizzardGame('s1', 'StarCraft', 'S1'),
        BlizzardGame('s2', 'StarCraft II', 'S2'),
        BlizzardGame('wow', 'World of Warcraft', 'WoW'),
        BlizzardGame('wow_classic', 'World of Warcraft Classic', 'WoW_wow_classic'),
        BlizzardGame('prometheus', 'Overwatch', 'Pro'),
        BlizzardGame('w3', 'Warcraft III', 'W3'),
        BlizzardGame('hs_beta', 'Hearthstone', 'WTCG'),
        BlizzardGame('heroes', 'Heroes of the Storm', 'Hero'),
        BlizzardGame('d3cn', '暗黑破壞神III', 'D3CN'),
        BlizzardGame('diablo3', 'Diablo III', 'D3'),
        BlizzardGame('fenris', 'Diablo IV', 'FEN'),
        BlizzardGame('viper', 'Call of Duty: Black Ops 4', 'VIPR'),
        BlizzardGame('odin', 'Call of Duty: Modern Warfare', 'ODIN'),
        BlizzardGame('lazarus', 'Call of Duty: MW2 Campaign Remastered', 'LAZR'),
        BlizzardGame('zeus', 'Call of Duty: Black Ops Cold War', 'ZEUS'),
        BlizzardGame('rtro', 'Blizzard Arcade Collection', 'RTRO'),
        BlizzardGame('wlby', 'Crash Bandicoot 4: It\'s About Time', 'WLBY'),
        BlizzardGame('osi', 'Diablo® II: Resurrected', 'OSI'),
        BlizzardGame('fore', 'Call of Duty: Vanguard', 'FORE')
    ]
    CLASSIC_GAMES = [
        ClassicGame('d2', 'Diablo® II', 'Diablo II', 'Diablo II', 'DisplayIcon', "Game.exe", "com.blizzard.diabloii"),
        ClassicGame('d2LOD', 'Diablo® II: Lord of Destruction®', 'Diablo II'),  # TODO exe and bundleid
        ClassicGame('w3ROC', 'Warcraft® III: Reign of Chaos', 'Warcraft III', 'Warcraft III',
                    'InstallLocation', 'Warcraft III.exe', 'com.blizzard.WarcraftIII'),
        ClassicGame('w3tft', 'Warcraft® III: The Frozen Throne®', 'Warcraft III', 'Warcraft III',
                    'InstallLocation', 'Warcraft III.exe', 'com.blizzard.WarcraftIII'),
        ClassicGame('sca', 'StarCraft® Anthology', 'Starcraft', 'StarCraft')  # TODO exe and bundleid
    ]

    def __init__(self):
        self._games = {
            game.uid: game for game in self.BATTLENET_GAMES + self.CLASSIC_GAMES}

    def __getitem__(self, key: str) -> BlizzardGame:
        """
        :param key: str uid (eg. "prometheus")
        :returns: game by `key`
        """
        return self._games[key]

    def game_by_title_id(self, title_id: int, cn: bool) -> BlizzardGame:
        """
        :param cn: flag if china game definitions should be search though
        :raises KeyError: when unknown title_id for given region
        """
        if cn:
            regional_info = self.TITLE_ID_MAP_CN[title_id]
        else:
            regional_info = self.TITLE_ID_MAP[title_id]
        return self[regional_info.uid]

    def try_for_free_games(self, cn: bool) -> List[BlizzardGame]:
        """
        :param cn: flag if china game definitions should be search though
        """
        return [
            self[info.uid] for info
            in (self.TITLE_ID_MAP_CN if cn else self.TITLE_ID_MAP).values()
            if info.try_for_free
        ]


Blizzard = _Blizzard()
