import io
import json
import random
import sys
from flask import Flask, make_response, redirect, request, jsonify, render_template, send_from_directory,send_file
import os
import dotenv
import requests
import account as account_module
import render
import re
from flask_qrcode import QRcode
import domainLookup
import urllib.parse
import importlib
import plugin as plugins_module
import gitinfo
import datetime
import functools
import time

dotenv.load_dotenv()

app = Flask(__name__)
qrcode = QRcode(app)


# Change this if network fees change
fees = 0.02
revokeCheck = random.randint(100000,999999)


THEME = os.getenv("THEME", "black")


def blocks_to_time(blocks: int) -> str:
    """
    Convert blocks to time in a human-readable format.
    Blocks are mined approximately every 10 minutes.
    """
    if blocks < 0:
        return "Invalid time"
    
    if blocks < 6:
        return f"{blocks * 10} mins"
    elif blocks < 144:
        hours = blocks // 6
        minutes = (blocks % 6) * 10
        if minutes == 0:
            return f"{hours} hrs"

        return f"{hours} hrs {minutes} mins"
    else:
        days = blocks // 144
        hours = (blocks % 144) // 6
        if hours == 0:
            return f"{days} days"
        return f"{days} days {hours} hrs"

@app.route('/')
def index():
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")
    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")

    
    plugins = ""
    dashFunctions = plugins_module.getDashboardFunctions()
    for function in dashFunctions:
        functionOutput = plugins_module.runPluginFunction(function["plugin"],function["function"],{},request.cookies.get("account"))
        plugins += render.plugin_output_dash(functionOutput,plugins_module.getPluginFunctionReturns(function["plugin"],function["function"]))

    # Check for updates
    if not os.path.exists(".git"):
        return render_template("index.html", account=account, plugins=plugins)
    
    info = gitinfo.get_git_info()
    if info is None:
        return render_template("index.html", account=account, plugins=plugins)
    branch = info['refs']
    commit = info['commit']
    if commit != latestVersion(branch):
        print("New version available",flush=True)
        plugins += render_template('components/dashboard-alert.html', name='Update', output='A new version of FireWallet is available') 

    return render_template("index.html", account=account, plugins=plugins)
 
def reverseDirection(direction: str):
    if direction == "⬆":
        return "⬇"
    else:
        return "⬆"


#region Transactions
# Add a cache for transactions with a timeout
tx_cache = {}
TX_CACHE_TIMEOUT = 60*5  # Cache timeout in seconds

@app.route('/tx')
def transactions():
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")
    
    # Get the page parameter
    page = request.args.get('page', 1)
    try:
        page = int(page)
    except:
        page = 1

    if page < 1:
        page = 1
    
    # Return the template with loading state - transactions will be loaded via AJAX
    return render_template("tx.html", account=account, 
                           page=page, txCount=0)
    

@app.route('/send')
def send_page():
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")
    max = account_module.getBalance(account)['available']
    # Subtract approx fee
    max = max - fees
    max = round(max, 2)

    message = ''
    address = ''
    amount = ''

    if 'message' in request.args:
        message = request.args.get("message")
    if 'address' in request.args:
        address = request.args.get("address")
    if 'amount' in request.args:
        amount = request.args.get("amount")
        

    return render_template("send.html", account=account,
                           
                           max=max,message=message,address=address,amount=amount)

@app.route('/send', methods=["POST"])
def send():
    if request.cookies.get("account") is None:
        return redirect("/login")
    
    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")
    
    # Get the address and amount
    address = request.form.get("address")
    amount = request.form.get("amount")

    if address is None or amount is None:
        return redirect(f"/send?message=Invalid address or amount&address={address}&amount={amount}")
    
    address_check = account_module.check_address(address.strip(),True,True)
    if not address_check:
        return redirect(f"/send?message=Invalid address&address={address}&amount={amount}")
    
    address = address_check
    # Check if the amount is valid
    if re.match(r"^\d+(\.\d+)?$", amount) is None:
        return redirect(f"/send?message=Invalid amount&address={address}&amount={amount}")
    
    # Check if the amount is valid
    amount = float(amount)
    if amount <= 0:
        return redirect(f"/send?message=Invalid amount&address={address}&amount={amount}")
    
    if amount > account_module.getBalance(account)['available'] - fees:
        return redirect(f"/send?message=Not enough funds to transfer&address={address}&amount={amount}")
    
    toAddress = address
    if request.form.get('address') != address:
        toAddress = f"{request.form.get('address')}<br>{address}"

    action = f"Send HNS to {request.form.get('address')}"
    content = f"Are you sure you want to send {amount} HNS to {toAddress}<br><br>"
    content += f"This will cost {amount} HNS + mining fees and is not able to be undone."

    cancel = f"/send"
    confirm = f"/send/confirm?address={address}&amount={amount}"


    return render_template("confirm.html", account=account_module.check_account(request.cookies.get("account")),
                            action=action,
                            content=content,cancel=cancel,confirm=confirm)
    

@app.route('/send/confirm')
def sendConfirmed():

    address = request.args.get("address")
    amount = float(request.args.get("amount","0"))
    response = account_module.send(request.cookies.get("account"),address,amount)
    if 'error' in response and response['error'] != None:
        # If error is a dict get the message
        if isinstance(response['error'], dict):
            if 'message' in response['error']:
                return redirect(f"/send?message={response['error']['message']}&address={address}&amount={amount}")
            else:
                return redirect(f"/send?message={response['error']}&address={address}&amount={amount}")
        
        # If error is a string
        return redirect(f"/send?message={response['error']}&address={address}&amount={amount}")
    
    return redirect(f"/success?tx={response['tx']}")
    


@app.route('/receive')
def receive():
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")
    
    address = account_module.getAddress(account)

    return render_template("receive.html", account=account,
                           
                           address=address)
    
@app.route('/success')
def success():
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")

    tx = request.args.get("tx")
    return render_template("success.html", account=account,
                           tx=tx)

@app.route('/checkaddress')
def check_address():
    address = request.args.get("address")
    if address is None:
        return jsonify({"result": "Invalid address"})
    
    return jsonify({"result": account_module.check_address(address.strip())})
#endregion

