#!/usr/bin/python3

# Copyright 2024-2025 Facundo Batista
# Licensed under the GPL v3 License
# For further info, check https://github.com/facundobatista/fuv

"""Main fuv script."""

import argparse
import logging
import os
import signal
import subprocess
import sys


# the signals to redirect to the child process (note: only these are
# allowed in Windows, see 'signal' doc).
REDIRECTED_SIGNALS = [
    signal.SIGABRT,
    signal.SIGFPE,
    signal.SIGILL,
    signal.SIGINT,
    signal.SIGSEGV,
    signal.SIGTERM,
]

HELP_EPILOG = """
The "child program" is the script that fades will execute. It's an
optional parameter, it will be the first thing received by fades that
is not a parameter.  If no child program is indicated, a Python
interactive interpreter will be opened.

The "child options" (everything after the child program) are
parameters passed as is to the child program.
"""

logger = logging.getLogger("fuv")

# TODO
#   fuv -d requests
#   ejecutar algo con
#        #!/usr/bin/fades
#            ,git.py
#        #!/usr/bin/fades --system-site-packages
#            transferencias-consult.py
#   fades -d algo --where
#   fades -d requests  <---- que abra el interprete pero autoimportaod!!!!
#   fades -r algo.txt
#   fades -d algo -x deadentro
#   que cuando es sólo el script, haga `uv run`
#
# META
#  - proyecto con tests
#  - que -V muestre versión de `uv`
#  - que "verbose" corra el mensaje que preparó y ponga `uv` en "normal"? "verbose"?


def parse_args():
    """Parse arguments as similar to fades as possible."""
    parser = argparse.ArgumentParser(
        prog="fuv", epilog=HELP_EPILOG, formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument(
        '-V', '--version', action='store_true',
        help="show version and info about the system, and exit")
    parser.add_argument(
        '-d', '--dependency', action='append',
        help="specify dependencies through command line (this option can be used multiple times)")
    # parser.add_argument(
    #     '-r', '--requirement', action='append',
    #     help="indicate files to read dependencies from (this option can be used multiple times)")
    parser.add_argument(
        '-p', '--python', action='store', help="specify the Python interpreter to use")
    # parser.add_argument(
    #     '--system-site-packages', action='store_true', default=False,
    #     help="give the virtual environment access to the system site-packages dir.")
    # parser.add_argument(
    #     '--where', '--get-venv-dir', action='store_true',
    #     help="show the virtualenv base directory (including the venv's UUID) and quit")
    # parser.add_argument(
    #     '-a', '--autoimport', action='store_true',
    #     help="automatically import the specified dependencies in the interactive mode "
    #          "(ignored otherwise).")

    mutexg = parser.add_mutually_exclusive_group()
    mutexg.add_argument(
        '-v', '--verbose', action='store_true',
        help="send all internal debugging lines to stderr, which may be very "
             "useful to debug any problem that may arise")
    mutexg.add_argument(
        '-q', '--quiet', action='store_true',
        help="don't show anything (unless it has a real problem), so the "
             "original script stderr is not polluted at all")

    mutexg = parser.add_mutually_exclusive_group()
    mutexg.add_argument(
        '-x', '--exec', dest='executable', action='store_true',
        help="execute the child_program (must be present) in the context of the virtualenv")
    mutexg.add_argument(
        '-m', '--module', action='store_true',
        help="run library module as a script (same behaviour than Python's -m parameter)")

    parser.add_argument('child_program', nargs='?', default=None)
    parser.add_argument('child_options', nargs=argparse.REMAINDER)

    args = parser.parse_args()

    # The --exec and --module flags needs child_program to exist (this is not handled at
    # argparse level because it's easier to collect the executable as the
    # normal child_program, so everything after that are parameteres
    # considered for the executable itself, not for fades).
    if args.executable and not args.child_program:
        parser.print_usage()
        print("fades: error: argument -x/--exec needs child_program to be present")
        exit(-1)
    if args.module and not args.child_program:
        parser.print_usage()
        print("fades: error: argument -m/--module needs child_program (module) to be present")
        exit(-1)

    return args


def get_uv_cmd(args):

    cmd = ["uv", "tool", "run", "--prerelease", "allow"]

    if args.python:
        cmd += ["--python", args.python]

    if args.dependency:
        dep_first, *dep_rest = args.dependency
        cmd += ["--from", dep_first]
        for dep in dep_rest:
            cmd += ["--with", dep]

    if args.module:
        cmd += ["python", "-m"]

    cmd.append(args.child_program)
    cmd.extend(args.child_options)
    return cmd


def main():
    args = parse_args()
    print("==== args", args)

    if args.version:
        print("fuv 0.1")
        exit(0)

    cmd = get_uv_cmd(args)
    try:
        proc = subprocess.Popen(cmd)
    except FileNotFoundError:
        logger.error("Command not found: %s", child_program)
        #raise FadesError("Command not found")
        exit()

    def _signal_handler(signum, _):
        """Handle signals received by parent process, send them to child."""
        logger.debug("Redirecting signal %s to child", signum)
        os.kill(proc.pid, signum)

    # redirect the useful signals
    for s in REDIRECTED_SIGNALS:
        signal.signal(s, _signal_handler)

    # wait child to finish, end
    rc = proc.wait()
    if rc:
        logger.debug("Child process not finished correctly: returncode=%d", rc)
    return rc


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