#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
╔══════════════════════════════════════════════════════════════════╗
║          ArcaBeauty Club — Auto-instalador para cPanel           ║
╚══════════════════════════════════════════════════════════════════╝

INSTRUCCIONES:
  1. Sube ESTE ARCHIVO (install.py) a la carpeta de tu app en cPanel.
  2. Sube también el archivo  arcabeauty_club.zip  a la misma carpeta.
  3. Abre la Terminal de cPanel y activa tu entorno virtual:
       source ~/virtualenv/TU_APP/3.11/bin/activate
  4. Navega a tu carpeta de app:
       cd ~/TU_APP
  5. Corre el instalador:
       python install.py

El script hace todo lo demás automáticamente.
"""

import os
import sys
import zipfile
import shutil
import subprocess
import textwrap
import platform
import datetime


# ─── colores ANSI (solo si la terminal los soporta) ──────────────────────────

def _soporta_color():
    return sys.stdout.isatty() and platform.system() != "Windows"

VERDE  = "\033[92m" if _soporta_color() else ""
AMARILLO = "\033[93m" if _soporta_color() else ""
ROJO   = "\033[91m" if _soporta_color() else ""
CYAN   = "\033[96m" if _soporta_color() else ""
BLANCO = "\033[97m" if _soporta_color() else ""
RESET  = "\033[0m"  if _soporta_color() else ""
NEGRITA = "\033[1m" if _soporta_color() else ""


def ok(msg):    print(f"  {VERDE}✓{RESET} {msg}")
def info(msg):  print(f"  {CYAN}→{RESET} {msg}")
def warn(msg):  print(f"  {AMARILLO}⚠{RESET}  {msg}")
def error(msg): print(f"  {ROJO}✗{RESET} {NEGRITA}{msg}{RESET}")
def titulo(msg): print(f"\n{NEGRITA}{BLANCO}{msg}{RESET}")
def separador(): print("─" * 60)


# ═══════════════════════════════════════════════════════════════════
# PASO 0 — Bienvenida y verificaciones previas
# ═══════════════════════════════════════════════════════════════════

def paso0_bienvenida():
    print()
    separador()
    print(f"{NEGRITA}{CYAN}  ArcaBeauty Club · Auto-instalador v1.0{RESET}")
    separador()
    print(f"  {datetime.datetime.now().strftime('%d/%m/%Y %H:%M:%S')}  |  Python {sys.version.split()[0]}")
    print(f"  Directorio: {os.getcwd()}")
    separador()

    titulo("Paso 0 — Verificaciones previas")

    # Python >= 3.10
    if sys.version_info < (3, 10):
        error(f"Se necesita Python 3.10 o superior. Tienes {sys.version.split()[0]}.")
        error("Cambia la versión en 'Setup Python App' y vuelve a activar el entorno virtual.")
        sys.exit(1)
    ok(f"Python {sys.version.split()[0]}")

    # Archivo zip presente
    zip_path = _encontrar_zip()
    if not zip_path:
        error("No se encontró el archivo arcabeauty_club.zip en el directorio actual.")
        error("Súbelo junto a este script y vuelve a correr el instalador.")
        sys.exit(1)
    ok(f"Archivo de instalación encontrado: {os.path.basename(zip_path)}")

    # Entorno virtual activo (recomendado, no obligatorio)
    if hasattr(sys, "real_prefix") or (hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix):
        ok("Entorno virtual activo")
    else:
        warn("No se detectó un entorno virtual activo.")
        warn("Se recomienda activarlo primero: source ~/virtualenv/TU_APP/3.xx/bin/activate")
        resp = input("  ¿Continuar de todas formas? (s/N): ").strip().lower()
        if resp not in ("s", "si", "sí", "y", "yes"):
            print("  Instalación cancelada.")
            sys.exit(0)

    return zip_path


def _encontrar_zip():
    for nombre in ["arcabeauty_club.zip", "arcabeauty.zip"]:
        if os.path.exists(nombre):
            return os.path.abspath(nombre)
    # Buscar cualquier zip que empiece por 'arcabeauty'
    for f in os.listdir("."):
        if f.startswith("arcabeauty") and f.endswith(".zip"):
            return os.path.abspath(f)
    return None


# ═══════════════════════════════════════════════════════════════════
# PASO 1 — Extraer archivos del proyecto
# ═══════════════════════════════════════════════════════════════════

def paso1_extraer(zip_path):
    titulo("Paso 1 — Extrayendo archivos del proyecto")

    destino = os.getcwd()

    with zipfile.ZipFile(zip_path, "r") as zf:
        nombres = zf.namelist()

        # El zip tiene una carpeta raíz (arcabeauty/). Detectarla.
        raiz_zip = ""
        for n in nombres:
            partes = n.split("/")
            if len(partes) > 1 and partes[0]:
                raiz_zip = partes[0]
                break

        info(f"Extrayendo {len(nombres)} archivos...")
        extraidos = 0

        for miembro in nombres:
            # Quitar la carpeta raíz del zip para que quede directo en destino
            ruta_relativa = miembro[len(raiz_zip):].lstrip("/") if raiz_zip else miembro
            if not ruta_relativa:
                continue

            ruta_destino = os.path.join(destino, ruta_relativa)

            if miembro.endswith("/"):
                os.makedirs(ruta_destino, exist_ok=True)
            else:
                os.makedirs(os.path.dirname(ruta_destino), exist_ok=True)
                with zf.open(miembro) as src, open(ruta_destino, "wb") as dst:
                    dst.write(src.read())
                extraidos += 1

    ok(f"{extraidos} archivos extraídos en {destino}")

    # Verificar que los archivos clave existen
    requeridos = ["app.py", "db.py", "schema.sql", "seed_data.py", "requirements.txt"]
    for f in requeridos:
        if not os.path.exists(f):
            error(f"El archivo {f} no se extrajo correctamente. Verifica el zip.")
            sys.exit(1)
    ok("Archivos clave verificados (app.py, db.py, schema.sql, seed_data.py)")


# ═══════════════════════════════════════════════════════════════════
# PASO 2 — Instalar dependencias Python
# ═══════════════════════════════════════════════════════════════════

def paso2_dependencias():
    titulo("Paso 2 — Instalando dependencias Python")

    if not os.path.exists("requirements.txt"):
        warn("No se encontró requirements.txt, omitiendo instalación de dependencias.")
        return

    info("Corriendo: pip install -r requirements.txt ...")
    resultado = subprocess.run(
        [sys.executable, "-m", "pip", "install", "-r", "requirements.txt",
         "--break-system-packages", "--quiet"],
        capture_output=True, text=True
    )

    if resultado.returncode != 0:
        # Intentar sin --break-system-packages (entornos virtuales normales)
        resultado = subprocess.run(
            [sys.executable, "-m", "pip", "install", "-r", "requirements.txt", "--quiet"],
            capture_output=True, text=True
        )

    if resultado.returncode == 0:
        ok("Flask instalado correctamente")
    else:
        error("Hubo un problema instalando las dependencias:")
        print(resultado.stderr[:500])
        warn("El sistema puede seguir funcionando si Flask ya estaba instalado.")

    # Verificar que Flask quedó disponible
    try:
        import importlib.metadata
        flask_version = importlib.metadata.version("flask")
        ok(f"Flask {flask_version} disponible")
    except Exception:
        try:
            import flask
            ok("Flask disponible")
        except ImportError:
            error("Flask no está disponible. Instálalo manualmente y vuelve a correr el instalador.")
            sys.exit(1)


# ═══════════════════════════════════════════════════════════════════
# PASO 3 — Crear passenger_wsgi.py
# ═══════════════════════════════════════════════════════════════════

PASSENGER_WSGI = """\
import sys
import os