#region Domains
@app.route('/auctions')
def auctions():
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")
    
    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")


    bids = account_module.getBids(account)
    domains = account_module.getDomains(account,False)
    
    # Sort
    sort = request.args.get("sort")
    if sort == None:
        sort = "time"
    sort = sort.lower()
    sort_price = ""
    sort_price_next = "⬇"
    sort_state = ""
    sort_state_next = "⬇"
    sort_domain = ""
    sort_domain_next = "⬇"
    sort_time = ""
    sort_time_next = "⬇"
    reverse = False

    direction = request.args.get("direction")
    if direction == None:
        if sort == "time":
            direction = "⬆"
        else:
            direction = "⬇"

    if direction == "⬆":
        reverse = True

    sortbyDomain = False

    if sort == "price":
        # Sort by price
        bids = sorted(bids, key=lambda k: k['value'],reverse=reverse)
        sort_price = direction
        sort_price_next = reverseDirection(direction)
    elif sort == "state":
        sort_state = direction
        sort_state_next = reverseDirection(direction)
        domains = sorted(domains, key=lambda k: k['state'],reverse=reverse)
        sortbyDomain = True
    elif sort == "time":
        sort_time = direction
        sort_time_next = reverseDirection(direction)

        # If older HSD version sort by domain height
        if bids[0]['height'] == 0:
            domains = sorted(domains, key=lambda k: k['height'],reverse=reverse)
            sortbyDomain = True
        else:
            bids = sorted(bids, key=lambda k: k['height'],reverse=reverse)
    else:
        # Sort by domain
        bids = sorted(bids, key=lambda k: k['name'],reverse=reverse)
        sort_domain = direction
        sort_domain_next = reverseDirection(direction)
    
    bidsHtml = render.bidDomains(bids,domains,sortbyDomain)
    plugins = ""
    message = ''
    if 'message' in request.args:
        message = request.args.get("message")
    return render_template("auctions.html", account=account, domains=bidsHtml,
                           domainsMobile=bidsHtml, plugins=plugins,
                           domain_count=bidsHtml,sort_price=sort_price,
                           sort_state=sort_state,sort_domain=sort_domain,
                           sort_price_next=sort_price_next,
                           sort_state_next=sort_state_next,sort_domain_next=sort_domain_next,
                           bids=len(bids),message=message,
                           sort_time=sort_time,sort_time_next=sort_time_next)


#region All Auctions
@app.route('/reveal')
@app.route('/all/reveal')
def revealAllBids():
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")
    
    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")

    response = account_module.revealAll(request.cookies.get("account"))
    if not response:
        return redirect("/auctions?message=Failed to reveal bids")

    if 'error' in response:
        if response['error'] != None:
            if response['error']['message'] == "Nothing to do.":
                return redirect("/auctions?message=No reveals pending")
            return redirect("/auctions?message=" + response['error']['message'])
            
    return redirect("/success?tx=" + response['result']['hash'])


@app.route('/all/redeem')
def redeemAllBids():
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")

    response = account_module.redeemAll(request.cookies.get("account"))
    if not response:
        return redirect("/auctions?message=Failed to redeem bids")

    if 'error' in response:
        if response['error'] != None:
            if response['error']['message'] == "Nothing to do.":
                return redirect("/auctions?message=No redeems pending")
            return redirect("/auctions?message=" + response['error']['message'])
            
    return redirect("/success?tx=" + response['result']['hash'])

@app.route('/all/register')
def registerAllDomains():
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")

    response = account_module.registerAll(request.cookies.get("account"))
    if not response:
        return redirect("/auctions?message=Failed to register domains")

    if 'error' in response:
        if response['error'] != None:
            if response['error']['message'] == "Nothing to do.":
                return redirect("/auctions?message=No domains to register")
            return redirect("/auctions?message=" + response['error']['message'])
            
    return redirect(f"/success?tx={response['hash']}")

@app.route('/all/finalize')
def finalizeAllBids():
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")

    response = account_module.finalizeAll(request.cookies.get("account"))
    if 'error' in response:
        if response['error'] != None:
            if response['error']['message'] == "Nothing to do.":
                return redirect("/dashboard?message=No domains to finalize")
            return redirect("/dashboard?message=" + response['error']['message'])
        
    return redirect(f"/success?tx={response['hash']}")
#endregion

@app.route('/search')
def search():
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")
    
    search_term = request.args.get("q")
    if search_term is None:
        return redirect("/")
    search_term = search_term.lower().strip()

    # Replace spaces with hyphens
    search_term = search_term.replace(" ","-")
    
    # Convert emoji to punycode
    search_term = domainLookup.emoji_to_punycode(search_term)
    if len(search_term) == 0:
        return redirect("/")

    domain = account_module.getDomain(search_term)
    
    plugins = "<div class='container-fluid'>"
    # Execute domain plugins
    searchFunctions = plugins_module.getSearchFunctions()
    for function in searchFunctions:
        functionOutput = plugins_module.runPluginFunction(function["plugin"],function["function"],{"domain":search_term},account)
        plugins += render.plugin_output(functionOutput,plugins_module.getPluginFunctionReturns(function["plugin"],function["function"]))

    plugins += "</div>"

    if 'error' in domain:
        return render_template("search.html", account=account,
                               rendered=renderDomain(search_term),
                               search_term=search_term, domain=domain['error'],plugins=plugins)
    
    if domain['info'] is None:
        return render_template("search.html", account=account, 
                               rendered=renderDomain(search_term),
                               search_term=search_term,domain=search_term,
                               state="AVAILABLE", next="Available Now",plugins=plugins)

    state = domain['info']['state']
    stats = domain['info']['stats']
    next = ""
    if state == 'CLOSED':
        if domain['info']['registered']:
            state = 'REGISTERED'
            expires = stats['daysUntilExpire']
            next = f"Expires in ~{expires} days"
        else:
            state = 'AVAILABLE'
            next = "Available Now"
    elif state == "REVOKED":
            next = "Available Now"
    elif state == 'OPENING':
        next = f"Bidding opens in {str(stats['blocksUntilBidding'])} blocks (~{blocks_to_time(stats['blocksUntilBidding'])})"
    elif state == 'BIDDING':
        next = f"Reveal in {str(stats['blocksUntilReveal'])} blocks (~{blocks_to_time(stats['blocksUntilReveal'])})"
    elif state == 'REVEAL':
        next = f"Reveal ends in {str(stats['blocksUntilClose'])} blocks (~{blocks_to_time(stats['blocksUntilClose'])})"



    domain_info = account_module.getDomain(search_term)
    owner = 'Unknown'
    dns = []
    txs = []

    if domain_info:
        # Check if info and info.owner
        if 'info' in domain_info and 'owner' in domain_info['info']:
            owner = account_module.getAddressFromCoin(domain_info['info']['owner']['hash'],domain_info['info']['owner']['index'])
    
    dns = account_module.getDNS(search_term)

    own_domains = account_module.getDomains(account)
    own_domains = [x['name'] for x in own_domains]
    own_domains = [x.lower() for x in own_domains]
    if search_term in own_domains:
        owner = "You"

    dns = render.dns(dns)

    return render_template("search.html", account=account, 
                           rendered=renderDomain(search_term),
                           search_term=search_term,domain=domain['info']['name'],
                           raw=domain,state=state, next=next, owner=owner,
                           dns=dns,plugins=plugins)
    
