import uuid
import shutil
import json
import socket
import subprocess
import petname
import hashlib
import platform
import os
from dotenv import load_dotenv
from datetime import datetime

# Archivo JSON para guardar los datos de los contenedores
CONTAINER_DATA_FILE = os.getenv("CONTAINER_DATA_FILE")
# Nombre y etiqueta para la imagen base
BASE_IMAGE_TAG = os.getenv("BASE_IMAGE_TAG")

#Lista de puertos internos
PUERTOS_INTERNOS = [5173, 5432, 8786, 8787, 8080, 17000]

# Numero de conteineres a crear
NUM_CONTAINERS = 4



# -------------------------------
# Función: Comprobar si un puerto está libre con nmap
# -------------------------------
def is_port_available(port):
    # Cargar datos existentes si el archivo existe y no está vacío
    if os.path.exists(CONTAINER_DATA_FILE) and os.path.getsize(CONTAINER_DATA_FILE) > 0:
        with open(CONTAINER_DATA_FILE, 'r') as file:
            try:
                data = json.load(file)  # Cargar datos existentes
                # Verificar si el puerto ya está en uso en los datos existentes
                for container in data:
                    for port_map in container['ports']:
                        if port_map['host_port'] == port:
                            return False  # Puerto en uso
            except json.JSONDecodeError:
                pass  # Si hay error al leer, continuar con la comprobación de nmap

    # Ejecuta nmap para comprobar si el puerto está abierto
    command = f"nmap -p {port} 127.0.0.1"
    print(f"[INFO] Ejecutando nmap para el puerto {port}...")
    result = subprocess.run(command, shell=True, capture_output=True, text=True)

    # Muestra el resultado del comando nmap
    print(f"[DEBUG] Resultado de nmap para el puerto {port}:\n{result.stdout}")

    # Analiza el resultado para ver si el puerto está cerrado
    if "closed" in result.stdout:
        return True  # Puerto libre
    else:
        return False  # Puerto ocupado



