import asyncio
from datetime import datetime
from decimal import Decimal, ROUND_DOWN

from public.times import Times
from init import Init

class Distribution(Init):
    def __init__(self, coin):
        method = "distribution"
        super().__init__(coin, method)

    # ------------------ 核心方法 ------------------

    def score(self, alluser_mhs24h, hash_percent=1):
        total_hashrate = sum(obj['mhs24h'] for obj in alluser_mhs24h)
        result = {}
        for item in alluser_mhs24h:
            user = item['user']
            mhs24h = item['mhs24h']
            result[user] = round((mhs24h / total_hashrate) * hash_percent, 4)
        return result

    async def query_last_day_hashrate(self, date):
        try:
            sql = f"SELECT user, SUM(mhs24h) AS mhs24h FROM {self.coin}_mhsv2 WHERE date = ? GROUP BY user;"
            data = await self.hashratedb.exec(sql, [date + " 00:00:00"])
            if not data:
                return False
            return [{'user': item['user'], 'mhs24h': float(item['mhs24h'])} for item in data]
        except Exception as err:
            raise err

    async def query_last_day_reward(self, start_time, end_time):
        try:
            sql = f"""
                SELECT MAX(height) AS max_height, SUM(reward) AS reward
                FROM {self.coin}_blkreportprofitv2
                WHERE date >= ? AND date < ? AND state = ?;
            """
            data = await self.distributiondb.exec(sql, [start_time, end_time, 1])
            if not data or not data[0]['reward']:
                return 0
            # 返回整数，完全对齐 JS BigInt
            data[0]['reward'] = int(data[0]['reward'])
            return data
        except Exception as err:
            raise err

    async def query_last_day_if_mature(self, start_time, end_time):
        try:
            sql = f"SELECT count(*) AS count FROM {self.coin}_blkreportprofitv2 WHERE date >= ? AND date < ? AND state = ?;"
            while True:
                await asyncio.sleep(60 * 15)
                data = await self.distributiondb.exec(sql, [start_time, end_time, 0])
                current_hour = int(Times.times()[4])
                if (self.coin == "rxd" and current_hour >= 9) or (self.coin != "rxd" and current_hour >= 4):
                    return False
                if data[0]['count'] == 0:
                    break
            return True
        except Exception as err:
            raise err

    async def query_users_address(self):
        try:
            sql = """
                SELECT
                    a.miner_user AS 'user',
                    b.balance AS 'address',
                    b.amount AS 'amount',
                    b.active AS 'state',
                    b.min_amount AS 'min_amount'
                FROM user_account_balance b
                LEFT JOIN user_miner_account a ON b.ma_id = a.id
                WHERE a.coin = ? AND b.status = 0;
            """
            data = await self.users_addresses.exec(sql, [self.coin])
            return data if data else False
        except Exception as err:
            raise err

    async def verify_block(self, height):
        try:
            data = await self.node.verify_block(height, self.REPORT_ADDRESS)
            if data and isinstance(data, dict):
                return await self.node.block(data)
            return False
        except Exception as err:
            raise err

    async def insert_blkreportprofit(self, data):
        try:
            sql = f"INSERT INTO {self.coin}_blkreportprofitv2 (date, height, hash, reward, fees, state) VALUES "
            values = []
            for item in data:
                sql += "(?,?,?,?,?,?), "
                values.extend([
                    Times.utcTime(item['time'] * 1000),
                    item['height'],
                    item['hash'],
                    item['block_reward'],
                    item['block_fees'],
                    1
                ])
            sql = sql[:-2]
            await self.distributiondb.exec_transaction(sql, values)
        except Exception as err:
            raise err

    async def check_last_data_blk(self, date):
        try:
            ts = int(datetime.fromisoformat(date).timestamp() * 1000) - 86400000
            yMd = Times.utcTime(ts).split(" ")[0]
            ymd = yMd.split("-")
            table_name = f"{self.coin}_pool_blkstats_{ymd[0]}{ymd[1]}{ymd[2]}"
            confirm_result = await self.pooldb.exec(f"SHOW TABLES LIKE '{table_name}';")
            if not confirm_result:
                print("pool_blkstats表未更新，退出本次执行，请手动校验")
                return False
            pool_data = await self.pooldb.exec(f"SELECT height FROM {table_name} WHERE DATE(date) >= ?;", [yMd])
            heights = [item['height'] for item in pool_data]
            blkreport_data = await self.distributiondb.exec(f"SELECT height FROM {self.coin}_blkreportprofitv2 WHERE DATE(date)=? AND state=?;", [yMd, 1])
            blkreport_heights = [item['height'] for item in blkreport_data]
            need_check_heights = [h for h in heights if h not in set(blkreport_heights)]
            if not need_check_heights:
                print(f"{self.coin}check 完成，没有需要重新校验的区块")
                return True
            need_insert_data = []
            for height in need_check_heights:
                result = await self.verify_block(height)
                if result:
                    need_insert_data.append(result)
            if need_insert_data:
                await self.insert_blkreportprofit(need_insert_data)
                print(f"{self.coin}check 完成，已将{self.coin}漏掉的报块全部插入blk表中！")
            else:
                print(f"{self.coin}check 完成，没有需要insert的区块")
            return True
        except Exception as err:
            raise err

    async def update_state(self, min_amount):
        try:
            data = await self.distributiondb.exec("SELECT user, SUM(amount) AS profit FROM wallet_in WHERE coin=? AND state=? GROUP BY user;", [self.coin, 2])
            if not data:
                return
            for item in data:
                user = item['user']
                if item['profit'] >= min_amount[user]:
                    await self.distributiondb.exec("UPDATE wallet_in SET state=? WHERE coin=? AND user=?", [0, self.coin, user])
        except Exception as err:
            raise err

    async def insert_wallet_in(self, data):
        try:
            sql = "INSERT INTO wallet_in(coin, user, address, create_date, should_out_date, max_height, amount, state) VALUES "
            values = []
            for item in data:
                sql += "(?,?,?,?,?,?,?,?), "
                values.extend([
                    item['coin'],
                    item['user'],
                    item['address'],
                    item['create_date'],
                    item['should_out_date'],
                    item['max_height'],
                    item['amount'],
                    item['state']
                ])
            sql = sql[:-2]
            await self.distributiondb.exec_transaction(sql, values)
        except Exception as err:
            raise err

    async def main(self, start_time, end_time):
        try:
            if not await self.query_last_day_if_mature(start_time, end_time):
                return
            if not await self.check_last_data_blk(end_time):
                return

            last_day_mhs24h, last_day_reward, users_address = await asyncio.gather(
                self.query_last_day_hashrate(end_time),
                self.query_last_day_reward(start_time, end_time),
                self.query_users_address()
            )
            if not last_day_mhs24h or not last_day_reward or not users_address:
                print("查询错误", last_day_mhs24h, last_day_reward, users_address)
                return

            reward = (int(last_day_reward[0]['reward']) * int((1 - self.POOL_FEE) * 10000)) // 10000
            score_ratio = self.score(last_day_mhs24h, 1)
            max_height = last_day_reward[0]['max_height']

            should_out_date = end_time
            accuracy = 100000000
            count = 8
            if self.coin == "nexa":
                should_out_date = Times.utcTime(int(datetime.fromisoformat(end_time).timestamp() * 1000) + 1000 * 60 * 60 * 24 * 7)
                accuracy = 100
                count = 2
            elif self.coin == "rxd":
                accuracy = 100
                count = 2
            elif self.coin == "alph":
                accuracy = 0
                count = 0

            user_profit = 0
            result = []
            pool_account_address = None
            min_amount = {}

            for user, ratio in score_ratio.items():
                ratio_int = round(ratio * 10000)
                profit = reward * ratio_int // 10000
                if profit == 0:
                    continue
                user_profit += profit
                for item in users_address:
                    if item['user'] == "pool_account":
                        pool_account_address = item['address']
                    if item['user'] == user:
                        min_amount[user] = item['min_amount']
                        state = 0
                        if profit >= item['amount'] and item['state'] == 0:
                            state = 0
                        elif profit < item['amount'] and item['state'] == 0:
                            state = 2
                        elif profit >= item['amount'] and item['state'] == 1:
                            state = 3
                        else:
                            state = 4
                        result.append({
                            'coin': self.coin,
                            'user': user,
                            'address': item['address'],
                            'create_date': end_time,
                            'should_out_date': should_out_date,
                            'max_height': max_height,
                            'amount': profit,
                            'state': state
                        })

            pool_account_amount = Decimal(last_day_reward[0]['reward']) - Decimal(user_profit)
            pool_account_amount = pool_account_amount.quantize(Decimal(f'1e-{count}'), rounding=ROUND_DOWN)
            result.append({
                'coin': self.coin,
                'user': "pool_account",
                'address': pool_account_address,
                'create_date': end_time,
                'should_out_date': should_out_date,
                'max_height': max_height,
                'amount': float(pool_account_amount),
                'state': 0
            })

            print(result)
            await self.insert_wallet_in(result)
            await self.update_state(min_amount)
        except Exception as err:
            raise err

__all__ = ['Distribution']