import fcntl
import logging
import os
import shutil
import signal
import subprocess
import sys

from myst_connector.settings import get_path


logger = logging.getLogger(__name__)


LOCK_FILE = get_path('lock_file')


def run_quiet(args):
    # resolve command to absolute path where possible to avoid partial-path execution
    if isinstance(args, (list, tuple)) and args:
        cmd = args[0]
        if os.sep not in cmd:
            resolved = shutil.which(cmd)
            if resolved:
                args = [resolved] + list(args[1:])
    # args[0] is resolved to an absolute path when possible above; inputs are validated by callers
    return subprocess.run(
        args,
        check=True,
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
        stdin=subprocess.DEVNULL,
    )


def acquire_single_instance_lock(script_path):
    """Create and lock a PID file safely.

    Behaviour:
    - Create the lock file with restrictive permissions.
    - Attempt to acquire an exclusive flock. If another process holds the lock,
      read its PID and check if the process is alive and matches the current
      script path; if so, abort. If the PID is stale, remove the lock file and retry.
    - Write our PID into the locked file and return an open file handle.
    """
    lock_file_path = get_path('lock_file')
    os.makedirs(os.path.dirname(lock_file_path), exist_ok=True)

    flags = os.O_RDWR | os.O_CREAT
    if hasattr(os, 'O_NOFOLLOW'):
        flags |= getattr(os, 'O_NOFOLLOW')

    # open with restrictive mode
    fd = os.open(lock_file_path, flags, 0o600)

    try:
        fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
    except BlockingIOError:
        # another process holds the lock; inspect its PID
        try:
            # Read PID from the lock file without taking ownership of fd
            os.lseek(fd, 0, os.SEEK_SET)
            content = os.read(fd, 1024).decode().strip()
        except Exception:
            content = ''

        try:
            existing_pid = int(content) if content else None
        except Exception:
            existing_pid = None

        if existing_pid:
            try:
                os.kill(existing_pid, 0)
                # verify the process command line contains the script_path
                try:
                    with open(f'/proc/{existing_pid}/cmdline', 'r') as cmdf:
                        cmdline = cmdf.read()
                except Exception:
                    cmdline = ''

                if script_path and script_path in cmdline:
                    logger.info(f'Another instance is running (pid {existing_pid}), attempting to terminate it')
                    try:
                        os.kill(existing_pid, signal.SIGTERM)
                        logger.info(f'Sent SIGTERM to process {existing_pid}')
                    except ProcessLookupError:
                        logger.info(f'Process {existing_pid} already terminated')
                    except Exception as e:
                        logger.warning(f'Failed to terminate process {existing_pid}: {e}')
                    
                    # Remove the stale lock file and retry
                    os.close(fd)
                    try:
                        os.remove(lock_file_path)
                    except Exception as e:
                        logger.error(f'Cannot remove lock file after terminating process: {e}')
                        sys.exit(1)
                    return acquire_single_instance_lock(script_path)
                else:
                    # stale or unrelated PID — remove file and retry
                    os.close(fd)
                    try:
                        os.remove(lock_file_path)
                    except Exception:
                        logger.error('Cannot remove stale lock file; aborting')
                        sys.exit(1)
                    return acquire_single_instance_lock(script_path)
            except ProcessLookupError:
                # process not found; remove stale lock and retry
                os.close(fd)
                try:
                    os.remove(lock_file_path)
                except Exception:
                    logger.error('Failed to remove stale lock file')
                    sys.exit(1)
                return acquire_single_instance_lock(script_path)
        else:
            logger.error('Lock file exists but PID not present; aborting')
            sys.exit(1)

    # we have the lock; write our PID
    f = os.fdopen(fd, 'w')
    f.truncate(0)
    f.write(str(os.getpid()))
    f.flush()
    return f
