"""
auditoria_drive.py
Diagnóstico completo de la integración con Google Drive (OAuth2 token.json).
Uso: python3 auditoria_drive.py
"""

import os
import sys
import io
import json
import socket
import datetime
import traceback
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent

# ─────────────────────────────────────────────────────────────
#  Helpers de salida
# ─────────────────────────────────────────────────────────────

class C:
    OK   = '\033[92m'
    WARN = '\033[93m'
    ERR  = '\033[91m'
    BOLD = '\033[1m'
    RST  = '\033[0m'

def titulo(texto):
    print(f"\n{C.BOLD}{'=' * 62}{C.RST}")
    print(f"{C.BOLD}  {texto}{C.RST}")
    print(f"{C.BOLD}{'=' * 62}{C.RST}")

def ok(texto):   print(f"  {C.OK}[OK]{C.RST}    {texto}")
def warn(texto): print(f"  {C.WARN}[WARN]{C.RST}  {texto}")
def err(texto):  print(f"  {C.ERR}[ERROR]{C.RST} {texto}")
def info(texto): print(f"          {texto}")

# ─────────────────────────────────────────────────────────────
#  1. ENTORNO
# ─────────────────────────────────────────────────────────────

titulo("1. ENTORNO PYTHON")
import platform
ok(f"Python {sys.version.split()[0]}  —  {sys.executable}")
ok(f"Plataforma: {platform.platform()}")

# ─────────────────────────────────────────────────────────────
#  2. VERIFICACIÓN DE CÓDIGO
# ─────────────────────────────────────────────────────────────

titulo("2. VERIFICACIÓN DE CÓDIGO")

utils_path = BASE_DIR / 'tramites' / 'utils.py'
if utils_path.exists():
    utils_text = utils_path.read_text(encoding='utf-8')

    if 'token.json' in utils_text and 'Credentials.from_authorized_user_file' in utils_text:
        ok("tramites/utils.py  →  usa OAuth2 token.json  (correcto)")
    elif 'service_account' in utils_text:
        err("tramites/utils.py  →  usa Service Account  (debe cambiarse a OAuth2)")
    else:
        warn("tramites/utils.py  →  no se pudo determinar el método de autenticación")

    if '_obtener_credenciales' in utils_text and 'creds.refresh(Request())' in utils_text:
        ok("tramites/utils.py  →  tiene refresh automático de token  ✓")
    else:
        err("tramites/utils.py  →  NO tiene lógica de refresh automático")

    if "permissions().create" in utils_text and "'type': 'anyone'" in utils_text:
        ok("tramites/utils.py  →  permisos públicos al subir  (anyone reader)  ✓")
    else:
        warn("tramites/utils.py  →  permissions().create con anyone NO encontrado")
else:
    err("tramites/utils.py no encontrado")

# ─────────────────────────────────────────────────────────────
#  3. TOKEN.JSON — OAuth2
# ─────────────────────────────────────────────────────────────

titulo("3. TOKEN.JSON — OAUTH2")

token_path = BASE_DIR / 'token.json'
oauth_creds = None
SCOPES = ['https://www.googleapis.com/auth/drive']

if not token_path.exists():
    err(f"token.json NO encontrado en {token_path}")
    info("→ Corre generar_token.py en tu máquina local y sube token.json al servidor.")
