#!/usr/bin/env python3
"""
Roundtrip tests for various formats:
- wasot -> pretty -> binary -> wasot -> pretty
- wasot -> pretty -> jsonl -> wasot -> pretty
- wasot -> pretty -> xml -> wasot -> pretty

Verifies that the two pretty-printed outputs are identical for each format.

Usage:
    python3 roundtrip_test.py                    # Uses ./target/release/wasot
    python3 roundtrip_test.py -e /path/to/wasot  # Use specific binary
"""
# ruff: noqa: T201

import argparse
import re
import shlex
import subprocess
import sys
import tempfile
from pathlib import Path

# Global wasot command (set by main)
WASOT_CMD: list[str] = ["./target/release/wasot"]


def run_wasot(*args: str) -> subprocess.CompletedProcess:
    """Run wasot command and return result."""
    cmd = [*WASOT_CMD, *args]
    return subprocess.run(cmd, capture_output=True, text=True)


def normalize_content(content: str) -> str:
    """Normalize content for comparison by replacing auto-generated IDs."""
    # Replace document ID line (./id <random>) with a placeholder
    # The ID is auto-generated on each pretty print, so we normalize it
    # Use MULTILINE flag so ^ matches at start of string
    return re.sub(r"^\./id [a-z0-9]+", "./id NORMALIZED", content, count=1, flags=re.MULTILINE)


def compare_files(content1: str, content2: str, format_name: str) -> bool:
    """Compare two file contents and report differences if any."""
    # Normalize auto-generated IDs before comparison
    norm1 = normalize_content(content1)
    norm2 = normalize_content(content2)

    if norm1 == norm2:
        return True

    lines1 = norm1.splitlines()
    lines2 = norm2.splitlines()

    # Find first differing line
    first_diff = None
    for i, (l1, l2) in enumerate(zip(lines1, lines2)):
        if l1 != l2:
            first_diff = i
            break
    if first_diff is None and len(lines1) != len(lines2):
        first_diff = min(len(lines1), len(lines2))

    print(f"  FAIL [{format_name}]: Files differ ({len(content1)} vs {len(content2)} bytes)")
    if first_diff is not None:
        print(f"  First difference at line {first_diff + 1}:")
        if first_diff < len(lines1):
            print(f"    orig: {lines1[first_diff]!r}")
        else:
            print(f"    orig: <EOF>")
        if first_diff < len(lines2):
            print(f"    trip: {lines2[first_diff]!r}")
        else:
            print(f"    trip: <EOF>")
    return False


def roundtrip_binary(wasot_file: Path, tmpdir: Path, pretty1: Path) -> bool:
    """Test binary roundtrip: pretty -> binary -> pretty."""
    binary_file = tmpdir / "converted.wasotb"
    pretty2 = tmpdir / "pretty2_binary.wasot"

    # Convert to binary format
    result = run_wasot(str(pretty1), "-t", "binary", "-o", str(binary_file))
    if result.returncode != 0:
        print(f"  FAIL [binary]: Binary conversion failed: {result.stderr}")
        return False

    # Convert binary back to wasot and pretty print
    result = run_wasot("-f", "binary", str(binary_file), "-o", str(pretty2))
    if result.returncode != 0:
        print(f"  FAIL [binary]: Binary to wasot failed: {result.stderr}")
        return False

    # Compare the two pretty-printed files
    return compare_files(pretty1.read_text(), pretty2.read_text(), "binary")


def roundtrip_jsonl(wasot_file: Path, tmpdir: Path, pretty1: Path) -> bool:
    """Test JSONL roundtrip: pretty -> jsonl -> pretty."""
    jsonl_file = tmpdir / "converted.jsonl"
    pretty2 = tmpdir / "pretty2_jsonl.wasot"

    # Convert to JSONL format
    result = run_wasot(str(pretty1), "-t", "jsonl", "-o", str(jsonl_file))
    if result.returncode != 0:
        print(f"  FAIL [jsonl]: JSONL conversion failed: {result.stderr}")
        return False

    # Convert JSONL back to wasot and pretty print
    result = run_wasot("-f", "jsonl", str(jsonl_file), "-o", str(pretty2))
    # Check both stdout and stderr for error messages (wasot may output errors to stdout)
    output_combined = result.stdout + result.stderr
    if result.returncode != 0 or "error" in output_combined.lower():
        print(f"  FAIL [jsonl]: JSONL to wasot failed: {output_combined.strip()}")
        return False

    # Compare the two pretty-printed files
    return compare_files(pretty1.read_text(), pretty2.read_text(), "jsonl")


