# 1. Add imports
from web3 import Web3
import json
import os
from pathlib import Path
from PyQt5.QtWidgets import QMessageBox
# import env variable class
from .envvariables import envvariables

import re

from fpdf import FPDF
import os
from datetime import datetime

from PyQt5.QtWidgets import QLabel
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices

class Notarisation:

    web3 = ''
    abi = ''

    # Notarisation details
    CONTRACT_ADDRESS   = ''
    BLOCKCHAIN_ADDRESS = ''
    PRIVATE_KEY        = ''
    NODE_URL           = '' 
    WORKING_DIR        = ''

    transaction_hash   = '' 

    def __init__(self, plugin_dir, envvar): 

        # https://ethereum.stackexchange.com/questions/46706/web3-py-how-to-use-abi-in-python-when-solc-doesnt-work
        filename = os.path.join(plugin_dir, 'abis/Location.json')
        with open(filename) as f:
            LocationNFT = json.load(f)
        self.abi = LocationNFT["abi"]               
        
        self.PRIVATE_KEY = envvar.get_private_key() 
        self.BLOCKCHAIN_ADDRESS = envvar.get_blockchain_address()
        self.CONTRACT_ADDRESS = envvar.get_contract_address()    
        self.NODE_URL = envvar.get_node_url()   
        self.WORKING_DIR = envvar.get_working_dir()   
         # 2. Add the Web3 provider logic here: 
        self.web3 = Web3(Web3.HTTPProvider(self.NODE_URL)) #node_url))  # Change to correct network   

        print('ENV Variables read from file ') 
        print('self.BLOCKCHAIN_ADDRESS: '   + str(self.BLOCKCHAIN_ADDRESS))
        print('self.CONTRACT_ADDRESS: '     + str(self.CONTRACT_ADDRESS))
        print('self.NODE_URL: '             + str(self.NODE_URL))
        print('self.PRIVATE_KEY: '          + str(self.PRIVATE_KEY))

        # have to make this check
        # note the init function is only called when notarisation is invoked from the notarisation tab
        #self.check_env_variables()


    # Check if the private key is a valid hexadecimal string
    def is_valid_private_key(self, private_key):
        #return bool(re.fullmatch(r'0x[0-9a-fA-F]{64}', private_key))
        # make the first two chars '0x' optional
        return bool(re.fullmatch(r'(0x)?[0-9a-fA-F]{64}', private_key))

    # def check_env_variables(self):
    #     error = False

    #     try:
    #         if self.PRIVATE_KEY is None or self.PRIVATE_KEY == "":
    #             error = True
    #             print("PRIVATE_KEY not set in ENV file.")
    #             QMessageBox.information(None, "DEBUG:", 'PRIVATE_KEY not set in ENV file. ')
    #         if self.BLOCKCHAIN_ADDRESS is None or self.BLOCKCHAIN_ADDRESS == "":
    #             error = True
    #             print("BLOCKCHAIN_ADDRESS not set in ENV file.")
    #             QMessageBox.information(None, "DEBUG:", 'BLOCKCHAIN_ADDRESS not set in ENV file. ')
    #         if self.CONTRACT_ADDRESS is None or self.CONTRACT_ADDRESS == "":
    #             error = True
    #             print("CONTRACT_ADDRESS not set in ENV file.")
    #             QMessageBox.information(None, "DEBUG:", 'CONTRACT_ADDRESS not set in ENV file. ')
    #         if self.NODE_URL is None or self.NODE_URL == "":
    #             error = True
    #             print("NODE_URL not set in ENV file.")
    #             QMessageBox.information(None, "DEBUG:", 'NODE_URL not set in ENV file. ')
            
    #         # Validate the private key and address
    #         if not self.is_valid_private_key(self.PRIVATE_KEY):
    #             error = True
    #             print("Invalid private key format.")
    #             QMessageBox.warning(None, "Error", "Invalid private key format.")

    #         if not self.is_valid_address(self.BLOCKCHAIN_ADDRESS):
    #             error = True
    #             print("Invalid blockchain address format.")
    #             QMessageBox.warning(None, "Error", "Invalid blockchain address format.")

    #         return error
    #     except Exception as e:
    #         print(f'Exception checking environment variables. Please check + {e}')
    #         QMessageBox.information(None, "DEBUG:", 'Exception checking environment variables. ') 

    #     return error    

    # def check_env_variables(self):
    #     error = False

    #     try:
    #         # Check if the private key is set
    #         if self.PRIVATE_KEY is None or self.PRIVATE_KEY == "":
    #             error = True
    #             print("PRIVATE_KEY not set in ENV file.")
    #             QMessageBox.information(None, "DEBUG:", 'PRIVATE_KEY not set in ENV file.')
    #         elif len(self.PRIVATE_KEY) != 66:
    #             error = True
    #             print("Invalid private key length.")
    #             QMessageBox.warning(None, "Error", "Invalid private key length.")
    #         elif not self.is_valid_private_key(self.PRIVATE_KEY):
    #             # Validate the private key format only if it's set
    #             error = True
    #             print("Invalid private key format.")
    #             QMessageBox.warning(None, "Error", "Invalid private key format.")

    #         # Check if the blockchain address is set
    #         if self.BLOCKCHAIN_ADDRESS is None or self.BLOCKCHAIN_ADDRESS == "":
    #             error = True
    #             print("BLOCKCHAIN_ADDRESS not set in ENV file.")
    #             QMessageBox.information(None, "DEBUG:", 'BLOCKCHAIN_ADDRESS not set in ENV file.')
    #         elif not Web3.isChecksumAddress(self.BLOCKCHAIN_ADDRESS):
    #             error = True
    #             print("Invalid blockchain address format.")
    #             QMessageBox.warning(None, "Error", "Invalid blockchain address format.")
    #         elif not self.is_valid_address(self.BLOCKCHAIN_ADDRESS):
    #             # Validate the blockchain address format only if it's set
    #             error = True
    #             print("Invalid blockchain address format.")
    #             QMessageBox.warning(None, "Error", "Invalid blockchain address format.")

    #         # Check if the contract address is set
    #         if self.CONTRACT_ADDRESS is None or self.CONTRACT_ADDRESS == "":
    #             error = True
    #             print("CONTRACT_ADDRESS not set in ENV file.")
    #             QMessageBox.information(None, "DEBUG:", 'CONTRACT_ADDRESS not set in ENV file.')

    #         # Check if the node URL is set
    #         if self.NODE_URL is None or self.NODE_URL == "":
    #             error = True
    #             print("NODE_URL not set in ENV file.")
    #             QMessageBox.information(None, "DEBUG:", 'NODE_URL not set in ENV file.')

    #         return error

    #     except Exception as e:
    #         print(f'Exception checking environment variables. Please check + {e}')
    #         QMessageBox.information(None, "DEBUG:", 'Exception checking environment variables.')

    #     return error

    # Checks that all required environment variables are set and correctly formatted.
    # Returns True if any error is found.
    def check_env_variables(self):
        error_messages = []
        
        # Check private key
        if not self.PRIVATE_KEY:
            error_messages.append("PRIVATE_KEY not set in ENV file.")
        elif not self.is_valid_private_key(self.PRIVATE_KEY):
            error_messages.append("Invalid private key format.")

        # Check blockchain address
        if not self.BLOCKCHAIN_ADDRESS:
            error_messages.append("BLOCKCHAIN_ADDRESS not set in ENV file.")
        elif not Web3.is_checksum_address(self.BLOCKCHAIN_ADDRESS):
            error_messages.append("Invalid blockchain address format.")

        # Check contract address
        # Check if the contract address is set and valid
        if not self.CONTRACT_ADDRESS:
            error_messages.append("CONTRACT_ADDRESS not set in ENV file.")
        elif not Web3.is_checksum_address(self.CONTRACT_ADDRESS):
            error_messages.append("Invalid contract address format.")
        #elif not self.is_contract_address(self.CONTRACT_ADDRESS):
        #    error_messages.append("Invalid or non-existent contract address.")

        # Check node URL
        if not self.NODE_URL:
            error_messages.append("NODE_URL not set in ENV file.")

        # Display all errors in a single message box if any
        if error_messages:
            QMessageBox.warning(None, "Environment Variable Errors", "\n".join(error_messages))
            for msg in error_messages:
                print(msg)
            return True

        return False

    # Checks if the given address is a deployed contract on the blockchain.
    def is_contract_address(self, address):
        try:
            # Get the bytecode at the address
            code = self.web3.eth.get_code(Web3.to_checksum_address(address))

            # If the code is not empty, it's a contract address
            return code != b'0x'
        except Exception as e:
            print(f"Error checking contract address: {e}")
            return False

    def mint(self, string_to_mint): #, full_file_name, hash_value):

        #return True if there was an error
        if self.check_env_variables() == True:            
           return
        
        #no need to show error as would have been shown from the called function 

        # 3. Create variables
        account_from = {
            'private_key': self.PRIVATE_KEY, 
            'address':     self.BLOCKCHAIN_ADDRESS, 
        }
        # contract address will always be this constant address
        contract_address = '0x8dD5Ca941A9F839062b6589A2E3f701458B011A9'

        print(f"Calling the mint() function in contract at address: { contract_address }")

        try:

            # Fill in your account here
            #balance = Web3.eth.getBalance(self.BLOCKCHAIN_ADDRESS)
            #print(Web3.fromWei(balance, "ether"))

            # Validate keys
            if not Web3.is_checksum_address(account_from['address']):
                raise ValueError("Invalid blockchain address format.")

            # # Validate the private key length
            # if len(account_from['private_key']) != 66:
            #     raise ValueError("Invalid private key length.")

            # 4. Create contract instance
            minter = self.web3.eth.contract(address=contract_address, abi=self.abi)
            # 5. Build reset tx
            mint_tx = minter.functions.mintNFT(string_to_mint).build_transaction(
                {
                    "from": Web3.to_checksum_address(account_from["address"]),
                    "nonce": self.web3.eth.get_transaction_count(
                        Web3.to_checksum_address(account_from["address"])
                    ),
                }
            )

            # 6. Sign tx with PK
            tx_create = self.web3.eth.account.sign_transaction(mint_tx, account_from["private_key"])
            # 7. Send tx and wait for receipt
            tx_hash = self.web3.eth.send_raw_transaction(tx_create.raw_transaction)
            tx_receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash)

            #self.transaction_hash = tx_receipt.transaction_hash.hex()
            self.transaction_hash = tx_receipt['transactionHash'].hex()

            #print(f"Tx successful with hash: { tx_receipt.transaction_hash.hex() }") 
            print(f"Tx successful with hash: {tx_receipt['transactionHash'].hex()}")

            #self.save_receipt(string_to_mint, self.BLOCKCHAIN_ADDRESS, self.transaction_hash, None, full_file_name, hash_value)

            return True #success


        except ValueError as e:
            print(f"Validation error: {e}")
            QMessageBox.warning(None, "Error", f"Validation error: {e}")
            return False
        except Exception as e:
            print(f"Notarisation unsuccessful - {e}")
            QMessageBox.information(None, "Error:", 'Notarisation unsucessfull ') 
            return False # failure        

    def getTransaction(self):
        return self.transaction_hash


    def save_receipt(self, minted_string, from_addr, tx_hash, gas_price, full_file_name, hash_value):
        print("Saving Transaction Receipt as PDF...")

        # Create a PDF object
        pdf = FPDF()
        pdf.add_page()

        # Set metadata
        pdf.set_title('Mapsafe Transaction Record')
        pdf.set_subject(full_file_name)
        pdf.set_author('Mapsafe QGIS Plugin')
        pdf.set_creator('Mapsafe QGIS Plugin')

        # Add a logo (replace with your plugin's logo path)
        #logo_path = os.path.join(os.path.dirname(__file__), 'images', 'logo_name.png')
        logo_path = os.path.join(os.path.dirname(__file__), 'logo_name.png')
        if os.path.exists(logo_path):
            pdf.image(logo_path, x=25, y=10, w=50, h=12.5)  # x, y, width, height

        # Add title
        pdf.set_font('Arial', 'B', 16)
        pdf.set_y(40)
        pdf.cell(0, 10, 'Transaction Details!', ln=True)

        # Add date
        date_str = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        pdf.set_font('Arial', '', 10)
        pdf.cell(0, 10, f'Date: {date_str}', ln=True)

        # Add wallet address
        pdf.cell(0, 10, 'Wallet Account Address:', ln=True)
        pdf.set_text_color(0, 0, 255)
        pdf.cell(0, 10, from_addr, ln=True)
        pdf.set_text_color(0, 0, 0)

        # Add encrypted volume details
        pdf.set_font('Arial', 'B', 12)
        pdf.cell(0, 10, 'Encrypted Volume:', ln=True)
        pdf.set_font('Arial', '', 10)
        pdf.cell(0, 10, 'Details of the generated encrypted volume:', ln=True)

        pdf.cell(0, 10, f'Filename: {full_file_name}', ln=True)
        pdf.set_text_color(0, 0, 255)
        pdf.cell(0, 10, f'Hash Value: {hash_value}', ln=True)
        pdf.set_text_color(0, 0, 0)

        # Add Ethereum blockchain details
        pdf.set_font('Arial', 'B', 12)
        pdf.cell(0, 10, 'Ethereum Blockchain Notarisation Details:', ln=True)


        # Build the transaction URL by prepending the explorer URL to the tx_hash
        etherscan_url = f"https://sepolia.etherscan.io/tx/Ox{tx_hash}"

        # # Add a clickable link to the PDF
        # pdf.set_text_color(0, 0, 255)  # Blue color for link
        # pdf.set_font('Arial', 'U', 10)  # Underline font for link
        # pdf.cell(0, 10, 'View Transaction on Etherscan', ln=True, link=etherscan_url)

        pdf.set_font('Arial', '', 9)
        pdf.cell(0, 10, f'Value: {minted_string}', ln=True)
        pdf.set_text_color(0, 0, 255)
        #pdf.cell(0, 10, f'Minted Address: {tx_hash}', ln=True)
        pdf.cell(0, 10, 'Minted Address', ln=True, link=etherscan_url)
        pdf.set_text_color(0, 0, 0)

        pdf.cell(0, 10, f'Gas Price: {gas_price}', ln=True)

        # Add footer
        pdf.set_y(-30)
        pdf.set_font('Arial', 'I', 10)
        pdf.cell(0, 10, 'MapSafe QGIS Geoprivacy Plugin', ln=True)
        pdf.cell(0, 10, 'https://sharmapn.github.io/MapSafeQGISGeoPrivPlugin/', ln=True)

        # Save the PDF
        #only_date = datetime.now().strftime('%Y-%m-%d')
        # Get the current date and time
        timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
        #output_file = f'MapsafeReceipt_{only_date}.pdf'
        # Create the output filename with date and time
        output_file = f'MapsafeReceipt_{timestamp}.pdf'
        # WORKING_DIR
        #output_path = os.path.join(os.path.expanduser('~'), 'Documents', output_file)
        output_path = os.path.join(self.WORKING_DIR, output_file)
        pdf.output(output_path)

        print(f'Receipt saved to: {output_path}')
        #self.label_notarisation_record.labelText = 'output_path'
        #QMessageBox.information(None, "Success", f"Transaction receipt saved as PDF:\n{output_path}")
        # Set the label text as clickable HTML
        self.label_notarisation_record.setText(f'<a href="{output_path}">Transaction receipt saved as PDF</a>')

        # Enable the label to recognize links
        self.label_notarisation_record.setOpenExternalLinks(True)