else:
    ok(f"token.json existe  ({token_path.stat().st_size} bytes)")
    try:
        data = json.loads(token_path.read_text(encoding='utf-8'))

        if data.get('client_id') and data.get('client_secret') and not data.get('private_key'):
            ok("Tipo: authorized_user (OAuth2)  ✓")
        elif data.get('type') == 'service_account':
            err("Tipo: service_account  (debe ser authorized_user / OAuth2)")
        else:
            warn(f"Tipo desconocido — claves presentes: {list(data.keys())}")

        if data.get('refresh_token'):
            ok("refresh_token presente  ✓")
        else:
            err("refresh_token AUSENTE — el token no se puede refrescar automáticamente")
            info("→ Corre generar_token.py con access_type=offline y sube el nuevo token.json")

        if data.get('client_id') and data.get('client_secret'):
            ok("client_id y client_secret presentes  ✓")
        else:
            err("client_id o client_secret AUSENTES")

        # Cargar credenciales y verificar expiración
        try:
            from google.oauth2.credentials import Credentials
            from google.auth.transport.requests import Request

            oauth_creds = Credentials.from_authorized_user_file(str(token_path), SCOPES)

            if oauth_creds.expiry:
                ahora  = datetime.datetime.utcnow()
                expiry = oauth_creds.expiry.replace(tzinfo=None) if oauth_creds.expiry.tzinfo else oauth_creds.expiry
                minutos = (expiry - ahora).total_seconds() / 60
                if minutos > 0:
                    ok(f"Token expira en {int(minutos)} minutos  ({expiry.strftime('%Y-%m-%d %H:%M:%S')} UTC)")
                else:
                    warn(f"Token EXPIRADO hace {int(-minutos)} minutos — se refrescará automáticamente")
            else:
                warn("expiry no definido en el token")

            ok(f"valid={oauth_creds.valid}  expired={oauth_creds.expired}")

        except Exception as e:
            oauth_creds = None
            err(f"No se pudieron cargar las credenciales OAuth2: {e}")

    except json.JSONDecodeError as e:
        err(f"token.json tiene JSON inválido: {e}")

# ─────────────────────────────────────────────────────────────
#  4. RED — conectividad a Google
# ─────────────────────────────────────────────────────────────

titulo("4. RED — CONECTIVIDAD A GOOGLE")

def tcp_check(host, port, timeout=8, force_ipv4=True):
    family = socket.AF_INET if force_ipv4 else socket.AF_UNSPEC
    try:
        resultados = socket.getaddrinfo(host, port, family, socket.SOCK_STREAM)
        if not resultados:
            return False, None, None, "Sin resultados DNS"
        ip = resultados[0][4][0]
        t0 = datetime.datetime.now()
        with socket.create_connection((ip, port), timeout=timeout):
            ms = int((datetime.datetime.now() - t0).total_seconds() * 1000)
        return True, ip, ms, None
    except Exception as e:
        return False, None, None, str(e)

HOSTS = [
    ('oauth2.googleapis.com', 443, 'OAuth2 token refresh — DEBE funcionar'),
    ('www.googleapis.com',    443, 'Drive API            — DEBE funcionar'),
    ('accounts.google.com',  443, 'Google accounts      — informativo'),
]

for host, port, label in HOSTS:
    exito, ip, ms, error_msg = tcp_check(host, port)
    if exito:
        ok(f"{host}:{port}  →  {ip}  {ms}ms  ({label})")
    else:
        if 'oauth2' in host or 'accounts' in host:
            err(f"{host}:{port}  →  FALLO: {error_msg}")
            info(f"         ({label})")
            if 'oauth2' in host:
                info("  → El token NO se podrá refrescar automáticamente en el servidor.")
                info("  → Habla con el soporte del hosting para abrir el puerto 443 a oauth2.googleapis.com")
        else:
            err(f"{host}:{port}  →  FALLO: {error_msg}  ({label})")

# ─────────────────────────────────────────────────────────────
#  5. REFRESH AUTOMÁTICO DEL TOKEN
# ─────────────────────────────────────────────────────────────

titulo("5. REFRESH AUTOMÁTICO DEL TOKEN")

if oauth_creds is None:
    err("Sin credenciales OAuth2 cargadas — omitiendo prueba de refresh")
