#!/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 sys
import os
import argparse
import subprocess
import signal
import tempfile
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 LockdirLock, CIFSLock, FlockLock

    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())
