import os
from pathlib import Path
from subprocess import Popen
from time import sleep
import sys
from otree.main import send_termination_notice
from .base import BaseCommand

print_function = print


def get_mtimes(files) -> dict:
    mtimes = {}
    for p in files:
        try:
            mtimes[p] = p.stat().st_mtime
        except FileNotFoundError:
            pass
    return mtimes


class Command(BaseCommand):
    def add_arguments(self, parser):
        parser.add_argument('port', nargs='?', default='8000')

    def handle(self, port):
        run_reloader(port)


_OTREE_CORE_DEV = os.getenv('OTREE_CORE_DEV')


def run_reloader(port):
    '''
    better to have my own autoreloader so i can easily swap between daphne/hypercorn/uvicorn
    '''

    proc = Popen(['otree', 'devserver_inner', port])

    root = Path('.')
    files_to_watch = list(root.glob('*.py')) + list(root.glob('*/*.py'))
    if _OTREE_CORE_DEV:
        # this code causes it to get stuck on proc.wait() for some reason
        # 2021-09-05: is this why it got stuck?
        files_to_watch.extend(Path('c:/otree/nodj/otree').glob('**/*.py'))
    mtimes = get_mtimes(files_to_watch)
    is_windows_venv = sys.platform.startswith("win") and sys.prefix != sys.base_prefix
    try:
        while True:
            exit_code = proc.poll()
            if exit_code is not None:
                return exit_code
            new_mtimes = get_mtimes(files_to_watch)
            changed_file = None
            for f in files_to_watch:
                if f in new_mtimes and f in mtimes and new_mtimes[f] != mtimes[f]:
                    changed_file = f
                    break
            if changed_file:
                print_function(changed_file, 'changed, restarting')
                mtimes = new_mtimes
                child_pid = send_termination_notice(port)

                # with Windows + virtualenv, proc.terminate() doesn't work.
                # sys.executable is a wrapper
                # so the actual process that is binding the port has a different pid.
                if is_windows_venv and not child_pid:
                    for retry_num in [1, 2, 3]:
                        print_function('Retrying shutdown', retry_num)
                        sleep(1)
                        child_pid = send_termination_notice(port)
                        if child_pid:
                            break
                    if not child_pid:
                        print_function('Failed to shut down')

                # child_pid is not guaranteed to be returned, so we need proc.terminate()
                proc.terminate()
                if child_pid:
                    os.kill(child_pid, 9)
                proc = Popen(['otree', 'devserver_inner', port, '--is-reload'])
            sleep(1)
    except KeyboardInterrupt:
        # handle KeyboardInterrupt (KBI) so we don't get a traceback to console.
        # The KBI is received first by the subprocess and then by the parent process.
        # Python's usual behavior is to wait until a subprocess exits before propagating the KBI
        # to the parent process.
        # but for some reason in this program, I got console output from the subprocess that seemed to come
        # after the main process exited. so, we wait.
        proc.wait(2)
