import math

import aiomysql
from cmyui.osu.oppai_ng import OppaiWrapper
from peace_performance_python.objects import Beatmap as PeaceMap
from peace_performance_python.objects import Calculator as PeaceCalculator

from constants.gamemodes import GameMode
from constants.mods import Mods
from objects import glob


def simple_calculate(mode_vn, nmiss, combo, acc, mods, osu_file_path, score) -> (float, float):
    if nmiss is None:
        nmiss = 0
    if acc is None:
        acc = 100.0
    if mods is None:
        mods = Mods.NOMOD
    # v2 makes no influence
    if mods & Mods.SCOREV2:
        mods &= ~ Mods.SCOREV2
    if mode_vn == 0:  # std
        with OppaiWrapper("oppai-ng/liboppai.so") as ezpp:
            if mods:
                ezpp.set_mods(int(mods))

            if mode_vn:
                ezpp.set_mode(mode_vn)

            ezpp.set_combo(combo)
            ezpp.set_nmiss(nmiss)  # clobbers acc
            ezpp.set_accuracy_percent(acc)

            ezpp.calculate(osu_file_path)

            pp = ezpp.get_pp()

            if math.isnan(pp) or math.isinf(pp):
                # TODO: report to logserver
                return (0.0, 0.0)

            return (round(pp, 5), ezpp.get_sr())
    elif mode_vn in (1, 2):  # taiko, catch
        beatmap = PeaceMap(osu_file_path)
        peace = PeaceCalculator()

        if mods != Mods.NOMOD:
            peace.set_mods(int(mods))

        if mode_vn:
            peace.set_mode(mode_vn)

        peace.set_combo(combo)
        peace.set_miss(nmiss)
        peace.set_acc(acc)

        calculated = peace.calculate(beatmap)

        if math.isnan(calculated.pp) or math.isinf(calculated.pp):
            # TODO: report to logserver
            return (0.0, 0.0)

        return (round(calculated.pp, 5), calculated.stars)
    elif mode_vn == 3:  # mania
        beatmap = PeaceMap(osu_file_path)
        peace = PeaceCalculator()

        if mods != Mods.NOMOD:
            peace.set_mods(int(mods))

        if mode_vn:
            peace.set_mode(mode_vn)
        try:
            peace.set_score(score)
        finally:
            calculated = peace.calculate(beatmap)

            if math.isnan(calculated.pp) or math.isinf(calculated.pp):
                # TODO: report to logserver
                return (0.0, 0.0)

            return (round(calculated.pp, 5), calculated.stars)
    else:
        raise ValueError(f"Invalid vanilla mode {mode_vn}")


async def recalc_player(scores_table: str, playerid: int, mode: GameMode):
    async with glob.db.pool.acquire() as conn:
        async with conn.cursor(aiomysql.DictCursor) as db_cursor:
            await db_cursor.execute(
                f'SELECT s.pp, s.acc FROM {scores_table} s '
                'INNER JOIN maps m ON s.map_md5 = m.md5 '
                'WHERE s.userid = %s AND s.mode = %s '
                'AND s.status = 2 AND m.status = 2 '  # only ranked
                'ORDER BY s.pp DESC',
                [playerid, mode.as_vanilla]
            )
            total_scores = db_cursor.rowcount
            top_100_pp = await db_cursor.fetchmany(100)

            # update total weighted accuracy
            tot = div = 0
            for i, row in enumerate(top_100_pp):
                add = int((0.95 ** i) * 100)
                tot += row['acc'] * add
                div += add
            acc = tot / (1 if div == 0 else div)

            # update total weighted pp
            weighted_pp = sum([row['pp'] * 0.95 ** i
                               for i, row in enumerate(top_100_pp)])
            bonus_pp = 416.6667 * (1 - 0.9994 ** total_scores)
            pp = round(weighted_pp + bonus_pp)
            await db_cursor.execute(
                f'UPDATE stats SET pp = %s, acc = %s '
                'WHERE id = %s AND mode = %s',
                [pp, acc, playerid, mode]
            )


async def recalc_player_allmode(playerid: int):
    for mode in range(8):
        gm = GameMode(mode)
        await recalc_player(gm.scores_table, playerid, gm)