@app.route('/manage/<domain>')
def manage(domain: str):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")
    
    domain = domain.lower()

    if not account_module.isOwnDomain(account, domain):
        return redirect("/search?q=" + domain)
    
    domain_info = account_module.getDomain(domain)
    if 'error' in domain_info:
        return render_template("manage.html", account=account, 
                               rendered=renderDomain(domain),
                               domain=domain, error=domain_info['error'])
    
    if domain_info['info'] is not None and 'stats' in domain_info['info'] and 'daysUntilExpire' in domain_info['info']['stats']:
        expiry = domain_info['info']['stats']['daysUntilExpire']
    else:
        expiry = "Unknown"
    dns = account_module.getDNS(domain)
    raw_dns = str(dns).replace("'",'"')
    dns = render.dns(dns)

    errorMessage = request.args.get("error")
    if errorMessage == None:
        errorMessage = ""
    address = request.args.get("address")
    if address == None:
        address = ""
    
    finalize_time = ""
    # Check if the domain is in transfer
    if domain_info['info']['transfer'] != 0:
        current_block = account_module.getBlockHeight()
        finalize_valid = domain_info['info']['transfer']+288
        finalize_blocks = finalize_valid - current_block
        if finalize_blocks > 0:
            finalize_time = "in "+ str(finalize_blocks) + " blocks (~" + str(round(finalize_blocks/6)) + " hours)"
        else:
            finalize_time = "now"

    plugins = "<div class='container-fluid'>"
    # Execute domain plugins
    domainFunctions = plugins_module.getDomainFunctions()
    for function in domainFunctions:
        functionOutput = plugins_module.runPluginFunction(function["plugin"],function["function"],{"domain":domain},account)
        plugins += render.plugin_output(functionOutput,plugins_module.getPluginFunctionReturns(function["plugin"],function["function"]))

    plugins += "</div>"


    return render_template("manage.html", account=account, 
                           rendered=renderDomain(domain),
                           error=errorMessage, address=address,
                           domain=domain,expiry=expiry, dns=dns,
                           raw_dns=urllib.parse.quote(raw_dns),
                           finalize_time=finalize_time,plugins=plugins)


@app.route('/manage/<domain>/finalize')
def finalize(domain: str):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    
    if not account_module.check_account(request.cookies.get("account")):
        return redirect("/logout")
    
    domain = domain.lower()
    response = account_module.finalize(request.cookies.get("account"),domain)
    if response['error'] != None:
        print(response)
        return redirect("/manage/" + domain + "?error=" + response['error']['message'])

    return redirect("/success?tx=" + response['result']['hash'])

@app.route('/manage/<domain>/cancel')
def cancelTransfer(domain: str):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    
    if not account_module.check_account(request.cookies.get("account")):
        return redirect("/logout")
    
    domain = domain.lower()
    response = account_module.cancelTransfer(request.cookies.get("account"),domain)
    if 'error' in response:
        if response['error'] != None:
            print(response)
            return redirect("/manage/" + domain + "?error=" + response['error']['message'])

    return redirect("/success?tx=" + response['result']['hash'])

@app.route('/manage/<domain>/revoke')
def revokeInit(domain: str):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    
    if not account_module.check_account(request.cookies.get("account")):
        return redirect("/logout")
    
    domain = domain.lower()

    content = f"Are you sure you want to revoke {domain}/?<br>"
    content += f"This will return the domain to the auction pool and you will lose any funds spent on the domain.<br>"
    content += f"This cannot be undone after the transaction is sent.<br><br>"
    content += f"Please enter your password to confirm."

    cancel = f"/manage/{domain}"
    confirm = f"/manage/{domain}/revoke/confirm"
    action = f"Revoke {domain}/"   

    
    return render_template("confirm-password.html", account=account_module.check_account(request.cookies.get("account")),
                            
                            action=action,
                            content=content,cancel=cancel,confirm=confirm,check=revokeCheck)

@app.route('/manage/<domain>/revoke/confirm', methods=["POST"])
def revokeConfirm(domain: str):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    
    if not account_module.check_account(request.cookies.get("account")):
        return redirect("/logout")
    
    domain = domain.lower()
    password = request.form.get("password")
    check = request.form.get("check")
    if check != str(revokeCheck):
        return redirect("/manage/" + domain + "?error=An error occurred. Please try again.")

    response = account_module.check_password(request.cookies.get("account"),password)
    if response == False:
        return redirect("/manage/" + domain + "?error=Invalid password")


    response = account_module.revoke(request.cookies.get("account"),domain)
    if 'error' in response:
        if response['error'] != None:
            print(response)
            return redirect("/manage/" + domain + "?error=" + response['error']['message'])

    return redirect(f"/success?tx={response['hash']}")

@app.route('/manage/<domain>/renew')
def renew(domain: str):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    
    if not account_module.check_account(request.cookies.get("account")):
        return redirect("/logout")
    
    domain = domain.lower()
    response = account_module.renewDomain(request.cookies.get("account"),domain)
    return redirect(f"/success?tx={response['hash']}")

@app.route('/manage/<domain>/edit')
def editPage(domain: str):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")
    
    domain = domain.lower()
    
    own_domains = account_module.getDomains(account)
    own_domains = [x['name'] for x in own_domains]
    own_domains = [x.lower() for x in own_domains]
    if domain not in own_domains:
        return redirect("/search?q=" + domain)
       

    user_edits = request.args.get("dns")
    if user_edits != None:
        dns = urllib.parse.unquote(user_edits)
    else:
        dns = account_module.getDNS(domain)

    if dns and isinstance(dns, str):
        dns = json.loads(dns)
    else:
        dns = []

    # Check if new records have been added
    dnsType = request.args.get("type")
    dnsValue = request.args.get("value")
    if dnsType != None and dnsValue != None:
        if dnsType != "DS":
            dns.append({"type": dnsType, "value": dnsValue})
        else:
            # Verify the DS record
            ds = dnsValue.split(" ")
            if len(ds) != 4:
                raw_dns = str(dns).replace("'",'"')
                return redirect("/manage/" + domain + "/edit?dns=" + urllib.parse.quote(str(raw_dns)) + "&error=Invalid DS record")
            
            try:
                key_tag = int(ds[0])
                algorithm = int(ds[1])
                digest_type = int(ds[2])
            except:
                raw_dns = str(dns).replace("'",'"')
                return redirect("/manage/" + domain + "/edit?dns=" + urllib.parse.quote(str(raw_dns)) + "&error=Invalid DS record")
            
            dns.append({"type": dnsType, "keyTag": key_tag, "algorithm": algorithm, "digestType": digest_type, "digest": ds[3]})

        dns = json.dumps(dns).replace("'",'"')
        return redirect("/manage/" + domain + "/edit?dns=" + urllib.parse.quote(dns))

    raw_dns = str(dns).replace("'",'"')
    dns = render.dns(dns,True)
    errorMessage = request.args.get("error")
    if errorMessage == None:
        errorMessage = ""

    
    return render_template("edit.html", account=account, 
                           rendered=renderDomain(domain),
                           domain=domain, error=errorMessage,
                           dns=dns,raw_dns=urllib.parse.quote(raw_dns))