def roundtrip_xml(wasot_file: Path, tmpdir: Path, pretty1: Path) -> bool:
    """Test XML roundtrip: pretty -> xml -> pretty."""
    xml_file = tmpdir / "converted.xml"
    pretty2 = tmpdir / "pretty2_xml.wasot"

    # Convert to XML format
    result = run_wasot(str(pretty1), "-t", "xml", "-o", str(xml_file))
    if result.returncode != 0:
        print(f"  FAIL [xml]: XML conversion failed: {result.stderr}")
        return False

    # Convert XML back to wasot and pretty print
    result = run_wasot("-f", "xml", str(xml_file), "-o", str(pretty2))
    # Check both stdout and stderr for error messages (wasot may output errors to stdout)
    output_combined = result.stdout + result.stderr
    if result.returncode != 0 or "error" in output_combined.lower():
        print(f"  FAIL [xml]: XML to wasot failed: {output_combined.strip()}")
        return False

    # Compare the two pretty-printed files
    return compare_files(pretty1.read_text(), pretty2.read_text(), "xml")


def roundtrip_test(wasot_file: Path) -> dict[str, bool]:
    """
    Test roundtrip conversion for a single file using all formats.
    Returns a dict mapping format name to success status.
    """
    results = {}

    with tempfile.TemporaryDirectory() as tmpdir:
        tmpdir = Path(tmpdir)
        pretty1 = tmpdir / "pretty1.wasot"

        # Step 1: Pretty print original wasot file
        result = run_wasot(str(wasot_file), "-o", str(pretty1))
        if result.returncode != 0:
            print(f"  FAIL: Pretty print failed: {result.stderr}")
            return {"binary": False, "jsonl": False, "xml": False}

        # Test each format
        results["binary"] = roundtrip_binary(wasot_file, tmpdir, pretty1)
        results["jsonl"] = roundtrip_jsonl(wasot_file, tmpdir, pretty1)
        results["xml"] = roundtrip_xml(wasot_file, tmpdir, pretty1)

    return results


def main():
    global WASOT_CMD

    parser = argparse.ArgumentParser(description="Roundtrip tests for wasot formats.")
    parser.add_argument(
        "-e", "--executable",
        help="Path to wasot executable (default: ./target/release/wasot)",
        default="./target/release/wasot",
    )
    args = parser.parse_args()

    WASOT_CMD = shlex.split(args.executable)

    # Find all wasot files in examples directory
    examples_dir = Path(__file__).parent.parent / "examples"
    wasot_files = sorted(examples_dir.glob("**/*.wasot"))

    if not wasot_files:
        print("No .wasot files found in examples directory")
        sys.exit(1)

    formats = ["binary", "jsonl", "xml"]
    print(f"Testing roundtrip for {len(wasot_files)} files ({', '.join(formats)})...\n")

    # Track results per format
    passed = {fmt: 0 for fmt in formats}
    failed = {fmt: 0 for fmt in formats}

    for wasot_file in wasot_files:
        relative_path = wasot_file.relative_to(examples_dir)
        print(f"Testing {relative_path}...", end=" ")

        results = roundtrip_test(wasot_file)

        # Collect status for each format
        status_parts = []
        all_ok = True
        for fmt in formats:
            if results.get(fmt, False):
                passed[fmt] += 1
                status_parts.append(f"{fmt}:OK")
            else:
                failed[fmt] += 1
                status_parts.append(f"{fmt}:FAIL")
                all_ok = False

        if all_ok:
            print("OK")
        else:
            print(f"[{', '.join(status_parts)}]")

    # Print summary
    print("\nResults:")
    total_failed = 0
    for fmt in formats:
        total = passed[fmt] + failed[fmt]
        print(f"  {fmt}: {passed[fmt]}/{total} passed")
        total_failed += failed[fmt]

    if total_failed > 0:
        sys.exit(1)


if __name__ == "__main__":
    main()