sys.path.insert(0, os.path.dirname(__file__))

from app import app as application
"""

def paso3_passenger():
    titulo("Paso 3 — Configurando passenger_wsgi.py")

    wsgi_path = "passenger_wsgi.py"
    if os.path.exists(wsgi_path):
        # Leer el existente para ver si ya está configurado
        contenido = open(wsgi_path).read()
        if "from app import app as application" in contenido:
            ok("passenger_wsgi.py ya está configurado correctamente")
            return
        # Respaldar el existente
        backup = wsgi_path + ".backup"
        shutil.copy(wsgi_path, backup)
        warn(f"Se respaldó el passenger_wsgi.py anterior como {backup}")

    with open(wsgi_path, "w") as f:
        f.write(PASSENGER_WSGI)
    ok("passenger_wsgi.py creado")


# ═══════════════════════════════════════════════════════════════════
# PASO 4 — Inicializar la base de datos
# ═══════════════════════════════════════════════════════════════════

def paso4_base_de_datos():
    titulo("Paso 4 — Inicializando base de datos")

    if os.path.exists("arcabeauty.db"):
        print()
        warn("Ya existe una base de datos (arcabeauty.db).")
        print(f"  {AMARILLO}Opciones:{RESET}")
        print(f"    1. Conservar la base de datos actual (recomendado si ya tienes datos reales)")
        print(f"    2. Reinicializar con datos de ejemplo (borra todos los datos actuales)")
        print(f"    3. Solo inicializar las tablas nuevas (si actualizas desde versión anterior)")
        resp = input("  Elige una opción (1/2/3) [1]: ").strip()

        if resp == "2":
            os.remove("arcabeauty.db")
            info("Base de datos eliminada. Creando con datos de ejemplo...")
            _correr_seed()
        elif resp == "3":
            info("Agregando tablas nuevas sin borrar datos...")
            _solo_init_db()
        else:
            ok("Base de datos conservada sin cambios")
    else:
        info("Creando base de datos con datos de ejemplo...")
        _correr_seed()


def _correr_seed():
    resultado = subprocess.run(
        [sys.executable, "seed_data.py"],
        capture_output=True, text=True
    )
    if resultado.returncode == 0:
        ok("Base de datos inicializada con datos de ejemplo")
        # Mostrar resumen del seed
        for linea in resultado.stdout.strip().splitlines():
            info(f"  {linea}")
    else:
        error("Error al inicializar la base de datos:")
        print(resultado.stderr[:500])
        sys.exit(1)


def _solo_init_db():
    resultado = subprocess.run(
        [sys.executable, "-c", "from db import init_db; init_db(); print('Tablas creadas/actualizadas')"],
        capture_output=True, text=True
    )
    if resultado.returncode == 0:
        ok(resultado.stdout.strip())
    else:
        error("Error al inicializar tablas:")
        print(resultado.stderr[:300])


# ═══════════════════════════════════════════════════════════════════
# PASO 5 — Permisos de archivos
# ═══════════════════════════════════════════════════════════════════

def paso5_permisos():
    titulo("Paso 5 — Configurando permisos")

    directorio = os.getcwd()

    # Directorio principal: 755
    try:
        os.chmod(directorio, 0o755)
        ok(f"Directorio principal: 755")
    except Exception as e:
        warn(f"No se pudo cambiar el permiso del directorio: {e}")

    # Archivos Python y config: 644
    for archivo in os.listdir(directorio):
        ruta = os.path.join(directorio, archivo)
        if os.path.isfile(ruta) and (archivo.endswith(".py") or archivo.endswith(".txt")
                                      or archivo.endswith(".sql") or archivo.endswith(".md")):
            try:
                os.chmod(ruta, 0o644)
            except Exception:
                pass

    # Base de datos: 664 (necesita escritura por el proceso de la app)
    db_path = os.path.join(directorio, "arcabeauty.db")
    if os.path.exists(db_path):
        try:
            os.chmod(db_path, 0o664)
            ok("arcabeauty.db: 664")
        except Exception as e:
            warn(f"No se pudo cambiar el permiso de la base de datos: {e}")

    # Static: 755
    static_dir = os.path.join(directorio, "static")
    if os.path.exists(static_dir):
        for root, dirs, files in os.walk(static_dir):
            for d in dirs:
                try: os.chmod(os.path.join(root, d), 0o755)
                except Exception: pass
            for f in files:
                try: os.chmod(os.path.join(root, f), 0o644)
                except Exception: pass
        ok("Carpeta static: permisos configurados")


# ═══════════════════════════════════════════════════════════════════
# PASO 6 — Prueba de carga de la app
# ═══════════════════════════════════════════════════════════════════

def paso6_prueba():
    titulo("Paso 6 — Verificando que la app carga correctamente")

    resultado = subprocess.run(
        [sys.executable, "-c",
         "import sys; sys.path.insert(0, '.'); from app import app; print('App cargada OK')"],
        capture_output=True, text=True, cwd=os.getcwd()
    )
    if resultado.returncode == 0 and "App cargada OK" in resultado.stdout:
        ok("La aplicación Flask se importa sin errores")
    else:
        error("La aplicación reportó errores al cargarse:")
        print(resultado.stderr[:600])
        warn("Revisa los logs de cPanel para más detalles.")
        return False
    return True


# ═══════════════════════════════════════════════════════════════════
# PASO 7 — Resumen final y próximos pasos
# ═══════════════════════════════════════════════════════════════════

def paso7_resumen(app_ok):
    print()
    separador()
    if app_ok:
        print(f"{NEGRITA}{VERDE}  ✓ Instalación completada exitosamente{RESET}")
    else:
        print(f"{NEGRITA}{AMARILLO}  ⚠  Instalación completada con advertencias{RESET}")
    separador()

    print(f"""
{NEGRITA}Próximos pasos en cPanel:{RESET}

  1. Ve a {CYAN}Setup Python App{RESET} → busca tu aplicación → haz clic en {CYAN}Restart{RESET}

  2. Abre tu navegador en la URL de tu app para verificar que funciona.

  3. {NEGRITA}Variables de entorno{RESET} (opcionales, las configuras en Setup Python App):

     • {AMARILLO}SECRET_KEY{RESET}           → cadena aleatoria para sesiones seguras
                              (obligatoria antes de usar las apps móviles en producción)

     • {AMARILLO}ANTHROPIC_API_KEY{RESET}    → tu llave de api.anthropic.com
                              (solo necesaria para activar el Asistente IA)

     • {AMARILLO}SMS_PROVIDER{RESET}         → "twilio" para envíos SMS reales
                              (sin esto funciona en modo de prueba)

     • {AMARILLO}TWILIO_ACCOUNT_SID{RESET}   → credenciales de Twilio
       {AMARILLO}TWILIO_AUTH_TOKEN{RESET}
       {AMARILLO}TWILIO_FROM_NUMBER{RESET}

     • {AMARILLO}DATABASE_URL{RESET}         → mysql://user:pass@host:3306/db
                              (solo para migrar a MySQL; sin esto usa SQLite)

  4. Datos de acceso a las apps de prueba:

     {CYAN}App de Clientes (/app-cliente):{RESET}
       Teléfono de cualquier cliente de ejemplo:
         833 201 5566  (Daniela Pérez Soto)
         833 455 9091  (Luis Fernando Garza)

     {CYAN}App de Empleados (/app-empleado):{RESET}
       Teléfono de cualquier empleado de ejemplo:
         833 555 0101  (Renata Solís)
         833 555 0102  (Camila Duarte)

     En ambas apps, el código de verificación aparece en pantalla
     mientras no hayas configurado un proveedor SMS real.