@app.route('/manage/<domain>/edit/save')
def editSave(domain: str):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    
    if not account_module.check_account(request.cookies.get("account")):
        return redirect("/logout")
    
    domain = domain.lower()
    dns = request.args.get("dns")
    if dns is None:
        return redirect(f"/manage/{domain}/edit?error=No DNS records provided")
    raw_dns = dns
    dns = urllib.parse.unquote(dns)
    response = account_module.setDNS(request.cookies.get("account"),domain,dns)
    if 'error' in response:
        print(response)
        return redirect(f"/manage/{domain}/edit?dns={raw_dns}&error={response['error']}")
    return redirect(f"/success?tx={response['hash']}")

@app.route('/manage/<domain>/transfer')
def transfer(domain):
    if request.cookies.get("account") is None:
        return redirect("/login")
    
    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")
    
    # Get the address and amount
    address = request.args.get("address")

    if address is None:
        return redirect("/manage/" + domain + "?error=Invalid address")
    
    address_check = account_module.check_address(address,True,True)
    if not address_check:
        return redirect("/manage/" + domain + "?error=Invalid address")
    
    address = address_check
        
    toAddress = address
    if request.form.get('address') != address:
        toAddress = f"{request.args.get('address')}<br>{address}"

    action = f"Send {domain}/ to {request.form.get('address')}"
    content = f"Are you sure you want to send {domain}/ to {toAddress}<br><br>"
    content += f"This requires sending a finalize transaction 2 days after the transfer is initiated."

    cancel = f"/manage/{domain}?address={address}"
    confirm = f"/manage/{domain}/transfer/confirm?address={address}"


    return render_template("confirm.html", account=account,action=action,
                            content=content,cancel=cancel,confirm=confirm)

@app.route('/manage/<domain>/sign')
def signMessage(domain):
    if request.cookies.get("account") is None:
        return redirect("/login")
    
    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")
    
    # Get the address and amount
    message = request.args.get("message")

    if message is None:
        return redirect("/manage/" + domain + "?error=Invalid message")
    

    content = "Message to sign:<br><code>" + message + "</code><br><br>"
    signedMessage = account_module.signMessage(request.cookies.get("account"),domain,message)
    if signedMessage["error"] != None:
        return redirect("/manage/" + domain + "?error=" + signedMessage["error"])
    content += f"Signature:<br><code>{signedMessage["result"]}</code><br><br>"

    data = {
        "domain": domain,
        "message": message,
        "signature": signedMessage["result"]
    }

    content += "Full information:<br><code style='text-align:left;display: block;'>" + json.dumps(data,indent=4).replace('\n',"<br>") + "</code><br><br>"

    content += "<textarea style='display: none;' id='data' rows='4' cols='50'>"+json.dumps(data)+"</textarea>"

    copyScript = "<script>function copyToClipboard() {var copyText = document.getElementById('data');copyText.style.display = 'block';copyText.select();copyText.setSelectionRange(0, 99999);document.execCommand('copy');copyText.style.display = 'none';var copyButton = document.getElementById('copyButton');copyButton.innerHTML='Copied';}</script>"
    content += "<button id='copyButton' onclick='copyToClipboard()' class='btn btn-secondary'>Copy to clipboard</button>" + copyScript

    

    return render_template("message.html", account=account,                           
                               title="Sign Message",content=content)
    

@app.route('/manage/<domain>/transfer/confirm')
def transferConfirm(domain):
    if request.cookies.get("account") is None:
        return redirect("/login")
    
    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")
    
    # Get the address and amount
    address = request.args.get("address")
    response = account_module.transfer(request.cookies.get("account"),domain,address)
    if 'error' in response:
        return redirect("/manage/" + domain + "?error=" + response['error'])
    
    return redirect(f"/success?tx={response['hash']}")


@app.route('/auction/<domain>')
def auction(domain):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")
    
    search_term = domain.lower().strip()    
    # Convert emoji to punycode
    search_term = domainLookup.emoji_to_punycode(search_term)
    if len(search_term) == 0:
        return redirect("/")

    domainInfo = account_module.getDomain(search_term)
    error = request.args.get("error")
    if error == None:
        error = ""
    
    if 'error' in domainInfo:
        return render_template("auction.html", account=account,
                               rendered=renderDomain(search_term),
                               search_term=search_term, domain=domainInfo['error'],
                               error=error)
    
    if domainInfo['info'] is None:
        if 'registered' in domainInfo and domainInfo['registered'] == False and 'expired' in domainInfo and domainInfo['expired'] == False:
            # Needs to be registered
                next_action = f'ERROR GETTING NEXT STATE'
        else:
            next_action = f'<a href="/auction/{domain}/open">Open Auction</a>'
        return render_template("auction.html", account=account, 
                               rendered=renderDomain(search_term),
                                search_term=search_term,domain=search_term,next_action=next_action,
                               state="AVAILABLE", next="Open Auction",
                               error=error)

    state = domainInfo['info']['state']
    next_action = ''
    next = ""

    bids = []
    stats = domainInfo['info']['stats'] if 'stats' in domainInfo['info'] else {}
    if state == 'CLOSED':
        if not domainInfo['info']['registered']:
            if account_module.isOwnDomain(account,domain):
                print("Waiting to be registered")
                state = 'PENDING REGISTER'
                next = "Pending Register"
                next_action = f'<a href="/auction/{domain}/register">Register Domain</a>'
            
            else:
                print("Not registered")
                state = 'AVAILABLE'
                next = "Available Now"
                next_action = f'<a href="/auction/{domain}/open">Open Auction</a>'
        else:
            state = 'REGISTERED'
            expires = domainInfo['info']['stats']['daysUntilExpire']
            next = f"Expires in ~{expires} days"

            if account_module.isOwnDomain(account,domain):
                next_action = f'<a href="/manage/{domain}">Manage</a>'
    elif state == "REVOKED":
        next = "Available Now"
        next_action = f'<a href="/auction/{domain}/open">Open Auction</a>'
    elif state == 'OPENING':
        next = f"Bidding opens in {str(stats['blocksUntilBidding'])} blocks (~{blocks_to_time(stats['blocksUntilBidding'])})"
    elif state == 'BIDDING':
        next = f"Reveal in {stats['blocksUntilReveal']} blocks (~{blocks_to_time(stats['blocksUntilReveal'])})"
        if stats['blocksUntilReveal'] == 1:
            next += "<br>Bidding no longer possible"
        elif stats['blocksUntilReveal'] == 2:
            next += "<br>LAST CHANCE TO BID"
        elif stats['blocksUntilReveal'] == 3:
            next += f"<br>Next block is last chance to bid"
        elif stats['blocksUntilReveal'] < 6:
            next += f"<br>Last chance to bid in {stats['blocksUntilReveal']-2} blocks"


    elif state == 'REVEAL':
        next = f"Reveal ends in {str(stats['blocksUntilClose'])} blocks (~{blocks_to_time(stats['blocksUntilClose'])})"
        next_action = f'<a href="/auction/{domain}/reveal">Reveal All</a>'

    message = ''
    if 'message' in request.args:
        message = request.args.get("message")


    return render_template("auction.html", account=account, 
                           rendered=renderDomain(search_term),
                           search_term=search_term,domain=domainInfo['info']['name'],
                           raw=domainInfo,state=state, next=next,
                           next_action=next_action, bids=bids,error=message)

