"""Utilities.
@Project   : genshinhelper
@Author    : y1ndan
@Blog      : https://www.yindan.me
@GitHub    : https://github.com/y1ndan
"""

import datetime
import gettext
import hashlib
import json
import logging
import os
import random
import string
import time
from urllib.parse import urlencode

import requests
from requests.utils import cookiejar_from_dict

from genshinhelper import config

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s %(levelname)s %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S')

log = logger = logging

_localedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'locale')
_translate = gettext.translation(
    'genshinhelper', _localedir, languages=[config.LANGUAGE], fallback=True)
_ = _translate.gettext

MESSAGE_TEMPLATE = _('''
    {today:#^18}
    🔅{nickname} {level} {region_name}
    Today's reward: {reward_name} × {reward_cnt}
    Total monthly check-ins: {total_sign_day} days
    Status: {status}
    {addons}
    {end:#^18}''')

DAIRY_TEMPLATE = _('''Traveler month {month} diary
    💠primogems: {current_primogems}
    🌕mora: {current_mora}''')

FINANCE_TEMPLATE = _('''Captain month {month} finance
    💎hcoin: {month_hcoin}
    🔮star: {month_star}''')


def set_lang(lang=None):
    if lang:
        os.environ['LANGUAGE'] = lang


def today():
    return datetime.date.today()


def month():
    return today().month


def get_mihoyo_app_cookie(cookie):
    if 'stoken' in cookie:
        return cookie

    cookie_dict = cookie_to_dict(cookie)
    stuid = cookie_dict['account_id']
    login_ticket = cookie_dict['login_ticket']
    url = 'https://api-takumi.mihoyo.com/auth/api/getMultiTokenByLoginTicket?uid={}&login_ticket={}&token_types=3'.format(stuid, login_ticket)
    response = request('get', url).json()
    stoken = nested_lookup(response, 'token')
    app_cookie = f'stuid={stuid}; stoken={stoken}; login_ticket={login_ticket}'
    return app_cookie if stoken else response


def minutes_to_hours(minutes):
    minutes = int(minutes)
    if minutes < 0:
        raise ValueError('Input number cannot be negative')

    return {'hour': int(minutes / 60), 'minute': minutes % 60}


def get_cookies(cookies: str = None):
    if '#' in cookies:
        return cookies.split('#')
    elif isinstance(cookies, list):
        return cookies
    elif '{' in cookies:
        return json.loads(cookies)
    else:
        return cookies.splitlines()


def extract_cookie(name: str, cookie: str):
    if name not in cookie:
        raise Exception(
            _('Failed to extract cookie: The cookie does not contain the `{name}` field.').format(
                name=name
            )
        )
    return cookie.split(f'{name}=')[1].split(';')[0]


def cookie_to_dict(cookie):
    if cookie and '=' in cookie:
        cookie = dict([line.strip().split('=', 1) for line in cookie.split(';')])
    return cookie


def merge_dicts(*dict_args):
    result = {}
    for d in dict_args:
        result.update(d)
    return result


def extract_subset_of_dict(raw_dict, keys):
    subset = {}
    if isinstance(raw_dict, dict):
        subset = {key: value for key, value in raw_dict.items() if key in keys}
    return subset


def nested_lookup(obj, key, with_keys=False, fetch_first=False):
    result = list(_nested_lookup(obj, key, with_keys=with_keys))
    if with_keys:
        values = [v for k, v in _nested_lookup(obj, key, with_keys=with_keys)]
        result = {key: values}
    if fetch_first:
        result = result[0] if result else result
    return result


def _nested_lookup(obj, key, with_keys=False):
    if isinstance(obj, list):
        for i in obj:
            yield from _nested_lookup(i, key, with_keys=with_keys)

    if isinstance(obj, dict):
        for k, v in obj.items():
            if key == k:
                if with_keys:
                    yield k, v
                else:
                    yield v

            if isinstance(v, list) or isinstance(v, dict):
                yield from _nested_lookup(v, key, with_keys=with_keys)


def get_ds(ds_type: str = None, new_ds: bool = False, data: dict = None, params: dict = None):
    # 1:  ios
    # 2:  android
    # 4:  pc web
    # 5:  mobile web
    def new():
        t = str(int(time.time()))
        r = str(random.randint(100000, 200000))
        b = json.dumps(data) if data else ''
        q = urlencode(params) if params else ''
        c = _hexdigest(f'salt={salt}&t={t}&r={r}&b={b}&q={q}')
        return f'{t},{r},{c}'

    def old():
        t = str(int(time.time()))
        r = ''.join(random.sample(string.ascii_lowercase + string.digits, 6))
        c = _hexdigest(f'salt={salt}&t={t}&r={r}')
        return f'{t},{r},{c}'

    app_version = '2.3.0'
    client_type = '5'
    salt = 'h8w582wxwgqvahcdkpvdhbh2w9casgfl'
    ds = old()
    if ds_type == '2' or ds_type == 'android':
        app_version = '2.8.0'
        client_type = '2'
        salt = 'dmq2p7ka6nsu0d3ev6nex4k1ndzrnfiy'
        ds = old()
    if new_ds:
        app_version = '2.11.1'
        client_type = '5'
        salt = 'xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs'
        ds = new()

    return app_version, client_type, ds


def _hexdigest(text):
    md5 = hashlib.md5()
    md5.update(text.encode())
    return md5.hexdigest()


def request(*args, **kwargs):
    is_retry = True
    count = 0
    max_retries = 3
    sleep_seconds = 5
    while is_retry and count <= max_retries:
        try:
            s = requests.Session()
            response = s.request(*args, **kwargs)
            is_retry = False
        except Exception as e:
            if count == max_retries:
                raise e
            log.error(_('Request failed: {}').format(e))
            count += 1
            log.info(
                _('Trying to reconnect in {} seconds ({}/{})...').format(
                    sleep_seconds, count, max_retries))
            time.sleep(sleep_seconds)
        else:
            return response