# -------------------------------
# Función: Ejecutar contenedor y guardar datos
# -------------------------------
def run_docker_compose(internal_ports=None):
    # Genera un UUID único
    unique_uuid = str(uuid.uuid4())
    # Genera un nombre único para el contenedor
    uuid_palabras = petname.Generate(3, separator='-')


    # Nombres únicos para el servicio, contenedor y la red
    service_name = f"{uuid_palabras}"
    container_name = f"{uuid_palabras}"
    network_name = f"alpine_network_{unique_uuid}"

    # Nombre de la imagen local a usar
    image_tag = BASE_IMAGE_TAG  # Usar la imagen local directamente

    # Nombre del archivo docker-compose nuevo
    new_docker_compose_file = f'docker-compose-{unique_uuid}.yml'

    try:
        # Configuración personalizada de puertos
        ports_mapping = []
        custom_ports = []
        host_port = 27000  # Puerto inicial
        print(f"[INFO] Iniciando la configuración de puertos a partir del puerto {host_port}")
        
        if internal_ports:
            for internal_port in internal_ports:
                print(f"[INFO] Configurando puerto interno: {internal_port}")
                
                # Buscar el siguiente puerto disponible usando nmap
                while host_port <= 65000:
                    if is_port_available(host_port):
                        print(f"[INFO] Asignando puerto {host_port} para el puerto interno {internal_port}...")
                        custom_ports.append(f"      - \"{host_port}:{internal_port}\"")
                        ports_mapping.append({
                            "host_port": host_port,
                            "internal_port": internal_port
                        })
                        host_port += 1  # Incrementa el puerto para el siguiente uso
                        break
                    else:
                        print(f"[WARNING] Puerto {host_port} ocupado, probando el siguiente...")
                        host_port += 1  # Incrementa si el puerto está ocupado

                if host_port > 65000:
                    raise Exception("Se superó el límite de puertos disponibles.")
            
            custom_ports_yaml = "\n".join(custom_ports)
            print(f"[INFO] Puertos personalizados configurados:\n{custom_ports_yaml}")
        else:
            custom_ports_yaml = ""  # No agrega puertos si no se especifican
            print("[INFO] No se especificaron puertos internos.")
        
        # Genera el contenido del archivo docker-compose dinámicamente
        docker_compose_content = f"""
version: '3.8'

services:
  {service_name}:
    image: {image_tag}  # Usar la imagen base directamente
    container_name: {container_name}
    networks:
      - {network_name}
    restart: always
    tty: true
    volumes:
      - ./share:/app/share
      - ./d_apps/{container_name}:/app/host_transfer
    ports:
{custom_ports_yaml}
    command: tail -f /dev/null  # Mantener activo el contenedor

networks:
  {network_name}:
    driver: bridge
"""

        # Guarda el archivo docker-compose generado
        with open(new_docker_compose_file, 'w') as file:
            file.write(docker_compose_content)

        # Muestra el contenido generado para depuración
        print(f"[DEBUG] Contenido del archivo {new_docker_compose_file}:\n")
        print(docker_compose_content)

        # Ejecuta docker-compose con el archivo nuevo y captura la salida
        print("[INFO] Ejecutando docker compose...")
        result = subprocess.run(
            f'docker compose -f {new_docker_compose_file} up -d --no-build',
            shell=True,
            capture_output=True,
            text=True
        )

        # Muestra el resultado del comando
        if result.returncode != 0:
            print(f"[ERROR] Error al ejecutar docker compose:\n{result.stderr}")
            raise Exception("Error al ejecutar docker-compose")
        else:
            print(f"[INFO] Contenedor iniciado exitosamente:\n{result.stdout}")

        # Mostrar contenedores activos y detenidos
        print("[DEBUG] Contenedores activos y detenidos:")
        subprocess.run("docker ps -a", shell=True)

        # Obtener el ID del contenedor creado
        container_id = subprocess.getoutput(f"docker ps -qf name={container_name}")
        if not container_id:
            print(f"[ERROR] No se pudo obtener el ID del contenedor {container_name}.")
            raise Exception("No se pudo obtener el ID del contenedor")
        
        print(f"[INFO] ID del contenedor: {container_id}")

        # Mostrar logs del contenedor si está detenido
        print("[INFO] Mostrando logs del contenedor:")
        subprocess.run(f"docker logs {container_id}", shell=True)

        # Guardar los datos del contenedor en el archivo JSON
        save_container_data({
            'id': container_id,
            'name': container_name,
            'network': network_name,
            'image': image_tag,
            'compose_file': new_docker_compose_file,
            'creation_timestamp': datetime.now().isoformat(),
            'ports': ports_mapping  # Agrega la información de los puertos mapeados
        })

    except Exception as e:
        print(f"[ERROR] Error: {str(e)}")

    finally:
        # Eliminar el archivo docker-compose generado si existe
        if os.path.exists(new_docker_compose_file):
            print(f"[INFO] Eliminando archivo temporal: {new_docker_compose_file}")
            os.remove(new_docker_compose_file)



# -------------------------------
# Función: Guardar datos del contenedor en JSON
# -------------------------------
def save_container_data(container_data):
    # Cargar datos existentes si el archivo existe y no está vacío
    if os.path.exists(CONTAINER_DATA_FILE) and os.path.getsize(CONTAINER_DATA_FILE) > 0:
        with open(CONTAINER_DATA_FILE, 'r') as file:
            try:
                data = json.load(file)  # Cargar datos existentes
            except json.JSONDecodeError:
                data = []  # Si hay error al leer, inicializar como lista vacía
    else:
        # Crear el archivo vacío y usar una lista vacía
        with open(CONTAINER_DATA_FILE, 'w') as file:
            json.dump([], file)
        data = []  # Inicializar como lista vacía

    # Agregar nuevo contenedor
    data.append(container_data)

    # Guardar datos actualizados
    with open(CONTAINER_DATA_FILE, 'w') as file:
        json.dump(data, file, indent=4)



# -------------------------------
# Función: Limpiar imágenes huérfanas
# -------------------------------
def clean_unused_images():
    print("➤ Eliminando imágenes huérfanas...")
    os.system('docker image prune -f')

# -------------------------------
# Función: Limpiar redes huérfanas
# -------------------------------
def clean_unused_networks():
    print("➤ Eliminando redes huérfanas...")
    os.system('docker network prune -f')


######################################### -------------------------------
# Ejecución del script
######################################### -------------------------------
if __name__ == "__main__":


    # Crear contenedores y guardarlos en el JSON
    for _ in range(NUM_CONTAINERS):
        run_docker_compose(internal_ports=PUERTOS_INTERNOS)
        print("➤ Contenedores creados y guardados en el JSON")

    # Limpiar imágenes huérfanas
    clean_unused_images()
    # Limpiar redes huérfanas
    clean_unused_networks()