@app.route('/auction/<domain>/scan')
def rescan_auction(domain):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")
    
    domain = domain.lower()
    
    response = account_module.rescan_auction(account,domain)
    return redirect("/auction/" + domain)

@app.route('/auction/<domain>/bid')
def bid(domain):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    
    if not account_module.check_account(request.cookies.get("account")):
        return redirect("/logout")
    
    domain = domain.lower()
    bid = request.args.get("bid","")
    blind = request.args.get("blind","")

    if bid == "":
        bid = 0
    if blind == "":
        blind = 0

    bid = float(bid)
    blind = float(blind)

    if bid+blind == 0:
        return redirect("/auction/" + domain+ "?error=Invalid bid amount")

    
    # Show confirm page
    total = bid + blind

    action = f"Bid on {domain}/"
    content = f"Are you sure you want to bid on {domain}/?"
    content += "You are about to bid with the following details:<br><br>"
    content += f"Bid: {str(bid)} HNS<br>"
    content += f"Blind: {str(blind)} HNS<br>"
    content += f"Total: {total} HNS (excluding fees)<br><br>"

    cancel = f"/auction/{domain}"
    confirm = f"/auction/{domain}/bid/confirm?bid={request.args.get('bid')}&blind={request.args.get('blind')}"



    return render_template("confirm.html", account=account_module.check_account(request.cookies.get("account")),
                            
                            action=action,
                            domain=domain,content=content,cancel=cancel,confirm=confirm)

@app.route('/auction/<domain>/bid/confirm')
def bid_confirm(domain):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    
    if not account_module.check_account(request.cookies.get("account")):
        return redirect("/logout")
    
    domain = domain.lower()
    bid = request.args.get("bid","")
    blind = request.args.get("blind","")

    if bid == "":
        bid = 0
    if blind == "":
        blind = 0

    bid = float(bid)
    blind = float(blind)

    
    # Send the bid
    response = account_module.bid(request.cookies.get("account"),domain,
                                  float(bid),
                                  float(blind))

    if 'error' in response:
        return redirect("/auction/" + domain + "?error=" + response['error']['message'])
    
    return redirect(f"/success?tx={response['hash']}")

@app.route('/auction/<domain>/open')
def open_auction(domain):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    
    if not account_module.check_account(request.cookies.get("account")):
        return redirect("/logout")
    
    domain = domain.lower()
    response = account_module.openAuction(request.cookies.get("account"),domain)

    if 'error' in response:
        if response['error'] != None:
            return redirect("/auction/" + domain + "?error=" + response['error']['message'])

    return redirect(f"/success?tx={response['hash']}")

@app.route('/auction/<domain>/reveal')
def reveal_auction(domain):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    
    if not account_module.check_account(request.cookies.get("account")):
        return redirect("/logout")
    
    domain = domain.lower()
    response = account_module.revealAuction(request.cookies.get("account"),domain)
    if 'error' in response:
        return redirect(f"/auction/{domain}?message={response['error']}")
    return redirect(f"/success?tx={response['hash']}")

@app.route('/auction/<domain>/register')
def registerdomain(domain):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")
    
    if not account_module.check_account(request.cookies.get("account")):
        return redirect("/logout")
    
    domain = domain.lower()
    response = account_module.register(request.cookies.get("account"),domain)
    if 'error' in response:
        return redirect("/auction/" + domain + "?message=" + response['error']['message'])
    return redirect(f"/success?tx={response['hash']}")

#endregion
#region Settings
@app.route('/settings')
def settings():
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")
    
    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")
    
    error = request.args.get("error")
    if error == None:
        error = ""
    success = request.args.get("success")
    if success == None:
        success = ""


    if not os.path.exists(".git"):
        return render_template("settings.html", account=account,
                               hsd_version=account_module.hsdVersion(False),
                               error=error,success=success,version="Error",
                               internal=account_module.HSD_INTERNAL_NODE,
                               spv=account_module.isSPV())
    info = gitinfo.get_git_info()
    if not info:
        return render_template("settings.html", account=account,                               
                               hsd_version=account_module.hsdVersion(False),
                               error=error,success=success,version="Error",
                               internal=account_module.HSD_INTERNAL_NODE,
                               spv=account_module.isSPV())

    branch = info['refs']
    if branch != "main":
        branch = f"({branch})"
    else:
        branch = ""
    last_commit = info['author_date']
    # import to time from format "2024-02-13 11:24:03"
    last_commit = datetime.datetime.strptime(last_commit, "%Y-%m-%d %H:%M:%S")
    version = f'{last_commit.strftime("%y-%m-%d")} {branch}'
    if info['commit'] != latestVersion(info['refs']):
        version += ' (New version available)'
    return render_template("settings.html", account=account,                           
                           hsd_version=account_module.hsdVersion(False),
                           error=error,success=success,version=version,internal=account_module.HSD_INTERNAL_NODE,
                           spv=account_module.isSPV())

@app.route('/settings/<action>')
def settings_action(action):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")
    
    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")

    if action == "rescan":
        resp = account_module.rescan()
        if 'error' in resp:
            return redirect("/settings?error=" + str(resp['error']))
        return redirect("/settings?success=Rescan started")
    
    if action == "resend":
        resp = account_module.resendTXs()
        if 'error' in resp:
            return redirect("/settings?error=" + str(resp['error']))
        return redirect("/settings?success=Resent transactions")


    if action == "zap":
        resp = account_module.zapTXs(request.cookies.get("account"))
        if type(resp) == dict and 'error' in resp:
            return redirect("/settings?error=" + str(resp['error']))
        return redirect("/settings?success=Zapped transactions")
    
    if action == "xpub":
        xpub = account_module.getxPub(request.cookies.get("account"))
        content = "<br><br>"
        content += f"<textarea style='display: none;' id='data' rows='4' cols='50'>{xpub}</textarea>"
        content += "<script>function copyToClipboard() {var copyText = document.getElementById('data');copyText.style.display = 'block';copyText.select();copyText.setSelectionRange(0, 99999);document.execCommand('copy');copyText.style.display = 'none';var copyButton = document.getElementById('copyButton');copyButton.innerHTML='Copied';}</script>"
        content += "<button id='copyButton' onclick='copyToClipboard()' class='btn btn-secondary'>Copy to clipboard</button>"

        return render_template("message.html", account=account,
                               title="xPub Key",
                               content=f"<code>{xpub}</code>{content}")

    if action == "restart":
        resp = account_module.hsdRestart()
        return render_template("message.html", account=account,
                               title="Restarting",
                               content="The node is restarting. This may take a minute or two. You can close this window.")

    return redirect("/settings?error=Invalid action")

