#!/usr/bin/env python3

import os
import sys
import subprocess
import platform
import socket
import json
import shutil
import time
import signal
from pathlib import Path
from typing import Optional, List, Dict
import http.server
import socketserver
import threading
import click

class Color:
    GREEN = '\033[1;32m'
    RED = '\033[1;31m'
    NC = '\033[0m'
    YELLOW = '\033[1;33m'

class DockerManager:
    @staticmethod
    def check_docker():
        try:
            subprocess.run(['docker', '--version'], capture_output=True, check=True)
            return True
        except (subprocess.CalledProcessError, FileNotFoundError):
            return False

    def cleanup_container(self, container_name: str):
        try:
            result = subprocess.run(
                ['docker', 'ps', '-a', '--format', '{{.Names}}'],
                capture_output=True,
                text=True,
                check=True
            )
            if container_name in result.stdout.split('\n'):
                subprocess.run(['docker', 'stop', container_name], 
                             capture_output=True,
                             check=False)
                subprocess.run(['docker', 'rm', '-f', container_name],
                             capture_output=True,
                             check=False)
        except subprocess.CalledProcessError as e:
            print(f"Erreur lors du nettoyage du conteneur {container_name}: {e}")

    def check_port_conflict(self, port: int) -> bool:
        try:
            result = subprocess.run(
                ['docker', 'ps', '--format', '{{.Ports}}'],
                capture_output=True,
                text=True,
                check=True
            )
            return f":{port}->" in result.stdout or f":{port}/" in result.stdout
        except subprocess.CalledProcessError:
            return False

    def deploy_container(self, app_dir: Path, app_name: str, port: int) -> bool:
        try:
            self.cleanup_container(app_name)

            if self.check_port_conflict(port):
                print(f"Le port {port} est déjà utilisé par un autre conteneur")
                return False

            print(f"Construction de l'image pour {app_name}...")
            build_result = subprocess.run(
                ['docker', 'build', '-t', app_name, str(app_dir)],
                capture_output=True,
                text=True
            )
            if build_result.returncode != 0:
                print(f"Erreur de build pour {app_name}: {build_result.stderr}")
                return False

            print(f"Lancement du conteneur {app_name} sur le port {port}...")
            run_result = subprocess.run(
                ['docker', 'run', '-d', '--rm',
                 '--name', app_name,
                 '-p', f"{port}:80",
                 app_name],
                capture_output=True,
                text=True
            )
            if run_result.returncode != 0:
                print(f"Erreur de lancement pour {app_name}: {run_result.stderr}")
                return False

            time.sleep(2)
            check_result = subprocess.run(
                ['docker', 'ps', '--format', '{{.Names}}'],
                capture_output=True,
                text=True
            )
            if app_name not in check_result.stdout:
                print(f"Le conteneur {app_name} n'a pas démarré correctement")
                return False

            print(f"✅ Conteneur {app_name} déployé avec succès sur le port {port}")
            return True

        except Exception as e:
            print(f"Erreur inattendue lors du déploiement de {app_name}: {str(e)}")
            return False

    def cleanup_all(self):
        try:
            result = subprocess.run(
                ['docker', 'ps', '-a', '--format', '{{.Names}}'],
                capture_output=True,
                text=True,
                check=True
            )
            for container in result.stdout.strip().split('\n'):
                if container:
                    self.cleanup_container(container)
        except Exception as e:
            print(f"Erreur lors du nettoyage global: {str(e)}")

class PortManager:
    @staticmethod
    def is_port_available(port: int) -> bool:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            try:
                s.bind(('', port))
                return True
            except OSError:
                return False

    @classmethod
    def find_available_port(cls, start: int, end: int) -> Optional[int]:
        for port in range(start, end + 1):
            if cls.is_port_available(port):
                return port
        return None

class WebServer:
    def __init__(self, port: int, directory: str = '.'):
        self.port = port
        self.directory = directory
        self.httpd = None

    def start(self):
        os.chdir(self.directory)
        handler = http.server.SimpleHTTPRequestHandler
        self.httpd = socketserver.TCPServer(("", self.port), handler)
        self.httpd.serve_forever()

