#!/usr/bin/env python3
"""
SkillMate - Windows-optimierter Haupteinstiegspunkt
"""

import os
import sys
import subprocess
import time
import webbrowser
import signal
import atexit
import argparse
from pathlib import Path
from typing import Optional, List

class SkillMateStarter:
    def __init__(self, mode: str = 'dev'):
        self.processes = []
        self.base_dir = Path(__file__).parent.absolute()
        self.mode = mode  # 'dev' oder 'prod'
        # Ports (Backend/Frontend/Admin)
        self.backend_port = int(os.environ.get('PORT', '3004'))
        self.frontend_port = 5173
        self.admin_port = 5174
        
        # Registriere Cleanup-Handler
        atexit.register(self.cleanup)
        if sys.platform != 'win32':
            signal.signal(signal.SIGINT, self.signal_handler)
            signal.signal(signal.SIGTERM, self.signal_handler)

    def _has_local_binary(self, project_dir: Path, binary: str) -> bool:
        """Prüft, ob ein lokales NPM-Binary vorhanden ist (Windows .cmd / Unix ohne Endung)."""
        bin_dir = project_dir / 'node_modules' / '.bin'
        if sys.platform == 'win32':
            return (bin_dir / f'{binary}.cmd').exists() or (bin_dir / f'{binary}.CMD').exists()
        else:
            return (bin_dir / binary).exists()

    def _ensure_dependencies(self) -> bool:
        """Installiert fehlende Node-Abhängigkeiten in backend, frontend, admin-panel.

        Rückgabe: True bei Erfolg, False wenn Installation irreparabel fehlschlägt.
        """
        projects = [
            (self.base_dir / 'backend', ['nodemon', 'ts-node']),
            (self.base_dir / 'frontend', ['vite']),
            (self.base_dir / 'admin-panel', ['vite'])
        ]

        any_install = False
        for proj_dir, required_bins in projects:
            if not proj_dir.exists():
                continue

            needs_install = not (proj_dir / 'node_modules').exists()
            if not needs_install:
                # Prüfe konkrete Binaries
                for b in required_bins:
                    if not self._has_local_binary(proj_dir, b):
                        needs_install = True
                        break

            if needs_install:
                any_install = True
                print(f"🔧 Installiere Abhängigkeiten in {proj_dir.name}...")
                # Bevorzugt npm ci, fallback auf npm install
                try:
                    cmd_ci = ["cmd", "/c", "npm ci"] if sys.platform == 'win32' else ["npm", "ci"]
                    result = subprocess.run(cmd_ci, cwd=str(proj_dir), check=False)
                    if result.returncode != 0:
                        cmd_install = ["cmd", "/c", "npm install"] if sys.platform == 'win32' else ["npm", "install"]
                        result = subprocess.run(cmd_install, cwd=str(proj_dir), check=False)
                        if result.returncode != 0:
                            print(f"❌ Konnte Abhängigkeiten in {proj_dir.name} nicht installieren. Prüfen Sie Internet/Proxy.")
                            return False
                except Exception as e:
                    print(f"❌ Fehler bei der Installation in {proj_dir.name}: {e}")
                    return False

        if any_install:
            print("✅ Abhängigkeiten installiert. Fahre mit dem Start fort...\n")
        else:
            print("✅ Abhängigkeiten geprüft – alles vorhanden.\n")
        return True

    def signal_handler(self, signum, frame):
        """Handle Strg+C und andere Signale"""
        print("\n\n🛑 SkillMate wird beendet...")
        self.cleanup()
        sys.exit(0)
        
    def cleanup(self):
        """Beende alle gestarteten Prozesse"""
        for process in self.processes:
            try:
                if process.poll() is None:  # Prozess läuft noch
                    if sys.platform == 'win32':
                        subprocess.call(['taskkill', '/F', '/T', '/PID', str(process.pid)], 
                                      stdout=subprocess.DEVNULL, 
                                      stderr=subprocess.DEVNULL)
                    else:
                        process.terminate()
                        process.wait(timeout=5)
            except:
                pass
        # Sicherheitshalber Ports freigeben, falls noch Prozesse hängen
        if sys.platform == 'win32':
            for p in [self.backend_port, self.frontend_port, self.admin_port]:
                self._free_port_windows(p)

    def _popen_new_console(self, workdir: Path, command: str) -> Optional[subprocess.Popen]:
        try:
            creationflag = getattr(subprocess, 'CREATE_NEW_CONSOLE', 0x00000010)
            p = subprocess.Popen(
                ["cmd", "/k", command],
                cwd=str(workdir),
                creationflags=creationflag,
            )
            self.processes.append(p)
            return p
        except Exception as e:
            print(f"❌ Konnte Konsole nicht starten: {e}")
            return None

    def _free_port_windows(self, port: int):
        """Versucht Prozesse auf Port zu beenden (Windows)."""
        try:
            # Finde Zeilen mit Port
            out = subprocess.check_output(f'netstat -ano | findstr :{port}', shell=True, text=True, stderr=subprocess.DEVNULL)
            pids: List[str] = []
            for line in out.splitlines():
                parts = line.split()
                if parts:
                    pid = parts[-1]
                    if pid.isdigit():
                        pids.append(pid)
            # Einzigartige PIDs killen
            for pid in sorted(set(pids)):
                try:
                    subprocess.call(["taskkill", "/PID", pid, "/F"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
                except Exception:
                    pass
        except subprocess.CalledProcessError:
            # Kein Treffer → Port frei
            pass
                    
    def check_node_npm(self):
        """Prüfe ob Node.js und npm installiert sind"""
        try:
            # Windows-spezifische Prüfung
            if sys.platform == 'win32':
                subprocess.run(["node", "--version"], 
                             capture_output=True, 
                             check=True, 
                             shell=True)
                subprocess.run(["npm", "--version"], 
                             capture_output=True, 
                             check=True, 
                             shell=True)
            else:
                subprocess.run(["node", "--version"], capture_output=True, check=True)
                subprocess.run(["npm", "--version"], capture_output=True, check=True)
            return True
        except (subprocess.CalledProcessError, FileNotFoundError):
            print("❌ Node.js und npm müssen installiert sein!")
            print("   Bitte installieren Sie Node.js von: https://nodejs.org/")
            return False
            
    def start_services_windows(self):
        """Starte alle Services in Windows mit separaten CMD-Fenstern"""
        print("🚀 Starte SkillMate Services...")

        # Ports freimachen (wenn vorherige Läufe hingen blieben)
        for p in [self.backend_port, self.frontend_port, self.admin_port]:
            self._free_port_windows(p)

        # Abhängigkeiten sicherstellen, bevor Services gestartet werden
        print("   Prüfe/Installiere Abhängigkeiten...")
        if not self._ensure_dependencies():
            print("❌ Abbruch: Abhängigkeiten konnten nicht installiert werden.")
            return

        # Backend
        backend_dir = self.base_dir / "backend"
        if self.mode == 'dev':
            self._popen_new_console(backend_dir, "npm run dev")
        else:
            # Produktionsstart: Build & Start
            self._popen_new_console(backend_dir, "cmd /c npm run build && npm start")
        
        # Warte bis Backend bereit ist
        print("   Warte auf Backend...")
        time.sleep(5)

        # Frontend
        frontend_dir = self.base_dir / "frontend"
        if self.mode == 'dev':
            self._popen_new_console(frontend_dir, f"npm run dev -- --strictPort --port {self.frontend_port}")
        else:
            self._popen_new_console(frontend_dir, f"cmd /c npm run build && npm run preview -- --port {self.frontend_port}")
        
        # Admin Panel (optional)
        admin_dir = self.base_dir / "admin-panel"
        if admin_dir.exists():
            if self.mode == 'dev':
                self._popen_new_console(admin_dir, f"npm run dev -- --strictPort --port {self.admin_port}")
            else:
                self._popen_new_console(admin_dir, f"cmd /c npm run build && npm run preview -- --port {self.admin_port}")
        
        print("\n✨ SkillMate läuft!")
        print("\n📍 Zugriff:")
        print(f"   - Frontend: http://localhost:{self.frontend_port}")
        print(f"   - Backend:  http://localhost:{self.backend_port}")
        if admin_dir.exists():
            print(f"   - Admin:    http://localhost:{self.admin_port}")
        
        # Öffne Frontend im Browser
        time.sleep(3)
        webbrowser.open(f"http://localhost:{self.frontend_port}")
        
        print("\n⚡ Schließen Sie dieses Fenster, um SkillMate zu beenden")
        
        
    def run(self):
        """Hauptausführungsmethode"""
        print("🎯 SkillMate wird gestartet...\n")
        
        # Prüfe Voraussetzungen
        if not self.check_node_npm():
            return False
            
        # Windows-spezifischer Start
        if sys.platform == 'win32':
            self.start_services_windows()
            # Halte das Hauptfenster offen
            try:
                input("\nDrücken Sie Enter zum Beenden...")
            except KeyboardInterrupt:
                pass
        else:
            print("❌ Dieses Skript ist für Windows optimiert.")
            print("   Nutzen Sie 'python main.py' für andere Systeme.")
            return False
            
        return True

def main():
    """Haupteinstiegspunkt"""
    parser = argparse.ArgumentParser(description='SkillMate Starter')
    parser.add_argument('--mode', choices=['dev', 'prod'], default='dev', help='Startmodus: dev oder prod')
    args = parser.parse_args()

    starter = SkillMateStarter(mode=args.mode)
    
    try:
        success = starter.run()
        if not success:
            sys.exit(1)
    except Exception as e:
        print(f"\n❌ Unerwarteter Fehler: {e}")
        starter.cleanup()
        sys.exit(1)

if __name__ == "__main__":
    main()
