#!/usr/bin/env python3
"""
Cross-platform development server script.
Starts pnpm CSS watcher and Django dev server, handling cleanup on exit.
Works on both Windows and Unix systems.
"""

import atexit
import shutil
import signal
import subprocess
import sys
import time
import os
import dotenv


def find_pnpm():
    """
    Find pnpm executable on the system.
    Returns the path to pnpm or None if not found.
    """
    # Try to find pnpm using shutil.which
    # On Windows, this will find pnpm.cmd if it's in PATH
    pnpm_path = shutil.which("pnpm")

    if pnpm_path:
        return pnpm_path

    # On Windows, also try pnpm.cmd explicitly
    if sys.platform == "win32":
        pnpm_cmd = shutil.which("pnpm.cmd")
        if pnpm_cmd:
            return pnpm_cmd

    return None


class DevServerManager:
    """Manages background processes for development server."""

    def __init__(self):
        self.processes = []
        self._cleanup_registered = False

    def start_process(self, command, description, shell=False):
        """Start a background process and return the process object."""
        print(f"Starting {description}...")
        try:
            if shell:
                # Use shell=True for commands that need shell interpretation
                process = subprocess.Popen(
                    command,
                    shell=True,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.STDOUT,
                    text=True,
                    bufsize=1,
                )
            else:
                # Split command into list for subprocess
                process = subprocess.Popen(
                    command,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.STDOUT,
                    text=True,
                    bufsize=1,
                )
            self.processes.append((process, description))
            print(" ".join(command))
            print(f"✓ Started {description} (PID: {process.pid})")
            return process
        except Exception as e:
            print(f"✗ Failed to start {description}: {e}", file=sys.stderr)
            self.cleanup()
            sys.exit(1)

    def cleanup(self):
        """Terminate all running processes."""
        if not self.processes:
            return

        print("\nShutting down...")
        for process, description in self.processes:
            if process.poll() is None:  # Process is still running
                try:
                    # Try graceful termination first
                    if sys.platform == "win32":
                        process.terminate()
                    else:
                        process.send_signal(signal.SIGTERM)

                    # Wait up to 5 seconds for graceful shutdown
                    try:
                        process.wait(timeout=5)
                    except subprocess.TimeoutExpired:
                        # Force kill if graceful shutdown failed
                        if sys.platform == "win32":
                            process.kill()
                        else:
                            process.send_signal(signal.SIGKILL)
                        process.wait()

                    print(f"✓ {description} stopped")
                except Exception as e:
                    print(f"✗ Error stopping {description}: {e}", file=sys.stderr)

        self.processes.clear()

    def register_cleanup(self):
        """Register cleanup handlers for various exit scenarios."""
        if self._cleanup_registered:
            return

        self._cleanup_registered = True

        # Register atexit handler (works on all platforms)
        atexit.register(self.cleanup)

        # Register signal handlers (Unix only)
        if sys.platform != "win32":
            signal.signal(signal.SIGINT, self._signal_handler)
            signal.signal(signal.SIGTERM, self._signal_handler)

    def _signal_handler(self, signum, frame):
        """Handle Unix signals."""
        self.cleanup()
        sys.exit(0)

    def wait_for_process(self, process, description):
        """Wait for a process to finish and handle its output."""
        try:
            # Stream output from the process
            for line in iter(process.stdout.readline, ""):
                if line:
                    print(f"[{description}] {line.rstrip()}")

            process.wait()
            return process.returncode
        except KeyboardInterrupt:
            # Handle Ctrl+C
            self.cleanup()
            sys.exit(0)
        except Exception as e:
            print(f"Error waiting for {description}: {e}", file=sys.stderr)
            self.cleanup()
            sys.exit(1)


def main():
    """Main entry point."""
    dotenv.load_dotenv()
    manager = DevServerManager()
    manager.register_cleanup()

    # Find pnpm executable
    pnpm_path = find_pnpm()
    if not pnpm_path:
        print("Error: pnpm not found in PATH.", file=sys.stderr)
        print("\nPlease install pnpm:", file=sys.stderr)
        print("  Windows: https://pnpm.io/installation#on-windows", file=sys.stderr)
        print("  Unix: https://pnpm.io/installation#on-posix-systems", file=sys.stderr)
        sys.exit(1)

    # Determine shell usage based on platform
    use_shell = sys.platform == "win32"

    # Start pnpm CSS watcher
    # Use the found pnpm path to ensure it works on Windows
    pnpm_command = f'"{pnpm_path}" run dev' if use_shell else [pnpm_path, "run", "dev"]
    manager.start_process(
        pnpm_command,
        "CSS watcher",
        shell=use_shell,
    )

    # Give pnpm a moment to start
    time.sleep(1)

    # Start Django dev server
    port = os.environ.get("DJANGO_PORT", "8000")
    django_process = manager.start_process(
        ["uv", "run", "python", "manage.py", "runserver", f"0:{port}"],
        f"Django server on port {port}",
        shell=False,
    )

    print("\nDevelopment servers are running. Press Ctrl+C to stop.\n")

    try:
        # Wait for Django server (main process)
        # If Django exits, we should clean up everything
        return_code = manager.wait_for_process(django_process, "Django")

        # If Django exited unexpectedly, clean up and exit
        if return_code != 0:
            manager.cleanup()
            sys.exit(return_code)
    except KeyboardInterrupt:
        # Ctrl+C was pressed
        manager.cleanup()
        sys.exit(0)


if __name__ == "__main__":
    main()