else:
    try:
        from google.auth.transport.requests import Request
        info("Intentando refrescar el token con oauth2.googleapis.com ...")
        oauth_creds.refresh(Request())
        with open(token_path, 'w', encoding='utf-8') as f:
            f.write(oauth_creds.to_json())
        ok(f"Refresh OK  →  nuevo expiry: {oauth_creds.expiry}")
        ok("El token se guardó actualizado en token.json  ✓")
    except Exception as e:
        err(f"Refresh FALLÓ: {e}")
        info("  → Si el error es ConnectTimeout o similar, el servidor no puede llegar a")
        info("    oauth2.googleapis.com:443. Reporta esto al soporte de hosting.")
        info("  → Solución temporal: sube token.json manualmente cada hora con renovar_token.py")

# ─────────────────────────────────────────────────────────────
#  6. PRUEBA REAL DE DRIVE (list + upload + permissions + delete)
# ─────────────────────────────────────────────────────────────

titulo("6. PRUEBA REAL: OAUTH2 + GOOGLE DRIVE")

folder_id = os.environ.get('GOOGLE_DRIVE_FOLDER_ID')
if not folder_id:
    env_path = BASE_DIR / '.env'
    if env_path.exists():
        for line in env_path.read_text(encoding='utf-8').splitlines():
            if line.startswith('GOOGLE_DRIVE_FOLDER_ID='):
                folder_id = line.split('=', 1)[1].strip().strip('"').strip("'")
                break

if not folder_id:
    err("GOOGLE_DRIVE_FOLDER_ID no encontrado en .env ni en variables de entorno")
    info("→ Agrega GOOGLE_DRIVE_FOLDER_ID=<id_carpeta> en el .env del proyecto")
elif oauth_creds is None or not oauth_creds.valid:
    err("Sin credenciales válidas — omitiendo prueba de Drive")
    info("→ Resuelve los errores de las secciones 3/5 primero")
else:
    ok(f"GOOGLE_DRIVE_FOLDER_ID = {folder_id[:8]}…{folder_id[-4:]}")
    drive_service = None

    info("")
    info("Construyendo servicio Drive …")
    try:
        from googleapiclient.discovery import build
        from googleapiclient.http import MediaIoBaseUpload
        drive_service = build('drive', 'v3', credentials=oauth_creds)
        ok("Servicio Drive construido  ✓")
    except Exception as e:
        err(f"No se pudo construir el servicio Drive: {e}")

    if drive_service:
        # 6a. Listar archivos
        info("")
        info("Listando archivos en la carpeta …")
        try:
            resp = drive_service.files().list(
                q=f"'{folder_id}' in parents and trashed=false",
                fields="files(id, name)",
                pageSize=5,
                orderBy="createdTime desc",
                spaces='drive',
            ).execute()
            archivos = resp.get('files', [])
            ok(f"Listado OK  →  {len(archivos)} archivo(s) recientes")
            for a in archivos:
                info(f"  • {a['name'][:60]}")
        except Exception as e:
            err(f"Error al listar archivos: {e}")
            if 'notFound' in str(e) or '404' in str(e):
                info("→ La carpeta no existe o la cuenta no tiene acceso.")
            elif '403' in str(e):
                info("→ Permiso denegado. Verifica el GOOGLE_DRIVE_FOLDER_ID.")

        # 6b. Subir PDF de prueba con nombre acentuado
        info("")
        info("Subiendo PDF de prueba (nombre con caracteres especiales) …")
        test_file_id = None
        nombre_prueba = 'TESIS_PRUEBA_AUDIT_ÉÑÍÓ_' + datetime.datetime.utcnow().strftime('%Y%m%d_%H%M%S') + '.pdf'
        try:
            contenido = b"%PDF-1.4\nTEST_OAUTH2_" + datetime.datetime.utcnow().isoformat().encode()
            fh = io.BytesIO(contenido)
            media = MediaIoBaseUpload(fh, mimetype='application/pdf', resumable=True)
            archivo = drive_service.files().create(
                body={'name': nombre_prueba, 'parents': [folder_id]},
                media_body=media,
                fields='id',
            ).execute()
            test_file_id = archivo.get('id')
            ok(f"Upload OK  →  id: {test_file_id}")
            ok(f"Nombre con acentos: {nombre_prueba}")
        except Exception as e:
            err(f"Upload FALLÓ: {e}")

        # 6c. Aplicar permiso público
        if test_file_id:
            info("")
            info("Aplicando permiso público (anyone reader) …")
            try:
                drive_service.permissions().create(
                    fileId=test_file_id,
                    body={'type': 'anyone', 'role': 'reader'},
                ).execute()
                ok(f"Permiso público OK  →  https://drive.google.com/file/d/{test_file_id}/view")
            except Exception as e:
                err(f"permissions().create FALLÓ: {e}")

            # 6d. Eliminar archivo de prueba
            info("")
            info("Eliminando archivo de prueba …")
            try:
                drive_service.files().delete(fileId=test_file_id).execute()
                ok("Archivo de prueba eliminado  ✓")
            except Exception as e:
                warn(f"No se pudo eliminar el archivo de prueba: {e}")