@app.route('/settings/upload', methods=['POST'])
def upload_image():
    if not 'account' in request.cookies:
        return redirect("/login?message=Not logged in")
    
    account = request.cookies.get("account")
    account = account_module.check_account(account)
    if not account:
        return redirect("/logout")

    if not os.path.exists('user_data/images'):
        os.mkdir('user_data/images')

    if 'image' not in request.files:
        return redirect("/settings?error=No file selected")
    file = request.files['image']
    if file.filename == '':
        return redirect("/settings?error=No file selected")
    if file and file.filename:
        filepath = os.path.join(f'user_data/images/{account}.{file.filename.split(".")[-1]}')
        file.save(filepath)
        return redirect("/settings?success=File uploaded successfully")
    
    return redirect("/settings?error=An error occurred")

def latestVersion(branch):
    result = requests.get(f"https://git.woodburn.au/api/v1/repos/nathanwoodburn/firewalletbrowser/branches")
    if result.status_code != 200:
        return "Error"
    
    data = result.json()
    for b in data:
        if b['name'] == branch:
            return b['commit']['id']
    return "Invalid branch"

#endregion


#region Account
@app.route('/login')
def login():
    wallets = account_module.listWallets()
    wallets = render.wallets(wallets)

    # If there are no wallets redirect to either register or import
    if len(wallets) == 0:
        return redirect("/welcome")

    if 'message' in request.args:
        return render_template("login.html", 
                               error=request.args.get("message"),wallets=wallets)

    return render_template("login.html", 
                           
                           wallets=wallets)

@app.route('/login', methods=["POST"])
def login_post():
    # Get the account and password
    account = request.form.get("account")
    password = request.form.get("password")

    if account == None or password == None:
        wallets = account_module.listWallets()
        wallets = render.wallets(wallets)
        return render_template("login.html",
                               error="Invalid account or password",wallets=wallets)

    # Check if the account is valid
    if account.count(":") > 0:
        wallets = account_module.listWallets()
        wallets = render.wallets(wallets)
        return render_template("login.html",
                               error="Invalid account",wallets=wallets)

    account = account + ":" + password

    # Check if the account is valid
    if not account_module.check_password(account,password):
        wallets = account_module.listWallets()
        wallets = render.wallets(wallets)
        return render_template("login.html", 
                               error="Invalid account or password",wallets=wallets)
    # Set the cookie
    response = make_response(redirect("/"))
    response.set_cookie("account", account)
    return response

@app.route('/logout')
def logout():
    response = make_response(redirect("/login"))
    response.set_cookie("account", "", expires=0)
    return response

@app.route('/register', methods=["POST"])
def register():
    # Get the account and password
    account = request.form.get("name")
    password = request.form.get("password")
    repeatPassword = request.form.get("password_repeat")

    if account == None or password == None or repeatPassword == None:
        return render_template("register.html",
                               error="Invalid account or password",
                               name=account,password=password,password_repeat=repeatPassword)

    # Check if the passwords match
    if password != repeatPassword:
        return render_template("register.html",
                               error="Passwords do not match",
                               name=account,password=password,password_repeat=repeatPassword)

    # Check if the account is valid
    if account.count(":") > 0:
        return render_template("register.html",
                               error="Invalid account",
                               name=account,password=password,password_repeat=repeatPassword)

    # List wallets
    wallets = account_module.listWallets()
    if account in wallets:
        return render_template("register.html",
                               error="Account already exists",
                               name=account,password=password,password_repeat=repeatPassword)

    # Create the account
    response = account_module.createWallet(account,password)

    if 'error' in response:
        return render_template("register.html",
                               error=response['error'],
                               name=account,password=password,password_repeat=repeatPassword)
    
    
    # Set the cookie
    response = make_response(render_template("message.html",title="Account Created",
                                              content=f"Your account has been created. Here is your seed phrase. Please write it down and keep it safe as it will not be shown again<br><br>{response['seed']}"))
    response.set_cookie("account", account+":"+password)
    return response

@app.route('/import-wallet', methods=["POST"])
def import_wallet():
    # Get the account and password
    account = request.form.get("name")
    password = request.form.get("password")
    repeatPassword = request.form.get("password_repeat")
    seed = request.form.get("seed")

    if account == None or password == None or repeatPassword == None or seed == None:
        return render_template("import-wallet.html",
                               error="Invalid account, password or seed",
                               name=account,password=password,password_repeat=repeatPassword,
                               seed=seed)
    
    # Check if the passwords match
    if password != repeatPassword:
        return render_template("import-wallet.html",
                               error="Passwords do not match",
                               name=account,password=password,password_repeat=repeatPassword,
                               seed=seed)

    # Check if the account is valid
    if account.count(":") > 0:
        return render_template("import-wallet.html",
                               error="Invalid account",
                               name=account,password=password,password_repeat=repeatPassword,
                               seed=seed)

    # List wallets
    wallets = account_module.listWallets()
    if account in wallets:
        return render_template("import-wallet.html",
                               error="Account already exists",
                               name=account,password=password,password_repeat=repeatPassword,
                               seed=seed)
    
    # Create the account
    response = account_module.importWallet(account,password,seed)

    if 'error' in response:
        return render_template("import-wallet.html",
                               error=response['error'],
                               name=account,password=password,password_repeat=repeatPassword,
                               seed=seed)
    
    
    # Set the cookie
    response = make_response(redirect("/"))
    response.set_cookie("account", account+":"+password)
    return response

@app.route('/report')
def report():
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    account = account_module.check_account(request.cookies.get("account"))
    csv = '\n'.join(account_module.generateReport(account))
    # Create a download

    response = make_response(csv)
    response.headers["Content-Disposition"] = "attachment; filename=report.csv"
    response.headers["Content-Type"] = "text/csv"
    return response
    

#endregion

#region Plugins
@app.route('/plugins')
def plugins_index():
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")

    plugins = render.plugins(plugins_module.listPlugins(True))

    return render_template("plugins.html", account=account, 
                           
                           plugins=plugins)