class AppManager:
    def __init__(self):
        self.base_path = Path(__file__).parent
        self.template_file = self.base_path / "catalog.template.html"
        self.splash_file = self.base_path / "splash.html"
        self.docker_manager = DockerManager()
        self.used_ports = set()

        if not self.template_file.exists():
            print(f"Erreur: Template {self.template_file} manquant")
            sys.exit(1)
        if not self.splash_file.exists():
            print(f"Erreur: Template {self.splash_file} manquant")
            sys.exit(1)

    def find_next_available_port(self, start: int = 3000, end: int = 5000) -> Optional[int]:
        for port in range(start, end + 1):
            if port not in self.used_ports and PortManager.is_port_available(port):
                if not self.docker_manager.check_port_conflict(port):
                    return port
        return None

    def generate_main_page(self, catalog_url: str):
        with open(self.splash_file, "r") as f:
            splash_content = f.read()

        main_page = f"""<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Accueil</title>
    <style>
        body {{
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
            margin: 0;
            font-family: Arial, sans-serif;
            background: #f4f4f9;
        }}
        button {{
            padding: 15px 30px;
            font-size: 18px;
            font-weight: bold;
            color: #fff;
            background: #007bff;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            transition: background 0.3s ease;
        }}
        button:hover {{
            background: #0056b3;
        }}
    </style>
</head>
<body>
{splash_content}
<button onclick="location.href='{catalog_url}'">Parcourir les apps</button>
</body>
</html>"""

        with open("index.html", "w") as f:
            f.write(main_page)

    def deploy_apps(self) -> List[Dict]:
        deployed_apps = []
        app_infos = list(Path('apps').rglob('app-info.json'))
        
        if not app_infos:
            print("Aucune application trouvée (pas de fichier app-info.json)")
            return deployed_apps

        for app_info_path in app_infos:
            app_dir = app_info_path.parent
            app_name = app_dir.name.lower()

            try:
                with open(app_info_path) as f:
                    try:
                        app_info = json.load(f)
                    except json.JSONDecodeError:
                        print(f"Fichier app-info.json invalide dans {app_dir}")
                        continue

                port = self.find_next_available_port()
                if not port:
                    print(f"Pas de port disponible pour {app_name}")
                    continue

                if self.docker_manager.deploy_container(app_dir, app_name, port):
                    self.used_ports.add(port)
                    deployed_apps.append({
                        "id": app_name,
                        "category": "Applications",
                        "title": app_info.get('title', 'Sans titre'),
                        "description": app_info.get('description', 'Pas de description'),
                        "port": port,
                        "url": f"http://localhost:{port}"
                    })
                else:
                    print(f"Échec du déploiement de {app_name}")

            except Exception as e:
                print(f"Erreur lors du traitement de {app_name}: {str(e)}")
                continue

        return deployed_apps

    def generate_catalog(self, apps: List[Dict]):
        with open(self.template_file) as f:
            template = f.read()

        apps_data = []
        for app in sorted(apps, key=lambda x: x['title']):
            app_data = app.copy()
            app_data['url'] = f"http://localhost:{app_data['port']}"
            app_data['port'] = str(app_data['port'])
            apps_data.append(app_data)

        apps_json = json.dumps(apps_data)
        catalog_content = template.replace('{{VULNERABILITIES_DATA}}', apps_json)

        with open("catalog.html", "w") as f:
            f.write(catalog_content)

        print("\nVérification des URLs générées dans le catalogue:")
        for app in apps_data:
            print(f"- {app['title']}: {app['url']}")

def display_summary(main_port: int, catalog_port: int):
    print("\n===== Résumé des informations utiles =====")
    print(f"\n📌 Serveur principal : {Color.GREEN}http://localhost:{main_port}{Color.NC}")
    print(f"📌 Catalogue des applications : {Color.GREEN}http://localhost:{catalog_port}/catalog.html{Color.NC}")
    print("\n🛠️  Informations pour le formateur :")
    print("1. Utilisez l'URL du catalogue pour accéder à toutes les applications hébergées")
    print("2. Si vous ajoutez de nouvelles applications avec 'app-info.json', redémarrez le script")
    print("3. Assurez-vous que Docker fonctionne correctement pour déployer les conteneurs")
    print(f"\n🔍 Ports utilisés :")
    print(f"   - Port serveur principal : {main_port}")
    print(f"   - Port catalogue des applications : {catalog_port}")
    print(f"\n🎯 Pour arrêter le serveur :")
    print(f"   {Color.RED}pkill -f \"python3 -m http.server\"{Color.NC}")
    print("\n==========================================")

@click.command()
def main():
    try:
        # Vérification du dossier apps
        apps_dir = Path('apps')
        if not apps_dir.exists():
            apps_dir.mkdir(exist_ok=True)
            print("\n🚀 Initialisation de DemoKit")
            print("\n📁 Le dossier 'apps' a été créé.")
            print("\nPour utiliser DemoKit:")
            print("1. Créez un dossier pour chaque application dans 'apps/'")
            print("2. Ajoutez un fichier app-info.json dans chaque dossier")
            print("3. Ajoutez un Dockerfile dans chaque dossier")
            print("4. Relancez demokit")
            sys.exit(0)

        # Vérification des applications
        app_infos = list(apps_dir.rglob('app-info.json'))
        if not app_infos:
            print("\n❌ Aucune application trouvée dans le dossier 'apps'")
            print("\nStructure attendue:")
            print("apps/")
            print("├── app1/")
            print("│   ├── app-info.json")
            print("│   └── Dockerfile")
            print("└── app2/")
            print("    ├── app-info.json")
            print("    └── Dockerfile")
            sys.exit(0)

        docker_manager = DockerManager()
        app_manager = AppManager()

        main_port = PortManager.find_available_port(8000, 8100)
        catalog_port = PortManager.find_available_port(8101, 8200)

        if not main_port or not catalog_port:
            print("Pas de ports disponibles")
            sys.exit(1)

        apps = app_manager.deploy_apps()
        app_manager.generate_catalog(apps)
        app_manager.generate_main_page(f"http://localhost:{catalog_port}/catalog.html")

        main_server = threading.Thread(
            target=lambda: WebServer(main_port).start(),
            daemon=True
        )
        catalog_server = threading.Thread(
            target=lambda: WebServer(catalog_port).start(),
            daemon=True
        )

        main_server.start()
        catalog_server.start()

        display_summary(main_port, catalog_port)

        while True:
            time.sleep(1)

    except KeyboardInterrupt:
        print("\n👋 Arrêt de DemoKit...")
        if 'app_manager' in locals():
            app_manager.docker_manager.cleanup_all()
        sys.exit(0)
    except Exception as e:
        print(f"\n❌ Erreur: {str(e)}")
        if 'app_manager' in locals():
            app_manager.docker_manager.cleanup_all()
        sys.exit(1)

if __name__ == '__main__':
    main()
