"""Verify the frozen duplicate-payment evidence offline; Python3 standard library.

Usage: python verify.py.txt [package-directory]
Does not start Core, contact any server or write to the package directory.
The transparent transaction decoder is intentionally limited to this fixture.
It is not a consensus verifier or a tool for authorizing a real payment.
"""
from decimal import Decimal
from pathlib import Path
import csv
import hashlib
import io
import json
import sys

CHECKS = 0


def check(name, condition):
    global CHECKS
    CHECKS += 1
    if not condition:
        raise AssertionError(name)


def atoms(value):
    number = Decimal(str(value)) * 100000000
    check("LTC amount is an exact whole litoshi", number == number.to_integral_value())
    return int(number)


def decode(raw_hex):
    raw = bytes.fromhex(raw_hex)
    if len(raw) > 1000000:
        raise ValueError("Fixture exceeds decoder byte budget")
    offset = 0

    def take(count):
        nonlocal offset
        if count < 0 or offset + count > len(raw):
            raise ValueError("Truncated transaction")
        part = raw[offset:offset + count]
        offset += count
        return part

    def compact():
        first = take(1)[0]
        if first < 253:
            return first
        size = {253: 2, 254: 4, 255: 8}[first]
        value = int.from_bytes(take(size), "little")
        if value < {253: 253, 254: 65536, 255: 4294967296}[first]:
            raise ValueError("Noncanonical compact size")
        if value > 1000000:
            raise ValueError("Compact-size fixture budget exceeded")
        return value

    version = take(4)
    witness = raw[offset:offset + 1] == b"\x00"
    if witness and take(2) != b"\x00\x01":
        raise ValueError("Only ordinary transparent witness serialization is supported")
    inputs_start = offset
    count = compact()
    if count < 1 or count > 1000:
        raise ValueError("Input count outside fixture budget")
    inputs = []
    for _ in range(count):
        txid, vout = take(32)[::-1].hex(), int.from_bytes(take(4), "little")
        take(compact())
        take(4)
        inputs.append({"txid": txid, "vout": vout})
    inputs_bytes = raw[inputs_start:offset]
    outputs_start = offset
    output_count = compact()
    if output_count < 1 or output_count > 1000:
        raise ValueError("Output count outside fixture budget")
    outputs = []
    for _ in range(output_count):
        outputs.append({"atoms": int.from_bytes(take(8), "little"), "script_hex": take(compact()).hex()})
    outputs_bytes = raw[outputs_start:offset]
    if witness:
        for _ in range(count):
            stack_count = compact()
            if stack_count > 1000:
                raise ValueError("Witness stack outside fixture budget")
            for _ in range(stack_count):
                take(compact())
    locktime = take(4)
    if offset != len(raw):
        raise ValueError("Unexpected extension or trailing bytes")
    stripped = version + inputs_bytes + outputs_bytes + locktime
    txid = hashlib.sha256(hashlib.sha256(stripped).digest()).digest()[::-1].hex()
    return {"txid": txid, "inputs": inputs, "outputs": outputs, "raw_sha256": hashlib.sha256(raw).hexdigest()}