@app.route('/plugin/<ptype>/<path:plugin>')
def plugin(ptype,plugin):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")

    plugin = f"{ptype}/{plugin}"

    if not plugins_module.pluginExists(plugin):
        print(f"Plugin {plugin} not found")
        return redirect("/plugins")

    data = plugins_module.getPluginData(plugin)

    functions = plugins_module.getPluginFunctions(plugin)
    functions = render.plugin_functions(functions,plugin)

    if data['verified'] == False:
        functions = "<div class='container-fluid'><div class='alert alert-warning' role='alert'>This plugin is not verified and is disabled for your protection. Please check the code before marking the plugin as verified <a href='/plugin/" + plugin + "/verify' class='btn btn-danger'>Verify</a></div></div>" + functions

    
    error = request.args.get("error")
    if error == None:
        error = ""

    return render_template("plugin.html", account=account, 
                           
                           name=data['name'],description=data['description'],
                           author=data['author'],version=data['version'],
                           source=data['source'],functions=functions,error=error)

@app.route('/plugin/<ptype>/<path:plugin>/verify')
def plugin_verify(ptype,plugin):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")
    
    plugin = f"{ptype}/{plugin}"

    if not plugins_module.pluginExists(plugin):
        return redirect("/plugins")

    data = plugins_module.getPluginData(plugin)

    if data['verified'] == False:
        plugins_module.verifyPlugin(plugin)

    return redirect("/plugin/" + plugin)

@app.route('/plugin/<ptype>/<path:plugin>/<function>', methods=["POST"])
def plugin_function(ptype,plugin,function):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return redirect("/login")

    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return redirect("/logout")

    plugin = f"{ptype}/{plugin}"

    if not plugins_module.pluginExists(plugin):
        return redirect("/plugins")

    data = plugins_module.getPluginData(plugin)

    # Get plugin/main.py listfunctions()
    if function in plugins_module.getPluginFunctions(plugin):
        inputs = plugins_module.getPluginFunctionInputs(plugin,function)
        request_data = {}
        for input in inputs:
            request_data[input] = request.form.get(input)
            
            if inputs[input]['type'] == "address":
                # Handle hip2
                address_check = account_module.check_address(request_data[input],True,True)
                if not address_check:
                    return redirect("/plugin/" + plugin + "?error=Invalid address")
                request_data[input] = address_check
            elif inputs[input]['type'] == "dns":
                # Handle URL encoding of DNS
                request_data[input] = urllib.parse.unquote(request_data[input])

        response = plugins_module.runPluginFunction(plugin,function,request_data,request.cookies.get("account"))
        if not response:
            return redirect("/plugin/" + plugin + "?error=An error occurred")
        if 'error' in response:
            return redirect("/plugin/" + plugin + "?error=" + response['error'])
        outputs = plugins_module.getPluginFunctionReturns(plugin,function)
        # Check outputs
        for output in outputs:
            if outputs[output]['type'] == "file":
                data = io.BytesIO(response[output].encode('utf-8'))
                return send_file(data, as_attachment=True, download_name=outputs[output]['name'])

        response = render.plugin_output(response,outputs)
        return render_template("plugin-output.html", account=account,name=data['name'],
                               description=data['description'],output=response)

    else:
        return jsonify({"error": "Function not found"})

#endregion

#region API Routes
@app.route('/api/v1/hsd/<function>', methods=["GET"])
def api_hsd(function):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return jsonify({"error": "Not logged in"})
    
    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return jsonify({"error": "Invalid account"})
    
    if function == "sync":
        return jsonify({"result": account_module.getNodeSync()})
    if function == "version":
        return jsonify({"result": account_module.hsdVersion(False)})
    if function == "height":
        return jsonify({"result": account_module.getBlockHeight()})
    if function == "mempool":
        return jsonify({"result": account_module.getMempoolTxs()})
    if function == "mempoolBids":
        return jsonify({"result": account_module.getMempoolBids()})
    if function == "nextAuctionState":
        # Get the domain from the query parameters
        domain = request.args.get('domain')
        if not domain:
            return jsonify({"error": "No domain specified"}), 400
        domainInfo = account_module.getDomain(domain)
        if 'error' in domainInfo and domainInfo['error'] != None:
            return jsonify({"error": domainInfo['error']}), 400
        stats = domainInfo['info']['stats'] if 'stats' in domainInfo['info'] else {}
        state = domainInfo['info']['state']
        next_action = ""
        next = ""
        if state == 'CLOSED':
            if not domainInfo['info']['registered']:
                if account_module.isOwnDomain(account,domain):
                    print("Waiting to be registered")
                    state = 'PENDING REGISTER'
                    next = "Pending Register"
                    next_action = f'<a href="/auction/{domain}/register">Register Domain</a>'
                
                else:
                    print("Not registered")
                    state = 'AVAILABLE'
                    next = "Available Now"
                    next_action = f'<a href="/auction/{domain}/open">Open Auction</a>'
            else:
                state = 'REGISTERED'
                expires = domainInfo['info']['stats']['daysUntilExpire']
                next = f"Expires in ~{expires} days"
        elif state == "REVOKED":
            next = "Available Now"
            next_action = f'<a href="/auction/{domain}/open">Open Auction</a>'
        elif state == 'OPENING':
            next = f"Bidding opens in {str(stats['blocksUntilBidding'])} blocks (~{blocks_to_time(stats['blocksUntilBidding'])})"
        elif state == 'BIDDING':
            next = f"Reveal in {stats['blocksUntilReveal']} blocks (~{blocks_to_time(stats['blocksUntilReveal'])})"
            if stats['blocksUntilReveal'] == 1:
                next += "<br>Bidding no longer possible"
            elif stats['blocksUntilReveal'] == 2:
                next += "<br>LAST CHANCE TO BID"
            elif stats['blocksUntilReveal'] == 3:
                next += f"<br>Next block is last chance to bid"
            elif stats['blocksUntilReveal'] < 6:
                next += f"<br>Last chance to bid in {stats['blocksUntilReveal']-2} blocks"


        elif state == 'REVEAL':
            next = f"Reveal ends in {str(stats['blocksUntilClose'])} blocks (~{blocks_to_time(stats['blocksUntilClose'])})"
            next_action = f'<a href="/auction/{domain}/reveal">Reveal All</a>'

        return jsonify({
            "state": state,
            "next": next,
            "next_action": next_action
        })


    return jsonify({"error": "Invalid function", "result": "Invalid function"}), 400


@app.route('/api/v1/hsd/<function>/mobile', methods=["GET"])
def api_hsd_mobile(function):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return jsonify({"error": "Not logged in"})
    
    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return jsonify({"error": "Invalid account"})
    
    if function == "sync":
        sync = account_module.getNodeSync()
        if sync == 100:
            # Don't show sync percentage on mobile
            sync = ""
        elif sync == -1:
            sync = "HSD Error"
        else:
            sync = f"{sync}%"
        return jsonify({"result": sync})
    
    return jsonify({"error": "Invalid function", "result": "Invalid mobile function"}), 400

