import asyncio
from datetime import datetime
from decimal import Decimal
import time

from public.times import Times
from init import Init

class HashRate(Init):
    def __init__(self, coin):
        method = "hashrate"
        super().__init__(coin, method)
        self.diff_one_share_hashs_avg = 2 ** 32 - 1
        self.count = 0

    def calculate_hashrate(self, accepts, seconds, unit):
        num_map = {
            "H/s": 1,
            "KH/s": 1_000,
            "MH/s": 1_000_000,
            "GH/s": 1_000_000_000,
            "TH/s": 1_000_000_000_000,
            "PH/s": 1_000_000_000_000_000,
            "EH/s": 10 ** 18
        }
        if unit not in num_map:
            raise ValueError(f"{unit}不是已知单位")
        num = Decimal(num_map[unit])
        hashrate = (Decimal(accepts) * Decimal(self.diff_one_share_hashs_avg)) / Decimal(seconds) / num
        if self.coin == "alph":
            return hashrate * 4
        return hashrate

    def merge(self, data):
        results = {}
        for item in data:
            key = f"{item['user']}-{item['miner']}"
            if key in results:
                existing = results[key]
                existing['accepts'] += float(item['accepts'])
                if datetime.fromisoformat(item['last_submit']) > datetime.fromisoformat(existing['last_submit']):
                    existing['last_submit'] = item['last_submit']
                results[key] = existing
            else:
                results[key] = {
                    'user': item['user'],
                    'miner': item['miner'],
                    'accepts': float(item['accepts']),
                    'last_submit': item['last_submit']
                }
        return list(results.values())

    async def query_table(self, start_time, end_time):
        try:
            sql = f"(SELECT date, `from`, `to` FROM {self.coin}_blk_height_detail WHERE date >= ? ORDER BY date LIMIT 1) " \
                  f"UNION (SELECT date, `from`, `to` FROM {self.coin}_blk_height_detail WHERE date >= ? AND date < ?) ORDER BY date;"
            data = await self.sharesdb.exec(sql, [end_time, start_time, end_time])
            result = [f"{self.coin}_block_detail_{item['from']}_{int(item['to'] - 1)}" for item in data] if data else []
            result.append(f"{self.coin}_blk_detail")
            return result
        except Exception as err:
            print(err)
            return []

    async def query_slave_table(self, start_time, end_time):
        try:
            sql = f"(SELECT date, `from`, `to` FROM {self.coin}_blk_height_detail WHERE date >= ? ORDER BY date LIMIT 1) " \
                  f"UNION (SELECT date, `from`, `to` FROM {self.coin}_blk_height_detail WHERE date >= ? AND date < ?) ORDER BY date;"
            data = await self.sharesdb_slave.exec(sql, [end_time, start_time, end_time])
            result = [f"{self.coin}_block_detail_{item['from']}_{int(item['to'] - 1)}" for item in data] if data else []
            result.append(f"{self.coin}_blk_detail")
            return result
        except Exception as err:
            print(err)
            return []

    async def query_accepts(self, start_time, end_time, enable):
        try:
            if enable:
                tables_name, slave_tables_name = await asyncio.gather(
                    self.query_table(start_time, end_time),
                    self.query_slave_table(start_time, end_time)
                )

                def build_sql(table_list):
                    if len(table_list) <= 1:
                        return f'SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts ' \
                               f'FROM {self.coin}_blk_detail WHERE date >= "{start_time}" AND date < "{end_time}" GROUP BY user, miner;'
                    sql = 'SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ( '
                    for i, tbl in enumerate(table_list):
                        if i < len(table_list) - 1:
                            sql += f'SELECT date, user, miner, miner_diff, pool_diff FROM {tbl} WHERE date >= "{start_time}" AND date < "{end_time}" \nUNION ALL\n'
                        else:
                            sql += f'SELECT date, user, miner, miner_diff, pool_diff FROM {tbl} WHERE date >= "{start_time}" AND date < "{end_time}") AS combined_tables GROUP BY user, miner;'
                    return sql

                sql = build_sql(tables_name)
                slave_sql = build_sql(slave_tables_name)

                accepts_data, slave_accepts = await asyncio.gather(
                    self.sharesdb.exec(sql),
                    self.sharesdb_slave.exec(slave_sql)
                )
                return self.merge(accepts_data + slave_accepts)
            else:
                tables_name = await self.query_table(start_time, end_time)
                sql = 'SELECT MAX(date) AS last_submit, user, miner, SUM(miner_diff) AS accepts FROM ( '
                for i, tbl in enumerate(tables_name):
                    if i < len(tables_name) - 1:
                        sql += f'SELECT date, user, miner, miner_diff, pool_diff FROM {tbl} WHERE date >= "{start_time}" AND date < "{end_time}" \nUNION ALL\n'
                    else:
                        sql += f'SELECT date, user, miner, miner_diff, pool_diff FROM {tbl} WHERE date >= "{start_time}" AND date < "{end_time}") AS combined_tables GROUP BY user, miner;'
                accepts_data = await self.sharesdb.exec(sql)
                return self.merge(accepts_data)
        except Exception as err:
            print(f"Retry {self.count} for query_accepts due to {err}")
            await asyncio.sleep(15)
            if self.count > 3:
                self.count = 0
                raise err
            self.count += 1
            return await self.query_accepts(start_time, end_time, enable)

    async def query_miners(self, time_str):
        try:
            sql = f"SELECT date, user, miner, state, ratio, last_submit FROM {self.coin}_miners WHERE last_submit >= DATE_SUB(?, INTERVAL 1 DAY);"
            return await self.pooldb.exec(sql, [time_str])
        except Exception as err:
            raise err

    async def insert_mhs(self, data):
        if not data:
            print(int(time.time() * 1000), "：30分钟没有新增矿机提交数据")
            return
        sql = f"INSERT INTO {self.coin}_mhsv2 (user, miner, date, mhs30m, mhs24h, state, last_submit) VALUES "
        values = []
        for item in data:
            sql += "(?, ?, ?, ?, ?, ?, ?), "
            values.extend([item['user'], item['miner'], item['date'], item['mhs30m'], item['mhs24h'], item['state'], item['last_submit']])
        sql = sql[:-2]
        await self.hashratedb.exec_transaction(sql, values)

    async def insert_mhs_real(self, data):
        if not data:
            print(int(time.time() * 1000), "：5分钟没有新增矿机提交数据")
            return
        del_sql = f"DELETE FROM {self.coin}_mhs_realv2 WHERE id > 0;"
        sql = f"INSERT INTO {self.coin}_mhs_realv2 (user, miner, date, mhs30m, mhs24h, state, last_submit) VALUES "
        values = []
        for item in data:
            sql += "(?, ?, ?, ?, ?, ?, ?), "
            values.extend([item['user'], item['miner'], item['date'], item['mhs30m'], item['mhs24h'], item['state'], item['last_submit']])
        sql = sql[:-2]
        sqls = [{"sql": del_sql}, {"sql": sql, "param": values}]
        await self.hashratedb.exec_transaction_together(sqls)

    async def query_hashrate_miners_accepts(self, end_time):
        ymd_last_30m = Times.utcTime(int(datetime.fromisoformat(end_time).timestamp() * 1000) - 1000 * 60 * 30)
        ymd_last_24h = Times.utcTime(int(datetime.fromisoformat(end_time).timestamp() * 1000) - 1000 * 60 * 60 * 24)
        state_sql = f"""SELECT t1.*
FROM {self.coin}_minersv2 t1
INNER JOIN (
    SELECT user, miner, MAX(date) AS max_date
    FROM {self.coin}_minersv2
    WHERE date <= ?
    GROUP BY user, miner
) t2
ON t1.user = t2.user AND t1.miner = t2.miner AND t1.date = t2.max_date;"""
        mhs30m_sql = f"SELECT SUM(accepts) AS accepts_30min, user, miner FROM {self.coin}_minersv2 WHERE date >= ? AND date < ? GROUP BY user, miner;"
        mhs24h_sql = f"SELECT SUM(accepts) AS accepts_24h, user, miner FROM {self.coin}_minersv2 WHERE date >= ? AND date < ? GROUP BY user, miner;"

        state, mhs30m, mhs24h = await asyncio.gather(
            self.hashratedb.exec(state_sql, [end_time]),
            self.hashratedb.exec(mhs30m_sql, [ymd_last_30m, end_time]),
            self.hashratedb.exec(mhs24h_sql, [ymd_last_24h, end_time])
        )

        hashrate_map = {}
        for item in state:
            hashrate_map[f"{item['user']}:{item['miner']}"] = {
                'date': end_time,
                'user': item['user'],
                'miner': item['miner'],
                'state': item['state'],
                'last_submit': item['last_submit'],
                'mhs30m': 0,
                'mhs24h': 0
            }
        for item in mhs30m:
            key = f"{item['user']}:{item['miner']}"
            if key in hashrate_map:
                hashrate_map[key]['mhs30m'] = self.calculate_hashrate(item['accepts_30min'], 60*30, "MH/s")
        for item in mhs24h:
            key = f"{item['user']}:{item['miner']}"
            if key in hashrate_map:
                hashrate_map[key]['mhs24h'] = self.calculate_hashrate(item['accepts_24h'], 60*60*24, "MH/s")
        return hashrate_map

    async def insert_hashrate_miners_table(self, end_time):
        ymd = end_time.split(":")
        date = f"{ymd[0]}:{ymd[1]}:00"
        start_time = Times.utcTime(int(datetime.fromisoformat(end_time).timestamp() * 1000) - 1000*60*5)
        enable = (await self.redis.get(f"{self.coin}:enable")) or False

        accepts, miners_state = await asyncio.gather(
            self.query_accepts(start_time, end_time, enable),
            self.query_miners(end_time)
        )

        miners_map = {}
        if not accepts and not miners_state:
            return
        elif accepts and not miners_state:
            return
        elif not accepts and miners_state:
            for item in miners_state:
                miners_map[f"{item['user']}:{item['miner']}"] = {
                    'date': date,
                    'user': item['user'],
                    'miner': item['miner'],
                    'accepts': 0,
                    'state': "offline",
                    'last_submit': item['last_submit']
                }
        else:
            for item in accepts:
                miners_map[f"{item['user']}:{item['miner']}"] = {
                    'date': date,
                    'user': item['user'],
                    'miner': item['miner'],
                    'accepts': item['accepts'],
                    'last_submit': item['last_submit'],
                    'state': "online"
                }
            for item in miners_state:
                key = f"{item['user']}:{item['miner']}"
                if key not in miners_map:
                    miners_map[key] = {
                        'date': date,
                        'user': item['user'],
                        'miner': item['miner'],
                        'accepts': 0,
                        'state': item['state'],
                        'last_submit': item['last_submit']
                    }

        insert_sql = f"INSERT INTO {self.coin}_minersv2(user, miner, date, accepts, state, last_submit) VALUES "
        values = []
        for item in miners_map.values():
            insert_sql += "(?, ?, ?, ?, ?, ?), "
            values.extend([item['user'], item['miner'], item['date'], item['accepts'], item['state'], item['last_submit']])
        insert_sql = insert_sql[:-2]
        await self.hashratedb.exec_transaction(insert_sql, values)


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

    async def query_blk_detail_table_name(self):
        try:
            sql = f"SELECT `from`, `to` FROM {self.coin}_blk_height_detail WHERE date < NOW() - INTERVAL 5 MINUTE;"
            data = await self.sharesdb.exec(sql)
            tables = [f"{self.coin}_blk_detail"]
            if data:
                for item in data:
                    tables.append(f"{self.coin}_block_detail_{item['from']}_{int(item['to'] - 1)}")
            return tables
        except Exception as err:
            raise err

    async def query_blk_detail(self):
        try:
            pass 
        except Exception as err:
            raise err

__all__ = ['HashRate', 'HashRateNew']