from decimal import Decimal
from functools import cache
import json
from flask import (
    Flask,
    make_response,
    redirect,
    request,
    jsonify,
    render_template,
    send_from_directory,
    send_file,
)
import os
import json
import requests
from datetime import datetime
import dotenv
import hsd
import indexerClasses
import db
from datetime import datetime, timezone
from indexerClasses import Block, Transaction, Covenant


dotenv.load_dotenv()

app = Flask(__name__)

dbCon = db.DBConnection()

def find(name, path):
    for root, dirs, files in os.walk(path):
        if name in files:
            return os.path.join(root, name)

# region Main routes
@app.route("/")
def index():
    # txs = hsd.get_mempool()
    
    # mempool_info = hsd.get_mempool_info()
    # if mempool_info['result']:
    #     mempool_info = mempool_info['result']

    # mempool_info['txs'] = txs
    return render_template("index.html")

@app.route("/search")
def search():
    if not request.args.get("q"):
        return render_template("index.html")
    query = request.args.get("q")
    tx = dbCon.getTransaction(query)
    if tx:
        return redirect(f"/tx/{query}")
    block = dbCon.getBlock(query)
    if block:
        return redirect(f"/block/{query}")
    block = dbCon.getBlockHash(query)
    if block:
        return redirect(f"/block/{query}")
    name = hsd.get_name(query)
    if not name['error']:
        return redirect(f"/name/{query}")
    return render_template("index.html",message="No results found",query=query)
    

@app.route("/name")
def name():
    if request.args.get("name"):
        name = request.args.get("name")
        data = hsd.get_name(name)
        dns = hsd.get_name_resource(name)
        data = json.dumps(data, indent=4) + "<br><br>" + json.dumps(dns, indent=4)
        return render_template("data.html", data=data)
    else:
        return render_template("index.html")
    
@app.route("/tx/<tx>")
def txPage(tx):
    tx = dbCon.getTransaction(tx)
    if tx:
        return render_template("tx.html", tx=tx)
    return render_template("index.html",message="Transaction not found")
    
@app.route("/block/<block>")
def blockPage(block):
    block = dbCon.getBlock(block)
    if block:
        return render_template("block.html", block=block)
    return render_template("index.html",message="Block not found") 

@app.route("/address/<address>")
def addressPage(address):
    # if address:
    #     return render_template("address.html", address=address)
    return render_template("index.html",message="Not implemented")


@app.route("/<path:path>")
def catch_all(path: str):
    if os.path.isfile("templates/" + path):
        return render_template(path)

    # Try with .html
    if os.path.isfile("templates/" + path + ".html"):
        return render_template(path + ".html")

    if os.path.isfile("templates/" + path.strip("/") + ".html"):
        return render_template(path.strip("/") + ".html")

    # Try to find a file matching
    if path.count("/") < 1:
        # Try to find a file matching
        filename = find(path, "templates")
        if filename:
            return send_file(filename)

    return render_template("404.html"), 404


# endregion


# region API routes
@app.route("/api/v1/version")
def api_version():
    return jsonify({"version": "1.0.0"})

@app.route("/api/v1/tx/<txid>")
def api_tx(txid):
    tx = dbCon.getTransaction(txid)
    if tx:
        return jsonify(tx.toJSON())
    else:
        return jsonify({"error": "tx not found"}), 404
    
@app.route("/api/v1/block/<blockheight>")
def api_block(blockheight):
    block = dbCon.getBlock(blockheight)
    if block:
        print("Found block from height")
        return jsonify(block.toJSON())
    
    block = dbCon.getBlockHash(blockheight)
    if block:
        print("Found block from hash")
        return jsonify(block.toJSON())

    return jsonify({"error": "block not found"}), 404

@app.route("/api/v1/name/<name>")
def api_name(name):
    name = hsd.get_name(name)
    if name:
        return jsonify(name)
    else:
        return jsonify({"error": "name not found"}), 404
    
@app.route("/api/v1/name/<name>/resource")
@app.route("/api/v1/name/<name>/dns")
@app.route("/api/v1/resource/<name>")
@app.route("/api/v1/dns/<name>")
def api_name_resource(name):
    name = hsd.get_name_resource(name)
    if name:
        return jsonify(name)
    else:
        return jsonify({"error": "name not found"}), 404

