#!/usr/bin/env python3
"""Pure Python lock helper for concurrent builds.

Performance comparison alternative to bash ct-lock-helper.
Reuses tested locking.py implementation with identical algorithms.
"""

import argparse
import os
import signal
import subprocess
import sys
from types import SimpleNamespace


class GracefulExit:
    """Handle cleanup on signals."""

    def __init__(self):
        self.lock = None
        self.tempfile = None
        self.acquired = False

    def cleanup(self, signum=None, frame=None):
        """Clean up lock and temp file on exit."""
        if self.acquired and self.lock:
            try:
                self.lock.release()
            except Exception:
                pass

        if self.tempfile and os.path.exists(self.tempfile):
            try:
                os.unlink(self.tempfile)
            except Exception:
                pass

        if signum:
            sys.exit(128 + signum)


def create_args_from_env():
    """Create args object from environment variables matching bash version."""
    return SimpleNamespace(
        shared_objects=True,
        lock_warn_interval=int(os.getenv("CT_LOCK_WARN_INTERVAL", "30")),
        lock_cross_host_timeout=int(os.getenv("CT_LOCK_TIMEOUT", "600")),
        sleep_interval_lockdir=float(os.getenv("CT_LOCK_SLEEP_INTERVAL", "0.05")),
        sleep_interval_cifs=float(os.getenv("CT_LOCK_SLEEP_INTERVAL_CIFS", "0.1")),
        sleep_interval_flock_fallback=float(os.getenv("CT_LOCK_SLEEP_INTERVAL_FLOCK", "0.1")),
        verbose=int(os.getenv("CT_LOCK_VERBOSE", "0")),
    )


def create_lock(strategy, target_file, args):
    """Create appropriate lock instance based on strategy.

    Args:
        strategy: One of 'lockdir', 'cifs', 'flock'
        target_file: Target output file path
        args: Args object with lock configuration

    Returns:
        Lock instance (LockdirLock, CIFSLock, or FlockLock)
    """
    # Import here to reduce startup overhead if --help is requested
    from compiletools.locking import CIFSLock, FlockLock, LockdirLock

    if strategy == "lockdir":
        return LockdirLock(target_file, args)
    elif strategy == "cifs":
        return CIFSLock(target_file, args)
    elif strategy == "flock":
        return FlockLock(target_file, args)
    else:
        raise ValueError(f"Unknown strategy: {strategy}")


def execute_compile(lock, target, compile_cmd, exit_handler):
    """Execute compilation with locking.

    Args:
        lock: Lock instance
        target: Target output file
        compile_cmd: List of compile command arguments
        exit_handler: GracefulExit instance for cleanup

    Raises:
        subprocess.CalledProcessError: If compilation fails
    """
    # Generate temp file name (matches bash: target.PID.RANDOM.tmp)
    pid = os.getpid()
    random_suffix = os.urandom(2).hex()
    tempfile_path = f"{target}.{pid}.{random_suffix}.tmp"

    exit_handler.tempfile = tempfile_path
    exit_handler.lock = lock

    try:
        # Acquire lock
        lock.acquire()
        exit_handler.acquired = True

        # Execute compilation to temp file
        cmd = list(compile_cmd) + ["-o", tempfile_path]
        subprocess.run(cmd, check=True)

        # Atomic move
        os.rename(tempfile_path, target)

    finally:
        # Release lock
        if exit_handler.acquired:
            lock.release()
            exit_handler.acquired = False

        # Clean up temp file if it still exists
        if os.path.exists(tempfile_path):
            try:
                os.unlink(tempfile_path)
            except OSError:
                pass


def cmd_compile(args, exit_handler):
    """Handle 'compile' subcommand.

    Args:
        args: Parsed arguments
        exit_handler: GracefulExit instance
    """
    # Create args object from environment
    lock_args = create_args_from_env()

    # Create lock based on strategy
    lock = create_lock(args.strategy, args.target, lock_args)

    # Execute compilation with locking
    execute_compile(lock, args.target, args.compile_cmd, exit_handler)


def main(argv=None):
    """Main entry point.

    Args:
        argv: Command line arguments (default: sys.argv[1:])

    Returns:
        Exit code (0 for success, non-zero for failure)
    """
    if argv is None:
        argv = sys.argv[1:]

    # Set up signal handling
    exit_handler = GracefulExit()
    signal.signal(signal.SIGINT, exit_handler.cleanup)
    signal.signal(signal.SIGTERM, exit_handler.cleanup)

    # Parse arguments
    parser = argparse.ArgumentParser(
        prog="ct-lock-helper-py", description="File locking helper for concurrent builds (Python implementation)"
    )

    subparsers = parser.add_subparsers(dest="command", help="Command to execute")

    # Compile subcommand
    compile_parser = subparsers.add_parser("compile", help="Compile with file locking")
    compile_parser.add_argument("--target", required=True, help="Target output file (e.g., file.o)")
    compile_parser.add_argument(
        "--strategy",
        required=True,
        choices=["lockdir", "cifs", "flock"],
        help="Lock strategy: lockdir (NFS/GPFS/Lustre), cifs (CIFS/SMB), flock (local)",
    )
    compile_parser.add_argument("compile_cmd", nargs="+", help="Compile command and arguments")

    # Parse
    args = parser.parse_args(argv)

    if not args.command:
        parser.print_help()
        return 1

    # Execute command
    try:
        if args.command == "compile":
            cmd_compile(args, exit_handler)
        return 0
    except subprocess.CalledProcessError as e:
        # Compilation failed, return compiler's exit code
        return e.returncode
    except Exception as e:
        print(f"ERROR: {e}", file=sys.stderr)
        return 125  # Helper internal error (matches bash)


if __name__ == "__main__":
    sys.exit(main())
