'''
SPDX-License-Identifier: Apache-2.0

Copyright 2023 Cisco Systems, Inc. and its affiliates

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
'''
"""
Start (or restart) the Network Sketcher Online web server.

Usage:
    python start_ns_online.py

All running ns_web_start.py processes are stopped first (including any
that were started outside of this script), then a fresh server is launched.
Works on Windows, Mac OS, and Linux.
"""

import subprocess
import sys
import os
import platform
import signal
import time
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent
ONLINE_DIR = BASE_DIR / 'network-sketcher_online'
SERVER_SCRIPT = ONLINE_DIR / 'ns_web_start.py'
PID_FILE = ONLINE_DIR / 'ns_online.pid'


def _find_all_ns_server_pids():
    """Return a list of PIDs for all running ns_web_start.py processes.

    Searches the OS process list directly so that processes started outside
    of this script (e.g. from a terminal or IDE) are also found.
    """
    own_pid = os.getpid()
    pids = []
    try:
        if platform.system() == 'Windows':
            result = subprocess.run(
                ['wmic', 'process', 'where',
                 '(CommandLine like "%ns_web_start.py%") AND (Name like "%python%")',
                 'get', 'ProcessId', '/VALUE'],
                capture_output=True, text=True,
            )
            for line in result.stdout.splitlines():
                line = line.strip()
                if line.startswith('ProcessId='):
                    try:
                        pid = int(line.split('=', 1)[1])
                        if pid and pid != own_pid:
                            pids.append(pid)
                    except ValueError:
                        pass
        else:
            result = subprocess.run(
                ['pgrep', '-f', 'ns_web_start.py'],
                capture_output=True, text=True,
            )
            for line in result.stdout.splitlines():
                try:
                    pid = int(line.strip())
                    if pid != own_pid:
                        pids.append(pid)
                except ValueError:
                    pass
    except Exception:
        pass

    # Also include any PID recorded in the PID file that was missed above
    if PID_FILE.exists():
        try:
            pid = int(PID_FILE.read_text().strip())
            if pid and pid != own_pid and pid not in pids:
                pids.append(pid)
        except (ValueError, OSError):
            pass

    return pids


def _is_process_running(pid):
    """Check if a process with the given PID is still running."""
    try:
        if platform.system() == 'Windows':
            result = subprocess.run(
                ['tasklist', '/FI', f'PID eq {pid}', '/NH'],
                capture_output=True, text=True,
            )
            return str(pid) in result.stdout
        else:
            os.kill(pid, 0)
            return True
    except (OSError, ProcessLookupError):
        return False


def _stop_all():
    """Stop all running ns_web_start.py processes. Returns True if any were stopped."""
    pids = _find_all_ns_server_pids()
    if not pids:
        return False

    print(f'  Found {len(pids)} running server process(es): {pids}')
    for pid in pids:
        print(f'  Stopping PID {pid}...')
        try:
            if platform.system() == 'Windows':
                subprocess.run(
                    ['taskkill', '/F', '/T', '/PID', str(pid)],
                    capture_output=True,
                )
            else:
                os.kill(pid, signal.SIGTERM)
        except (OSError, ProcessLookupError):
            pass

    # Wait up to 5 seconds for all processes to exit
    deadline = time.time() + 5.0
    while time.time() < deadline:
        time.sleep(0.3)
        if not any(_is_process_running(pid) for pid in pids):
            break

    PID_FILE.unlink(missing_ok=True)
    print('  All server processes stopped.')
    return True


def main():
    if not SERVER_SCRIPT.exists():
        print(f'Error: {SERVER_SCRIPT} not found.')
        sys.exit(1)

    print('=' * 56)
    print('  Network Sketcher Online — Start')
    print('=' * 56)

    _stop_all()

    log_dir = ONLINE_DIR / 'logs'
    log_dir.mkdir(exist_ok=True)
    server_log = log_dir / 'server.log'

    kwargs = {}
    log_file = open(str(server_log), 'a', encoding='utf-8')
    if platform.system() == 'Windows':
        kwargs['creationflags'] = (
            subprocess.DETACHED_PROCESS
            | subprocess.CREATE_NEW_PROCESS_GROUP
            | subprocess.CREATE_NO_WINDOW
        )
        kwargs['stdin'] = subprocess.DEVNULL
        kwargs['stdout'] = log_file
        kwargs['stderr'] = log_file
    else:
        kwargs['start_new_session'] = True
        kwargs['stdin'] = subprocess.DEVNULL
        kwargs['stdout'] = log_file
        kwargs['stderr'] = log_file

    proc = subprocess.Popen(
        [sys.executable, str(SERVER_SCRIPT)],
        cwd=str(ONLINE_DIR),
        **kwargs,
    )

    PID_FILE.write_text(str(proc.pid))

    print(f'  Server started (PID: {proc.pid})')
    print(f'  PID file: {PID_FILE}')
    print('=' * 56)


if __name__ == '__main__':
    main()
