#!/usr/bin/env python3
# Copyright (c) The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://opensource.org/license/mit/.

import argparse
import os
import shlex
import subprocess
import sys
from pathlib import Path

sys.path.append(str(Path(__file__).resolve().parent.parent / "test"))
from download_utils import download_script_assets


def run(cmd, **kwargs):
    print("+ " + shlex.join(cmd), flush=True)
    kwargs.setdefault("check", True)
    try:
        return subprocess.run(cmd, **kwargs)
    except Exception as e:
        sys.exit(str(e))


def print_version():
    bitcoind = Path.cwd() / "bin" / "bitcoind.exe"
    run([str(bitcoind), "-version"])


def check_manifests():
    release_dir = Path.cwd() / "bin"
    manifest_path = release_dir / "bitcoind.manifest"

    cmd_bitcoind_manifest = [
        "mt.exe",
        "-nologo",
        f"-inputresource:{release_dir / 'bitcoind.exe'}",
        f"-out:{manifest_path}",
    ]
    run(cmd_bitcoind_manifest)
    print(manifest_path.read_text())

    skipped = {  # Skip as they currently do not have manifests
        "fuzz.exe",
        "bench_bitcoin.exe",
    }
    for entry in release_dir.iterdir():
        if entry.suffix.lower() != ".exe":
            continue
        if entry.name in skipped:
            print(f"Skipping {entry.name} (no manifest present)")
            continue
        print(f"Checking {entry.name}")
        run(["mt.exe", "-nologo", f"-inputresource:{entry}", "-validate_manifest"])


def prepare_tests():
    workspace = Path.cwd()
    config_path = workspace / "test" / "config.ini"
    rpcauth_path = workspace / "share" / "rpcauth" / "rpcauth.py"
    replacements = {
        "SRCDIR=": f"SRCDIR={workspace}",
        "BUILDDIR=": f"BUILDDIR={workspace}",
        "RPCAUTH=": f"RPCAUTH={rpcauth_path}",
    }
    lines = config_path.read_text().splitlines()
    for index, line in enumerate(lines):
        for prefix, new_value in replacements.items():
            if line.startswith(prefix):
                lines[index] = new_value
                break
    content = "\n".join(lines) + "\n"
    config_path.write_text(content)
    print(content)
    previous_releases_dir = Path(os.environ["PREVIOUS_RELEASES_DIR"])
    cmd_download_prev_rel = [
        sys.executable,
        str(workspace / "test" / "get_previous_releases.py"),
        "--target-dir",
        str(previous_releases_dir),
    ]
    run(cmd_download_prev_rel)
    run([sys.executable, "-m", "pip", "install", "pyzmq"])

    dest = workspace / "unit_test_data"
    download_script_assets(dest)


def run_functional_tests():
    workspace = Path.cwd()
    num_procs = str(os.process_cpu_count())
    test_runner_cmd = [
        sys.executable,
        str(workspace / "test" / "functional" / "test_runner.py"),
        "--jobs",
        num_procs,
        "--quiet",
        f"--tmpdirprefix={workspace}",
        "--combinedlogslen=99999999",
        *shlex.split(os.environ.get("TEST_RUNNER_EXTRA", "").strip()),
        # feature_unsupported_utxo_db.py fails on Windows because of emojis in the test data directory.
        "--exclude",
        "feature_unsupported_utxo_db.py",
        # See https://github.com/bitcoin/bitcoin/issues/31409.
        "--exclude",
        "wallet_multiwallet.py",
    ]
    run(test_runner_cmd)

    # Run feature_unsupported_utxo_db sequentially in ASCII-only tmp dir,
    # because it is excluded above due to lack of UTF-8 support in the
    # ancient release.
    cmd_feature_unsupported_db = [
        sys.executable,
        str(workspace / "test" / "functional" / "feature_unsupported_utxo_db.py"),
        "--previous-releases",
        "--tmpdir",
        str(Path(workspace) / "test_feature_unsupported_utxo_db"),
    ]
    run(cmd_feature_unsupported_db)


def run_unit_tests():
    workspace = Path.cwd()
    os.environ["DIR_UNIT_TEST_DATA"] = str(workspace / "unit_test_data")
    # Can't use ctest here like other jobs as we don't have a CMake build tree.
    commands = [
        ["./bin/test_bitcoin-qt.exe"],
        # Intentionally run sequentially here, to catch test case failures caused by dirty global state from prior test cases:
        ["./bin/test_bitcoin.exe", "-l", "test_suite"],
        ["./src/secp256k1/bin/exhaustive_tests.exe"],
        ["./src/secp256k1/bin/noverify_tests.exe"],
        ["./src/secp256k1/bin/tests.exe"],
        ["./src/univalue/object.exe"],
        ["./src/univalue/unitester.exe"],
    ]
    for cmd in commands:
        run(cmd)


def main():
    parser = argparse.ArgumentParser(description="Utility to run Windows CI steps.")
    steps = list(map(lambda f: f.__name__, [
        print_version,
        check_manifests,
        prepare_tests,
        run_unit_tests,
        run_functional_tests,
    ]))
    parser.add_argument("step", choices=steps, help="CI step to perform.")
    args = parser.parse_args()

    os.environ.setdefault(
        "PREVIOUS_RELEASES_DIR",
        str(Path.cwd() / "previous_releases"),
    )

    exec(f'{args.step}()')


if __name__ == "__main__":
    main()