@app.route("/api/v1/address/<address>")
def api_address(address):
    address = hsd.get_address(address)
    if address:
        return jsonify(address)
    else:
        return jsonify({"error": "address not found"}), 404

@app.route("/api/v1/mempool")
def api_mempool():
    mempool = hsd.get_mempool()
    if mempool:
        return jsonify(mempool)
    else:
        return jsonify({"error": "mempool not found"}), 404
    
@app.route("/api/v1/mempool/info")
def api_mempool_info():
    mempool_info = hsd.get_mempool_info()
    if mempool_info:
        return jsonify(mempool_info)
    else:
        return jsonify({"error": "mempool info not found"}), 404

# endregion

# region Error Catching
# 404 catch all
@app.errorhandler(404)
def not_found(e):
    return render_template("404.html"), 404

# endregion
# region Assets routes
@app.route("/assets/<path:path>")
def send_assets(path):
    if path.endswith(".json"):
        return send_from_directory(
            "templates/assets", path, mimetype="application/json"
        )

    if os.path.isfile("templates/assets/" + path):
        return send_from_directory("templates/assets", path)

    # Try looking in one of the directories
    filename: str = path.split("/")[-1]
    if (
        filename.endswith(".png")
        or filename.endswith(".jpg")
        or filename.endswith(".jpeg")
        or filename.endswith(".svg")
    ):
        if os.path.isfile("templates/assets/img/" + filename):
            return send_from_directory("templates/assets/img", filename)
        if os.path.isfile("templates/assets/img/favicon/" + filename):
            return send_from_directory("templates/assets/img/favicon", filename)

    return render_template("404.html"), 404

@app.route("/favicon.png")
def faviconPNG():
    return send_from_directory("templates/assets/img", "favicon.png")


@app.route("/.well-known/<path:path>")
def wellknown(path):
    # Try to proxy to https://nathan.woodburn.au/.well-known/
    req = requests.get(f"https://nathan.woodburn.au/.well-known/{path}")
    return make_response(
        req.content, 200, {"Content-Type": req.headers["Content-Type"]}
    )
@app.template_filter('datetimeformat')
def datetimeformat(value, format='%Y-%m-%d %H:%M:%S'):
    return datetime.fromtimestamp(value, tz=timezone.utc).strftime(format)

@app.template_filter('convert_bits_to_difficulty')
def convert_bits_to_difficulty(bits):
    """Convert compact bits format to difficulty."""
    bits_hex = f"{bits:08x}"  # Convert to 8-char hex string
    exp = int(bits_hex[:2], 16)  # First byte (exponent)
    coeff = int(bits_hex[2:], 16)  # Remaining 3 bytes (coefficient)

    # Compute target from bits
    target = coeff * (256 ** (exp - 3))

    # Maximum target (difficulty 1)
    max_target = 0xFFFF * (256 ** (0x1D - 3))

    # Compute difficulty
    difficulty = Decimal(max_target) / Decimal(target)
    return f"{difficulty:,.0f}"

@app.template_filter('hexToAscii')
def hexToAscii(hex_string):
    # Convert the hex string to bytes
    bytes_obj = bytes.fromhex(hex_string)
    # Decode the bytes object to an ASCII string
    ascii_string = bytes_obj.decode('ascii')
    return ascii_string

@app.template_filter('getTX')
def getTX(txid):
    tx = dbCon.getTransaction(txid)
    if tx:
        return tx.toJSON()
    else:
        return {"error": "tx not found"}
    
@app.template_filter("parse_covenant")
def parse_covenant(covenant):
    covenant = Covenant(covenant)
    if covenant.name:
        return f"{covenant.action} {covenant.name}"
    elif covenant.type == 0:
        return f"{covenant.action}"
    
    name = dbCon.getNameByHash(covenant.nameHash)
    if name:
        return f"{covenant.action} {name}"
    return f"{covenant.action} Unknown Name"


# endregion

if __name__ == "__main__":
    app.run(debug=True, port=5000, host="0.0.0.0")
