#!/usr/bin/env python3
"""
Process Manager for Paradisomatch

Manages server processes (HTTP and API) with proper tracking and cleanup:
- Track PIDs in .pids/ directory
- Auto-cleanup on failure
- Health checks
- Graceful shutdown
"""

import subprocess
import signal
import os
import time
import requests
from pathlib import Path

class ProcessManager:
    """Manage Paradisomatch server processes"""

    def __init__(self, pids_dir='.pids'):
        self.pids_dir = Path(pids_dir)
        self.pids_dir.mkdir(exist_ok=True)

        self.processes = {
            'http': {
                'name': 'HTTP Server',
                'port': 8000,
                'command': ['python3', '-m', 'http.server', '8000'],
                'pid_file': self.pids_dir / 'http.pid',
                'health_url': 'http://localhost:8000/'
            },
            'api': {
                'name': 'API Server',
                'port': 5001,
                'command': ['python3', 'criteria_api.py'],
                'pid_file': self.pids_dir / 'api.pid',
                'health_url': 'http://localhost:5001/api/properties'
            }
        }

    def is_port_in_use(self, port):
        """Check if a port is in use"""
        try:
            result = subprocess.run(
                ['lsof', '-ti', f':{port}'],
                capture_output=True,
                text=True
            )
            return bool(result.stdout.strip())
        except Exception:
            return False

    def get_pid_from_port(self, port):
        """Get PID of process using a port"""
        try:
            result = subprocess.run(
                ['lsof', '-ti', f':{port}'],
                capture_output=True,
                text=True
            )
            pid_str = result.stdout.strip()
            return int(pid_str) if pid_str else None
        except Exception:
            return None

    def save_pid(self, service, pid):
        """Save PID to file"""
        pid_file = self.processes[service]['pid_file']
        with open(pid_file, 'w') as f:
            f.write(str(pid))

    def load_pid(self, service):
        """Load PID from file"""
        pid_file = self.processes[service]['pid_file']
        if pid_file.exists():
            try:
                with open(pid_file, 'r') as f:
                    return int(f.read().strip())
            except Exception:
                pass
        return None

    def is_process_running(self, pid):
        """Check if a process is running"""
        if not pid:
            return False
        try:
            os.kill(pid, 0)  # Signal 0 = check if process exists
            return True
        except (ProcessLookupError, PermissionError):
            return False

    def kill_process(self, pid, timeout=5):
        """Kill a process gracefully, force if needed"""
        if not pid or not self.is_process_running(pid):
            return True

        try:
            # Try SIGTERM first (graceful)
            os.kill(pid, signal.SIGTERM)
            time.sleep(1)

            # Check if stopped
            if not self.is_process_running(pid):
                return True

            # Wait a bit more
            for _ in range(timeout):
                if not self.is_process_running(pid):
                    return True
                time.sleep(1)

            # Force kill with SIGKILL
            os.kill(pid, signal.SIGKILL)
            time.sleep(0.5)

            return not self.is_process_running(pid)

        except Exception as e:
            print(f"  ⚠️ Error killing PID {pid}: {e}")
            return False

    def start_service(self, service):
        """Start a service"""
        config = self.processes[service]
        port = config['port']

        # Check if already running
        if self.is_port_in_use(port):
            pid = self.get_pid_from_port(port)
            print(f"⚠️  {config['name']} already running on port {port} (PID: {pid})")
            if pid:
                self.save_pid(service, pid)
            return False

        # Start process
        try:
            process = subprocess.Popen(
                config['command'],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                preexec_fn=os.setsid  # Create new process group
            )

            # Save PID
            self.save_pid(service, process.pid)

            # Wait a moment for startup
            time.sleep(2)

            # Verify it's running
            if self.is_process_running(process.pid):
                print(f"✅ {config['name']} started on port {port} (PID: {process.pid})")
                return True
            else:
                print(f"❌ {config['name']} failed to start")
                return False

        except Exception as e:
            print(f"❌ Error starting {config['name']}: {e}")
            return False

    def stop_service(self, service):
        """Stop a service"""
        config = self.processes[service]

        # Try PID file first
        pid = self.load_pid(service)

        # Fallback to port lookup
        if not pid or not self.is_process_running(pid):
            pid = self.get_pid_from_port(config['port'])

        if pid:
            print(f"🛑 Stopping {config['name']} (PID: {pid})...")
            success = self.kill_process(pid)

            if success:
                print(f"✅ {config['name']} stopped")
                # Remove PID file
                if config['pid_file'].exists():
                    config['pid_file'].unlink()
                return True
            else:
                print(f"❌ Failed to stop {config['name']}")
                return False
        else:
            print(f"⚠️  {config['name']} not running")
            return True

    def check_health(self, service):
        """Check if a service is healthy"""
        config = self.processes[service]

        # Check if port is in use
        if not self.is_port_in_use(config['port']):
            return False

        # Try HTTP health check
        try:
            response = requests.get(config['health_url'], timeout=2)
            return response.status_code < 500
        except Exception:
            return False

    def get_status(self):
        """Get status of all services"""
        statuses = {}

        for service, config in self.processes.items():
            pid = self.load_pid(service)
            running = self.is_port_in_use(config['port'])
            healthy = self.check_health(service) if running else False

            statuses[service] = {
                'name': config['name'],
                'port': config['port'],
                'pid': pid,
                'running': running,
                'healthy': healthy
            }

        return statuses

    def start_all(self):
        """Start all services"""
        print("="*70)
        print("STARTING FARMMATCH SERVICES")
        print("="*70)

        for service in self.processes.keys():
            self.start_service(service)

        print("\n" + "="*70)
        self.print_status()

    def stop_all(self):
        """Stop all services"""
        print("="*70)
        print("STOPPING FARMMATCH SERVICES")
        print("="*70)

        for service in self.processes.keys():
            self.stop_service(service)

        print("\n✅ All services stopped")

    def restart_all(self):
        """Restart all services"""
        self.stop_all()
        time.sleep(1)
        self.start_all()

    def cleanup_zombies(self):
        """Clean up zombie processes on specified ports"""
        print("🧹 Cleaning up zombie processes...")

        cleaned = 0
        for service, config in self.processes.items():
            port = config['port']
            pid = self.get_pid_from_port(port)

            if pid:
                print(f"  Found process on port {port} (PID: {pid})")
                if self.kill_process(pid):
                    print(f"  ✅ Killed PID {pid}")
                    cleaned += 1
                else:
                    print(f"  ❌ Failed to kill PID {pid}")

        if cleaned:
            print(f"\n✅ Cleaned up {cleaned} zombie process(es)")
        else:
            print(f"\n✅ No zombie processes found")

    def print_status(self):
        """Print status of all services"""
        print("📊 SERVICE STATUS")
        print("="*70)

        statuses = self.get_status()

        for service, status in statuses.items():
            name = status['name']
            port = status['port']
            running = "✅ Running" if status['running'] else "❌ Stopped"
            healthy = "✅ Healthy" if status['healthy'] else "⚠️  Unhealthy"

            print(f"{name:20} Port {port:5} {running:15} {healthy}")

            if status['pid']:
                print(f"{'':20} PID: {status['pid']}")


# CLI Interface
def main():
    import argparse

    parser = argparse.ArgumentParser(description='Paradisomatch Process Manager')
    parser.add_argument('action', choices=['start', 'stop', 'restart', 'status', 'cleanup'],
                       help='Action to perform')

    args = parser.parse_args()

    pm = ProcessManager()

    if args.action == 'start':
        pm.start_all()
    elif args.action == 'stop':
        pm.stop_all()
    elif args.action == 'restart':
        pm.restart_all()
    elif args.action == 'status':
        pm.print_status()
    elif args.action == 'cleanup':
        pm.cleanup_zombies()


if __name__ == '__main__':
    main()