@app.route('/api/v1/wallet/<function>', methods=["GET"])
def api_wallet(function):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return jsonify({"error": "Not logged in"})
    
    account = account_module.check_account(request.cookies.get("account"))
    if not account:
        return jsonify({"error": "Invalid account"})

    password = request.cookies.get("account","").split(":")[1]
    if not account:
        return jsonify({"error": "Invalid account"})
    
    if function == "sync":
        return jsonify({"result": account_module.getWalletStatus()})
    
    if function == "available":
        return jsonify({"result": account_module.getBalance(account)['available']})
    if function == "total":
        return jsonify({"result": account_module.getBalance(account)['total']})
    if function == "pending":
        return jsonify({"result": account_module.getPendingTX(account)})
    if function == "locked":
        return jsonify({"result": account_module.getBalance(account)['locked']})
    
    if function == "domainCount":
        return jsonify({"result": len(account_module.getDomains(account))})
    
    if function == "bidCount":
        return jsonify({"result": len(account_module.getBids(account))})
    
    if function == "pendingReveal":
        return jsonify({"result": account_module.getPendingReveals(account)})
    if function == "pendingRegister":
        return jsonify({"result": account_module.getPendingRegisters(account)})
    if function == "pendingRedeem":
        return jsonify({"result": account_module.getPendingRedeems(account,password)})
    
    if function == "pendingFinalize":
        return jsonify({"result": account_module.getPendingFinalizes(account,password)})

    if function == "domains":
        domains = account_module.getDomains(account)
        if type(domains) == dict and 'error' in domains:
            return jsonify({"result": [], "error": domains['error']})
        
        # Add nameRender to each domain
        for domain in domains:
            domain['nameRender'] = renderDomain(domain['name'])

        return jsonify({"result": domains})
    
    if function == "transactions":
        # Get the page parameter
        page = request.args.get('page', 1)
        try:
            page = int(page)
        except:
            page = 1

        if page < 1:
            page = 1
            
        # Check for force refresh parameter
        force_refresh = request.args.get('refresh') == 'true'
        
        # Create a cache key based on account and page
        cache_key = f"{account}_{page}"
        
        # Check if data is in cache and not expired
        current_time = time.time()
        if not force_refresh and cache_key in tx_cache and (current_time - tx_cache[cache_key]['time'] < TX_CACHE_TIMEOUT):
            transactions = tx_cache[cache_key]['data']
            txCount = len(transactions)
            transactions_html = render.transactions(transactions)
        else:
            # Fetch transactions from account module
            transactions = account_module.getTransactions(account, page)
            txCount = len(transactions)
            transactions_html = render.transactions(transactions)
            
            # Store in cache
            tx_cache[cache_key] = {
                'data': transactions,
                'html': transactions_html,
                'time': current_time
            }
        
        return jsonify({
            "html": transactions_html,
            "txCount": txCount,
            "page": page
        })

    if function == "domainBids":
        domain = request.args.get('domain')
        if not domain:
            return jsonify({"error": "No domain specified"}), 400
        bids = account_module.getBids(account,domain)
        if bids == []:
            return jsonify({"result": [], "error": "No bids found"}), 404
        else:
            reveals = account_module.getReveals(account,domain)
            for reveal in reveals:
                # Get TX
                revealInfo = account_module.getRevealTX(reveal)
                reveal['bid'] = revealInfo
            bids = render.bids(bids,reveals)
        return jsonify({"result": bids})

    if function == "icon":
        # Check if there is an icon
        if not os.path.exists(f'user_data/images'):
            return send_file('templates/assets/img/HNS.png')
        files = os.listdir(f'user_data/images')
        for file in files:
            if file.startswith(account):
                return send_file(f'user_data/images/{file}')
            
        return send_file('templates/assets/img/HNS.png')

    return jsonify({"error": "Invalid function", "result": "Invalid function"}), 400

@app.route('/api/v1/wallet/<function>/mobile', methods=["GET"])
def api_wallet_mobile(function):
    # Check if the user is logged in
    if request.cookies.get("account") is None:
        return jsonify({"error": "Not logged in"})
    
    account = account_module.check_account(request.cookies.get("account"))
    password = request.cookies.get("account","").split(":")[1]
    if not account:
        return jsonify({"error": "Invalid account"})
    
    if function == "sync":
        sync = account_module.getWalletStatus()
        if sync == "Ready":
            # Don't show sync percentage on mobile
            sync = ""
        elif sync == "Error wallet ahead of node":
            sync = "HSW Sync Error"
        else:
            sync = "HSW Syncing"        
        return jsonify({"result": sync})

    return jsonify({"error": "Invalid function", "result": "Invalid mobile function"}), 400
    
@app.route('/api/v1/icon/<account>')
def api_icon(account):
    if not os.path.exists(f'user_data/images'):
        return send_file('templates/assets/img/HNS.png')
    files = os.listdir(f'user_data/images')
    for file in files:
        if file.startswith(account):
            return send_file(f'user_data/images/{file}')
    return send_file('templates/assets/img/HNS.png')


@app.route('/api/v1/status')
def api_status():
    # This doesn't require a login
    # Check if the node is connected
    if not account_module.hsdConnected():
        return jsonify({"status":503,"error": "Node not connected"}), 503
    return jsonify({"status": 200,"result": "FireWallet is running"})


#endregion

#region Helper functions

def renderDomain(name: str) -> str:
    """
    Render a domain name with emojis and other special characters.
    """
    # Convert emoji to punycode
    try:
        rendered = name.encode("ascii").decode("idna") 
        if rendered == name:
            return f"{name}/"
        return f"{rendered}/ ({name})"


    except Exception as e:
        return f"{name}/"

#endregion


#region Assets and default pages
@app.route('/qr/<data>')
def qr(data):

    output = qrcode(data, mode="raw")
    if output is None:
        return jsonify({"error": "Invalid data"}), 400
    return send_file(output, mimetype="image/png")

# Theme
@app.route('/assets/css/styles.min.css')
def send_css():
    if THEME == "live":
        return send_from_directory('templates/assets/css', 'styles.min.css')
    return send_from_directory('themes', f'{THEME}.css')

@app.route('/assets/<path:path>')
def send_assets(path):
    return send_from_directory('templates/assets', path)

# Try path
@app.route('/<path:path>')
def try_path(path):
    # Check if node connected
    if not account_module.hsdConnected():
        return redirect("/login?message=Node not connected")

    if os.path.isfile("templates/" + path + ".html"):
        return render_template(path + ".html")
    else:
        return page_not_found(404)

@app.errorhandler(404)
def page_not_found(e):
    account = account_module.check_account(request.cookies.get("account"))

    return render_template('404.html',account=account), 404
#endregion

if __name__ == '__main__':
    #TODO add parsing to allow for custom port and host
    # Check to see if --debug is in the command line arguments
    if "--debug" in sys.argv:
        app.run(debug=True)
    else:
        app.run()