# ─────────────────────────────────────────────────────────────
#  7. BASE DE DATOS — tesis atascadas en UPLOADING
# ─────────────────────────────────────────────────────────────

titulo("7. BASE DE DATOS — TESIS ATASCADAS EN UPLOADING")

try:
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sistema_titulacion.settings')
    if str(BASE_DIR) not in sys.path:
        sys.path.insert(0, str(BASE_DIR))
    import django
    django.setup()

    from tramites.models import Tesis
    atascadas = Tesis.objects.filter(archivo_drive_id='UPLOADING')
    if atascadas.exists():
        warn(f"{atascadas.count()} tesis atascada(s) en estado UPLOADING:")
        for t in atascadas:
            alumno = t.alumno.get_full_name() or t.alumno.username
            info(f"  • Tesis ID {t.id}  —  {alumno}")
            info(f"    Reset: POST /tramites/admin/tesis/{t.id}/reiniciar-subida/")
        info("")
        info("→ Para resetear: ve a Django Admin → Tesis → cambia archivo_drive_id a vacío")
    else:
        ok("Sin tesis atascadas en estado UPLOADING  ✓")

    total      = Tesis.objects.count()
    con_archivo = Tesis.objects.exclude(archivo_drive_id__isnull=True).exclude(archivo_drive_id='').count()
    ok(f"Total tesis: {total}  |  Con archivo en Drive: {con_archivo}")

except Exception as e:
    warn(f"No se pudo consultar la BD: {e}")
    info(f"  {e}")

# ─────────────────────────────────────────────────────────────
#  RESUMEN
# ─────────────────────────────────────────────────────────────

titulo("RESUMEN — QUÉ HACER SI ALGO FALLÓ")

print(f"""
  {C.BOLD}Si sección 3 falla (token.json):{C.RST}
  • Corre generar_token.py en tu máquina local para obtener un token.json
    con refresh_token. Luego súbelo al servidor en la raíz del proyecto.

  {C.BOLD}Si sección 5 falla (refresh) con ConnectTimeout:{C.RST}
  • El servidor no puede conectarse a oauth2.googleapis.com:443.
  • Solución A (preferida): Pide al soporte del hosting que abra ese puerto.
  • Solución B (temporal): Corre renovar_token.py en tu máquina local cada 45 min
    para mantener el token fresco y subirlo automáticamente por FTP.
    Cron local (Mac/Linux):
      */45 * * * * cd /ruta/del/proyecto && python3 renovar_token.py

  {C.BOLD}Si sección 6 falla con 403 o notFound:{C.RST}
  • Verifica que GOOGLE_DRIVE_FOLDER_ID está correcto en el .env del servidor.
  • La cuenta OAuth2 debe tener acceso a esa carpeta en Drive.

  {C.BOLD}Si sección 6 pasa todo en verde:{C.RST}
  • El sistema está listo. Reinicia el servidor (touch tmp/restart.txt).
  • Las tesis atascadas en UPLOADING (sección 7) resetéalas desde Django Admin
    o POST a /tramites/admin/tesis/<id>/reiniciar-subida/
""")
