from classes.Transaction import Tx
from classes.TxBlock import TxBlock
from helpers import UtilityHelper as utilityHelper
import time

MIN_MINING_TIME = 0
MAX_MINING_TIME = 20

def exploreBlocks(self):
    blocks = utilityHelper.loadFile("../data/ledger.dat")
    x = 0
    total_transactions = 0
    for block in blocks:
        x += 1
        total_transactions += len(block.data)
        print(f"---------------------------------------{x}-------------------------------------------")
        print(block)
        print(f"Block created: {time.strftime('%Y-%m-%d', time.localtime(block.date))}")
    print("---------------------------------------END-------------------------------------------")
    print(f"Total blocks: {x}")
    print(f"Total transactions: {total_transactions}")
    print("-----------------------------------------------------------------------------------")
    print("Select a number to view the block, keep empty to return.")
    user_input = input(">>: ")
    if user_input == "":
        utilityHelper.clearScreen()
        return

    try:
        user_input = int(user_input) -1
    except:
        utilityHelper.clearScreen()
        print(f"{utilityHelper.warningMessage('Wrong input, try again')}")
        return
    
    if user_input > len(blocks) - 1 or user_input < 0:
        utilityHelper.clearScreen()
        print(f"{utilityHelper.warningMessage('Wrong input, try again')}")
        return

    # print all info of the block
    utilityHelper.clearScreen()
    print(f"---------------------------------------{user_input +1}-------------------------------------------")
    print(f"Block created: {time.strftime('%Y-%m-%d', time.localtime(blocks[user_input].date))}")
    if blocks[user_input].metadata['validated']:
        print(f"Block validated: True")
    else:
        print(f"Block validated: False, {blocks[user_input].metadata['true_validations']} valid flags, {blocks[user_input].metadata['false_validations']} invalid flags.")
    print(f"Block id: {blocks[user_input].id}")
    print(f"Block hash: {blocks[user_input].blockHash}")
    print(f"Block nonce: {blocks[user_input].nonce}")
    print(f"Block miner: {blocks[user_input].metadata['miner']}")
    print(f"Block mined time: {blocks[user_input].time}")
    print(f"Block transactions: {len(blocks[user_input].data)}")
    print(f"Block previous hash: {blocks[user_input].previousHash}")
    print("---------------------------------------------------------------------------------------")
    print("0 -- Go back")
    print("1 -- Validate block")
    print("2 -- View transactions of current block")
    print("3 -- View another block")
    choice = input(">>: ")
    
    match choice:
        case "0":
            utilityHelper.clearScreen()
            return
        case "1":
            blocks[user_input].is_valid()
        case "2":
            utilityHelper.clearScreen()
            x = 0
            for transaction in blocks[user_input].data:
                x += 1
                if transaction.type == 1:
                    input_amount = 0
                    input_user = "Reward"
                    fee = 0
                else:
                    input_user = transaction.inputs[0][0]
                    input_amount = transaction.inputs[0][1]
                    fee = input_amount - transaction.outputs[0][1]
                print(f"---------------------------------------{x}-------------------------------------------")
                print(f"Transaction input: {input_amount}")
                print(f"Transaction output: {transaction.outputs[0][1]}")
                print(f"Transaction fees: {fee}")
                print(f"Transaction sender: \n{input_user}")
                print(f"Transaction recipient: \n{transaction.outputs[0][0]}")
            print("-----------------------------------------------------------------------------------")
        case "3":
            utilityHelper.clearScreen()
            exploreBlocks(self)
        case _:
            utilityHelper.clearScreen()
            print(f"{utilityHelper.warningMessage('Wrong input, try again')}")
            return