def main(folder):
    checksums = folder / "SHA256SUMS.txt"
    if checksums.is_file():
        for line in checksums.read_text(encoding="utf-8").splitlines():
            if not line.strip():
                continue
            expected, name = line.split("  ", 1)
            target = (folder / name).resolve()
            check("Manifest stays within package", target.is_relative_to(folder.resolve()))
            check("SHA256 " + name, hashlib.sha256(target.read_bytes()).hexdigest() == expected)
    else:
        raise FileNotFoundError("SHA256SUMS.txt is required")
    results = json.loads((folder / "results.json").read_text(encoding="utf-8"))
    transactions = json.loads((folder / "transactions.json").read_text(encoding="utf-8"))
    check("Results transaction copy matches independent file", results["transactions"] == transactions)
    check("Recorded Core release", results["core_version"] == "/LitecoinCore:0.21.5.8/")
    check("Only regtest", results["network"] == "regtest")
    check("Two distinct payments", len(transactions) == 2 and len({row["txid"] for row in transactions}) == 2)
    total_fee = 0
    identities = set()
    for payment in transactions:
        parsed = decode(payment["raw_hex"])
        check("Recomputed txid " + payment["payment_id"], parsed["txid"] == payment["txid"])
        check("Raw bytes checksum " + payment["payment_id"], parsed["raw_sha256"] == payment["raw_sha256"])
        target = parsed["outputs"][payment["recipient_vout"]]
        check("Recipient script independently decoded", target["script_hex"] == payment["recipient_script_hex"])
        check("Recipient native witness script", target["script_hex"].startswith("0014") and len(target["script_hex"]) == 44)
        check("Intended recipient amount0.125LTC", target["atoms"] == atoms(payment["recipient_value_ltc"]) == 12500000)
        check("All outputs agree with independent decoder", [row["atoms"] for row in parsed["outputs"]] == [atoms(value) for value in payment["all_outputs_ltc"]])
        check("Recorded input identities match raw", parsed["inputs"] == [{"txid": row["txid"], "vout": row["vout"]} for row in payment["inputs"]])
        input_atoms = 0
        for row in payment["inputs"]:
            previous = decode(row["previous_raw_hex"])
            check("Previous transaction hash independently derived", previous["txid"] == row["txid"])
            value = previous["outputs"][row["vout"]]["atoms"]
            check("Prevout value derived from raw bytes", value == atoms(row["value_ltc"]))
            input_atoms += value
        fee = input_atoms - sum(row["atoms"] for row in parsed["outputs"])
        check("Fee derived from all input/output amounts", fee == atoms(payment["fee_ltc"]) == 1410)
        check("Same local invoice comment", payment["sender_comment"] == results["invoice_comment"])
        check("Observed confirmations positive", payment["final_confirmations"] >= 1)
        total_fee += fee
        identities.add((payment["txid"], payment["recipient_vout"]))
    check("Two independent recipient output identities", len(identities) == 2)
    check("Final summed fee2820litoshi", total_fee == atoms(results["summary"]["total_miner_fees_ltc"]) == 2820)
    check("Final recipient0.25LTC", atoms(results["summary"]["recipient_total_ltc"]) == 25000000)
    first_id = transactions[0]["txid"]
    mempool, confirmed = results["rebroadcasts"]
    check("Observed mempool rebroadcast returns original hash", mempool["response"] == first_id)
    check("Observed confirmed rebroadcast error preserved", confirmed["response"]["kind"] == "rpc_error" and confirmed["response"]["code"] == -27 and "Transaction already in block chain" in confirmed["response"]["message"])
    stages = results["stages"]
    expected = [(0, 0, 0, 0), (12500000, 0, 1, 1), (12500000, 0, 1, 1), (12500000, 12500000, 1, 0), (12500000, 12500000, 1, 0), (25000000, 12500000, 2, 1), (25000000, 25000000, 2, 0)]
    check("Seven declared stages", len(stages) == len(expected))
    csv_rows = list(csv.DictReader(io.StringIO((folder / "stages.csv").read_text(encoding="utf-8"))))
    check("CSV stage count", len(csv_rows) == len(stages))
    for stage, csv_row, state in zip(stages, csv_rows, expected):
        all_received, confirmed_received, output_count, mempool_count = state
        check("Stage total " + stage["stage"], atoms(stage["recipient_received_minconf0_ltc"]) == all_received)
        check("Confirmed-only total " + stage["stage"], atoms(stage["recipient_received_minconf1_ltc"]) == confirmed_received)
        check("Stage output count " + stage["stage"], len(stage["recipient_unspent_outputs"]) == output_count)
        check("Stage mempool count " + stage["stage"], len(stage["mempool_txids"]) == mempool_count)
        check("Zero peers at every stage", stage["peer_count"] == 0)
        check("CSV totals and identities match JSON", csv_row["stage"] == stage["stage"] and atoms(csv_row["recipient_received_minconf0_ltc"]) == all_received and int(csv_row["recipient_output_count"]) == output_count)
    check("Final receipt identities match decoded transactions", {(row["txid"], row["vout"]) for row in stages[-1]["recipient_unspent_outputs"]} == identities)
    check("All experiment assertions passed", all(row["passed"] is True for row in results["assertions"]) and len(results["assertions"]) == results["assertions_passed"] == 38)
    check("Own node exited cleanly and temporary directory removed", results["cleanup"] == {"own_node_stopped": True, "fresh_directory_removed": True, "exit_code": 0})
    calls = json.loads((folder / "rpc-trace.json").read_text(encoding="utf-8"))["calls"]
    send_calls = [row for row in calls if row["method"] == "sendtoaddress"]
    raw_calls = [row for row in calls if row["method"] == "sendrawtransaction"]
    check("Exactly two wallet Send operations", len(send_calls) == 2)
    check("Same address, amount and comment in both Send calls", send_calls[0]["params"] == send_calls[1]["params"])
    check("No fee subtraction", send_calls[0]["params"][4] == "false")
    check("Exactly two rebroadcast calls use identical original bytes", len(raw_calls) == 2 and all(row["params"][0] == transactions[0]["raw_hex"] for row in raw_calls))
    forbidden = {"dumpprivkey", "dumpwallet", "backupwallet", "gethdkeys", "listdescriptors", "getaddressinfo", "importprivkey", "importwallet"}
    check("No export or import of private wallet material", not forbidden.intersection(row["method"] for row in calls))
    print(json.dumps({"success": True, "checks_passed": CHECKS, "recipient_litoshi": 25000000, "miner_fee_litoshi": total_fee, "distinct_outputs": len(identities), "mode": "offline verification of frozen regtest evidence"}))


if __name__ == "__main__":
    main(Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else Path(__file__).resolve().parent)