{NEGRITA}Archivos generados:{RESET}
  ✓ passenger_wsgi.py   — punto de entrada para Passenger/cPanel
  ✓ arcabeauty.db       — base de datos SQLite con datos de ejemplo
""")

    if not app_ok:
        print(f"  {AMARILLO}Si la app no cargó correctamente, revisa:{RESET}")
        print("  • Que el entorno virtual esté activo y Flask instalado")
        print("  • Los logs de error en cPanel → Logs")
        print()

    separador()
    print(f"  {CYAN}Documentación incluida en el zip:{RESET}")
    print("  • README.md                 → descripción técnica completa")
    print("  • guia_despliegue_cpanel.md → guía paso a paso de despliegue")
    print("  • manual_usuario_arcabeauty.md → manual para el equipo del salón")
    separador()
    print()


# ═══════════════════════════════════════════════════════════════════
# Punto de entrada
# ═══════════════════════════════════════════════════════════════════

if __name__ == "__main__":
    try:
        zip_path = paso0_bienvenida()
        paso1_extraer(zip_path)
        paso2_dependencias()
        paso3_passenger()
        paso4_base_de_datos()
        paso5_permisos()
        app_ok = paso6_prueba()
        paso7_resumen(app_ok)
    except KeyboardInterrupt:
        print(f"\n\n  {AMARILLO}Instalación cancelada por el usuario.{RESET}\n")
        sys.exit(0)
    except SystemExit:
        raise
    except Exception as e:
        print(f"\n  {ROJO}Error inesperado:{RESET} {e}")
        import traceback
        traceback.print_exc()
        sys.exit(1)
