"""
End-to-end validation script for the brownan solver wrapper.

Generates random scrambles with pycuber, feeds the resulting 54-character
kociemba-order facelet string to both the cahidenes and brownan wrappers,
then verifies each solution actually solves the cube by re-applying it in pycuber.
"""
import argparse
import os
import random
import subprocess
import sys
from pathlib import Path

try:
    from pycuber import Cube, Formula
except ImportError:
    print("ERROR: pycuber package not installed. Run: pip install pycuber")
    sys.exit(1)

try:
    import kociemba
except ImportError:
    print("ERROR: kociemba package not installed. Run: pip install kociemba")
    sys.exit(1)


# Map pycuber colour names to single uppercase letters used in kociemba facelets.
# The wrapper only requires that the 6 centres are distinct, so any consistent
# mapping works.
PYCUBER_TO_CHAR = {
    'red': 'L',
    'orange': 'R',
    'green': 'F',
    'blue': 'B',
    'white': 'D',
    'yellow': 'U',
}

FACES = ["U", "D", "F", "B", "L", "R"]
SUFFIXES = ["", "'", "2"]
N_SCRAMBLES = 10
SCRAMBLE_DEPTH = 12


def facelet_string_from_cube(cube: Cube) -> str:
    """Build a 54-char kociemba-order facelet string from a pycuber Cube."""
    out = []
    for f in "URFDLB":
        face = cube.get_face(f)
        for row in face:
            for cell in row:
                out.append(PYCUBER_TO_CHAR[cell.colour])
    return "".join(out)


def random_scramble(depth: int = SCRAMBLE_DEPTH) -> str:
    return " ".join(random.choice(FACES) + random.choice(SUFFIXES) for _ in range(depth))


def inverse_moves(moves_str: str) -> list[str]:
    rev = list(reversed(moves_str.strip().split()))
    out = []
    for m in rev:
        if m.endswith("'"):
            out.append(m[:-1])
        elif m.endswith("2"):
            out.append(m)
        else:
            out.append(m + "'")
    return out


def is_solved(cube: Cube) -> bool:
    for f in "URFDLB":
        face = cube.get_face(f)
        colours = {cell.colour for row in face for cell in row}
        if len(colours) != 1:
            return False
    return True


def solve_with_wrapper(wrapper_path: Path, facelets: str) -> list[str]:
    result = subprocess.run(
        ["python.exe", str(wrapper_path), facelets],
        capture_output=True,
        text=True,
        timeout=300,
        cwd=str(wrapper_path.parent),
    )
    if result.returncode != 0:
        print(f"  {wrapper_path.name} error (rc={result.returncode}): {result.stderr.strip()}")
        return []
    output = result.stdout.strip()
    if not output:
        return []
    return output.split()


def verify_wca_moves(moves: list[str]) -> bool:
    faces = set("URFDLB")
    for m in moves:
        if not m:
            return False
        if m[0] not in faces:
            return False
        if len(m) == 2 and m[1] not in ("'", "2"):
            return False
        if len(m) > 2:
            return False
    return True


def main():
    parser = argparse.ArgumentParser(description="Validate brownan solver end-to-end")
    parser.add_argument("--brownan-wrapper", default=None, help="Path to brownan_solver_wrapper.py")
    parser.add_argument("--cahidenes-wrapper", default=None, help="Path to cahidenes_solver_wrapper.py")
    parser.add_argument("--count", type=int, default=N_SCRAMBLES, help="Number of random scrambles to test")
    parser.add_argument("--depth", type=int, default=SCRAMBLE_DEPTH, help="Scramble depth")
    parser.add_argument("--seed", type=int, default=None, help="Random seed for reproducibility")
    args = parser.parse_args()

    solvers_dir = Path("C:/HyperTwist/UnrealHyperTwist/Binaries/Win64/Solvers")
    brownan_wrapper = Path(args.brownan_wrapper) if args.brownan_wrapper else solvers_dir / "brownan_solver_wrapper.py"
    cahidenes_wrapper = Path(args.cahidenes_wrapper) if args.cahidenes_wrapper else solvers_dir / "cahidenes_solver_wrapper.py"

    if not brownan_wrapper.exists():
        print(f"ERROR: Brownan wrapper not found at {brownan_wrapper}")
        sys.exit(1)
    if not cahidenes_wrapper.exists():
        print(f"ERROR: Cahidenes wrapper not found at {cahidenes_wrapper}")
        sys.exit(1)

    if args.seed is not None:
        random.seed(args.seed)

    all_passed = True
    for i in range(args.count):
        print(f"\n=== Test {i+1}/{args.count} ===")
        scramble = random_scramble(args.depth)
        print(f"Scramble: {scramble}")

        c = Cube()
        c(Formula(scramble))
        facelets = facelet_string_from_cube(c)
        print(f"Facelets: {facelets}")

        # Sanity-check with kociemba
        try:
            canonical = kociemba.solve(facelets)
            print(f"Canonical ({len(canonical.split())} moves): {canonical}")
        except Exception as e:
            print(f"FAIL: kociemba rejected facelets: {e}")
            all_passed = False
            continue

        cahidenes_moves = solve_with_wrapper(cahidenes_wrapper, facelets)
        brownan_moves = solve_with_wrapper(brownan_wrapper, facelets)

        print(f"Cahidenes ({len(cahidenes_moves)} moves): {' '.join(cahidenes_moves)}")
        print(f"Brownan   ({len(brownan_moves)} moves): {' '.join(brownan_moves)}")

        cah_ok = True
        brn_ok = True

        if not verify_wca_moves(cahidenes_moves):
            print("FAIL: Cahidenes returned invalid WCA moves")
            cah_ok = False
            all_passed = False
        if not verify_wca_moves(brownan_moves):
            print("FAIL: Brownan returned invalid WCA moves")
            brn_ok = False
            all_passed = False

        if cah_ok:
            check = Cube()
            check(Formula(scramble))
            check(Formula(" ".join(cahidenes_moves)))
            if not is_solved(check):
                print("FAIL: Cahidenes solution did not solve the cube")
                cah_ok = False
                all_passed = False
            else:
                print("PASS: Cahidenes solution verified")

        if brn_ok:
            check = Cube()
            check(Formula(scramble))
            check(Formula(" ".join(brownan_moves)))
            if not is_solved(check):
                print("FAIL: Brownan solution did not solve the cube")
                brn_ok = False
                all_passed = False
            else:
                print("PASS: Brownan solution verified")

    print("\n" + ("ALL TESTS PASSED" if all_passed else "SOME TESTS FAILED"))
    sys.exit(0 if all_passed else 1)


if __name__ == "__main__":
    main()