def createBlock(self):
        # add last block if its available
    try:
        lastBlock = utilityHelper.loadFile("../data/ledger.dat")
    except:
        lastBlock = []

    if lastBlock != []:
        lastBlock = lastBlock[-1]
    
        if lastBlock != None and time.time() - lastBlock.date < 180:
            print(f"{utilityHelper.bcolors.WARNING}You can only mine a block every 3 minutes. Time left: {int(180 - (time.time() - lastBlock.date))}{utilityHelper.bcolors.ENDC}")
            return False
        
        if lastBlock != None and lastBlock.metadata['validated'] == False:
            print(f"{utilityHelper.warningMessage('You can only mine a block if the last block is validated')}")
            return False
    
    transactions = Tx()
    transactions = utilityHelper.loadFile("../data/transaction_pool.dat")
    if len(transactions) < 5:
        print(f"{utilityHelper.warningMessage('You need atleast 5 transactions to mine a block')}")
        return False
    
    print("Select items out the pool to mine a block, 2 values already selected.")
    if input("Press enter to continue... (type b to return)") == "b":
        return False
    utilityHelper.clearScreen()
    x = 0
    fees = {}
    for transaction in transactions:
        if transaction.type == 1:
            fees[x] = 0
        else:
            fees[x] = transaction.inputs[0][1] - transaction.outputs[0][1]
        x+=1

    fees_list = list(fees.keys())
    selected_transactions = fees_list[:2]
    available_transactions = fees_list[2:]

    # print fees with values and keys if key is in available_transactions
    for key, value in fees.items():
        if key in available_transactions:
            print(f"{key} -- fee: {value}")

    # get user input
    print("Enter the numbers of the transactions you want to mine, seperated by a comma. (ex: 1,2,3)")
    user_input = input(">>: ")
    if user_input == "":
        utilityHelper.clearScreen()
        return False
    # seperate user input by comma, check if all the values are in available_transactions
    user_input = set(user_input.split(","))
    for i in user_input:
        try:
            if int(i) not in available_transactions:
                print(f"{utilityHelper.warningMessage('Wrong input, try again')}")
                return False
            selected_transactions.append(int(i))
        except:
            utilityHelper.clearScreen()
            print(f"{utilityHelper.warningMessage('Wrong input, try again')}")
            return False

    if len(selected_transactions) > 10:
        print(f"{utilityHelper.warningMessage('You can only select up to 10 transactions')}")
        return False
    
    if len(selected_transactions) < 5:
        print(f"{utilityHelper.warningMessage('You need atleast 5 transactions to mine a block')}")
        return False
    
    only_personal = True
    # check if the user selected more user id's in the selected list
    for i in selected_transactions[2:]:
        if transactions[i].type == 1 and transactions[i].outputs[0][0] != self.user.public_ser:
            only_personal = False

        elif transactions[i].type == 0 and transactions[i].inputs[0][0] != self.user.public_ser:
            only_personal = False

    if only_personal:
        print(f"{utilityHelper.warningMessage('You need atleast 1 transaction from another user')}")
        return False
    if lastBlock == []:
        lastBlock = None
    block = TxBlock(lastBlock)

    # create block
    for i in selected_transactions:
        if not transactions[i].is_valid():
            self.db.createLog(transactions[i].inputs[0][0], time.time(), f"Transaction of {transactions[i].outputs[0][1]} with {transactions[i].inputs[0][1] - transactions[i].outputs[0][1]} coins fee is declined")
            try:
                del transactions[i]
            except:
                return False
            for transaction in transactions:
                utilityHelper.addFile("../data/transaction_pool.dat", transaction)
            print(f"{utilityHelper.errorMessage('ERROR! Invalid transaction found. transaction removed from pool. Please restart mining a block...')}")
            return False
        block.addTx(transactions[i])

    
    start = time.time()
    nonce = block.find_nonce()
    elapsed = time.time() - start

    utilityHelper.clearScreen()
    if not block.good_nonce():
        print(f"{utilityHelper.errorMessage('ERROR! Bad nonce')}")
        return False

    print(f"{utilityHelper.successMessage('Success! Nonce is good!')}")
    print(f'Accepted Nonce = {str(nonce)}')

    print("elapsed time: " + str(elapsed) + " s.")
    if elapsed < MIN_MINING_TIME:
        print(f"{utilityHelper.errorMessage('Mining declined, too fast')}")
        return False
    elif elapsed > MAX_MINING_TIME:
        print(f"{utilityHelper.errorMessage('Mining declined, too Slow')}")
        return False
    
    # add block to blockchain
    if lastBlock == None:
        block.id = 0
    else:
        block.id = lastBlock.id + 1

    block.metadata['miner'] = self.user.public_ser
    block.metadata['validated'] = False
    block.metadata['validated_by'] = []
    block.metadata['true_validations'] = 0
    block.metadata['false_validations'] = 0

    block.time = elapsed
    block.nonce = nonce
    block.date = time.time()
    block.blockHash = block.computeHash()

    utilityHelper.addFile("../data/ledger.dat", block)
    utilityHelper.resetFile("../data/transaction_pool.dat")
    transaction_count = 0
    for transaction in transactions:
        if transaction_count not in selected_transactions:
            utilityHelper.addFile("../data/transaction_pool.dat", transaction)
        transaction_count += 1

    return True

def validateMinedBlock(self):
    blocks = utilityHelper.loadFile("../data/ledger.dat")
    
    if blocks == []:
        return False
    
    new_block = blocks[-1]
    
    if new_block.metadata['validated']:
        return False
    
    if new_block.metadata['miner'] == self.user.public_ser:
        return False
    
    if self.user.public_ser in new_block.metadata['validated_by']:
        return False
    
    block_status = new_block.is_valid()
    new_block.metadata['validated_by'].append(self.user.public_ser)
    if block_status:
        new_block.metadata['true_validations'] += 1


        if new_block.metadata['true_validations'] >= 3:
            new_block.metadata['validated'] = True
            # get fees from transactions in block
            fees = 0
            for transaction in new_block.data:
                if transaction.type != 1:
                    fees += transaction.inputs[0][1] - transaction.outputs[0][1]
                    self.db.createLog(transaction.inputs[0][0], time.time(), f"Transaction of {transaction.inputs[0][1]} is added in block id {new_block.id}")
                    self.db.createLog(transaction.outputs[0][0], time.time(), f"You have received new coins: {transaction.outputs[0][1]}, To see all info view block id {new_block.id}")

            new_reward = Tx()
            new_reward.createRewardTransaction(self.user.public_ser, self.user.private_ser, "MINE", fees)
            self.db.createLog(new_block.metadata['miner'], time.time(), f"Block id {new_block.id}, is validated!")
            utilityHelper.addFile("../data/transaction_pool.dat", new_reward)


        try:
            del blocks[-1]
        except:
            return False
        
        utilityHelper.resetFile("../data/ledger.dat")
        blocks.append(new_block)
        for block in blocks:
            utilityHelper.addFile("../data/ledger.dat", block)
        
        return True
    
    else:
        new_block.metadata['false_validations'] += 1
        if new_block.metadata['false_validations'] >= 3:
            # Create log for miner
            self.db.createLog(new_block.metadata['miner'], time.time(), f"Block id {new_block.id}, was flagged invalid. Mine unsuccesful...")
            # setsaveFil transactions in block back to the pool
            for transaction in new_block.data:
                utilityHelper.addFile("../data/transaction_pool.dat", transaction)
            try:
                del blocks[-1]
            except:
                print(f"{utilityHelper.errorMessage('Something went wrong')}")
                return False
            
        utilityHelper.resetFile("../data/ledger.dat")
        for block in blocks:
            utilityHelper.addFile("../data/ledger.dat", block)

        return True
            

    
