#!/usr/bin/env python3
import argparse
from pathlib import Path
from typing import List, NamedTuple
import shutil
import subprocess
import sys

ROOT = Path(__file__).parent.parent.absolute()


class TestOptions(NamedTuple):
    exe: Path
    toolchain: str


def run_test_dir(dir: Path, opts: TestOptions) -> bool:
    fails = 0
    fails += _run_subproc_test(
        dir,
        opts,
        'Full Build',
        'build',
        '--full',
        f'--toolchain={opts.toolchain}',
    )
    fails += _run_subproc_test(
        dir,
        opts,
        'Source Distribution',
        'sdist',
        'create',
        f'--out=_build/{dir.stem}/test.dsd',
        '--replace',
    )
    return fails == 0


def _run_subproc_test(dir: Path, opts: TestOptions, name: str,
                      *args: str) -> int:
    print(f'Running test: {dir.stem} - {name} ', end='')
    out_dir = dir / '_build'
    if out_dir.exists():
        shutil.rmtree(out_dir)
    res = subprocess.run(
        [
            str(opts.exe),
        ] + list(str(s) for s in args),
        cwd=dir,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
    )
    if res.returncode != 0:
        print('- FAILED')
        print(f'Test failed with exit code '
              f'[{res.returncode}]:\n{res.stdout.decode()}')
        return 1
    print('- PASSED')
    return 0


def _run_build_test(dir: Path, opts: TestOptions) -> int:
    print(f'Running test: {dir.stem} - build', end='')
    out_dir = dir / '_build'
    if out_dir.exists():
        shutil.rmtree(out_dir)
    res = subprocess.run(
        [
            str(opts.exe),
            'build',
            '--export',
            '--warnings',
            '--tests',
            '--full',
            f'--toolchain={opts.toolchain}',
            f'--out={out_dir}',
            f'--export-name={dir.stem}',
        ],
        cwd=dir,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
    )
    if res.returncode != 0:
        print('- FAILED')
        print(f'Test failed with exit code '
              f'[{res.returncode}]:\n{res.stdout.decode()}')
        return 1
    print('- PASSED')
    return 0


def run_tests(opts: TestOptions) -> int:
    print('Sanity check...')
    subprocess.check_output([str(opts.exe), '--help'])
    tests_subdir = ROOT / 'tests'

    test_dirs = tests_subdir.glob('*.test')
    ret = 0
    for td in test_dirs:
        if not run_test_dir(td, opts):
            ret = 1
    return ret


def bootstrap_self(opts: TestOptions):
    # Copy the exe to another location, as windows refuses to let a binary be
    # replaced while it is executing
    new_exe = ROOT / '_dds.bootstrap-test.exe'
    shutil.copy2(opts.exe, new_exe)
    res = subprocess.run([
        str(new_exe),
        'build',
        f'-FT{opts.toolchain}',
    ])
    new_exe.unlink()
    if res.returncode != 0:
        print('The bootstrap test failed!', file=sys.stderr)
        return False
    print('Bootstrap test PASSED!')
    return True


def main(argv: List[str]) -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--exe',
        '-e',
        help='Path to the dds executable to test',
        required=True)
    parser.add_argument(
        '--toolchain',
        '-T',
        help='The dds toolchain to use while testing',
        required=True,
    )
    parser.add_argument(
        '--skip-bootstrap-test',
        action='store_true',
        help='Skip the self-bootstrap test',
    )
    args = parser.parse_args(argv)

    tc = args.toolchain
    if not tc.startswith(':'):
        tc = Path(tc).absolute()
    opts = TestOptions(exe=Path(args.exe).absolute(), toolchain=tc)

    if not args.skip_bootstrap_test and not bootstrap_self(opts):
        return 2

    return run_tests(opts)


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
