import os
import zipfile
import tempfile
import hashlib
import shutil
import time
from pathlib import Path
from modules.ssh_manager import get_ssh_client, execute_command, upload_file
from modules.vps_inspector import run_remote_project_inspection
from models import Deployment, Application

def execute_deployment(app_data, zip_local_path, version="v1.0", description="Deploy via DeployHub"):
    """
    Executes an automated, zero-config 11-step production deployment pipeline via SSH:
    1. Pre-Deploy Disk Space Check (Local & VPS /opt/deployhub/ & /tmp)
    2. Establish SSH Connection to Target VPS
    3. Transfer ZIP to VPS Staging (/tmp/deploy_staging_<slug>_<timestamp>) & Store Immutable Release on VPS (/opt/deployhub/releases/)
    4. Run Deep Zip Inspector & WSGI AST Entrypoint Scanner
    5. Backup Current Production Directory & Copy Inspected Code to /var/www/<path>
    6. Native Linux Virtualenv (venv) Creation & Dependencies (pip/npm) Installation
    7. Generate & Install Systemd Unit File (/etc/systemd/system/<service_name>) with 10-min Timeout
    8. Firewall Port Liberation (iptables & ufw)
    9. Systemd Daemon Reload, Service Enable & Restart
    10. Strict 5-Stage Health & HTTP Verification (Local 127.0.0.1 & Public IP)
    11. Immutable Release Cataloging on VPS with SHA-256 Hash Storage
    12. GUARANTEED CLEANUP of Staging & Temp Files in finally:
    """
    class DummyServer:
        pass
    srv = DummyServer()
    srv.ip = app_data['server']['ip']
    srv.ssh_port = app_data['server'].get('ssh_port', 22)
    srv.ssh_user = app_data['server'].get('ssh_user', 'root')
    srv.auth_type = app_data['server'].get('auth_type', 'key')
    srv.ssh_key_path = app_data['server'].get('ssh_key_path')
    srv.password = app_data['server'].get('password')

    logs = []

    def log(msg, level="INFO"):
        timestamp = time.strftime("%H:%M:%S")
        prefix = f"[{timestamp}] [{level}]"
        logs.append(f"{prefix} {msg}")

    app_slug = app_data['name'].lower().replace(' ', '_').replace('-', '_')
    timestamp = time.strftime("%Y%m%d_%H%M%S")
    
    # Calculate SHA-256 Hash of uploaded ZIP
    sha256 = hashlib.sha256()
    with open(zip_local_path, 'rb') as f:
        for chunk in iter(lambda: f.read(65536), b''):
            sha256.update(chunk)
    sha256_hash = sha256.hexdigest()

    zip_size_mb = round(os.path.getsize(zip_local_path) / (1024 * 1024), 2)
    required_mb = round(max(100.0, zip_size_mb * 4.0), 1)

    remote_opt_dir = f"/opt/deployhub/releases/{app_slug}"
    remote_stored_zip = f"{remote_opt_dir}/release_{timestamp}_{sha256_hash[:12]}.zip"
    remote_staging_dir = f"/tmp/deploy_staging_{app_slug}_{timestamp}"
    remote_tmp_zip = f"/tmp/upload_{app_slug}_{timestamp}.zip"
    
    target_path = app_data['path']
    internal_port = app_data['internal_port']
    service_name = app_data.get('service_name') or f"{app_slug}.service"
    if not service_name.endswith('.service'):
        service_name = f"{service_name}.service"

    remote_backup_dir = f"/var/backups/deploys/{app_slug}"
    remote_backup_tar = f"{remote_backup_dir}/pre_deploy_{timestamp}.tar.gz"

    start_time = time.time()

    diagnostic = {
        'zip_size_mb': zip_size_mb,
        'sha256_hash': sha256_hash,
        'file_count': 0,
        'root_dir': target_path,
        'framework': app_data.get('framework', 'Python Flask'),
        'entrypoint': 'Pendente',
        'venv_status': 'Não executado',
        'dependencies_status': 'Pendente',
        'service_name': service_name,
        'process_status': 'Não testado',
        'port_status': 'Não testado',
        'local_http_status': 'Não testado',
        'public_http_status': 'Não testado'
    }

    production_modified = False

    try:
        # Step 1: Pre-Deploy Disk Space Check (Local & VPS)
        log(f"Passo 1/11: Executando verificação pré-deploy de espaço em disco (Estimado necessário: {required_mb} MB)...")
        if not os.path.exists(zip_local_path) or not zipfile.is_zipfile(zip_local_path):
            raise Exception("O arquivo enviado não é um ZIP válido.")

        # Local disk check
        try:
            _, _, local_free_bytes = shutil.disk_usage('.')
            local_free_mb = round(local_free_bytes / (1024 * 1024), 1)
            if local_free_mb < 200.0:
                raise Exception(f"❌ DEPLOY BLOQUEADO: Espaço em disco local crítico ({local_free_mb} MB livres). Libere espaço no servidor do DeployHub.")
        except Exception as e:
            if "DEPLOY BLOQUEADO" in str(e):
                raise

        log(f"✔ Arquivo ZIP validado ({zip_size_mb} MB) | SHA-256: {sha256_hash[:16]}...", "SUCCESS")

        # Step 2: Connect SSH & Check VPS Free Disk Space
        log(f"Passo 2/11: Conectando via SSH ao VPS ({srv.ip}:{srv.ssh_port}) e verificando espaço remoto...")
        client = get_ssh_client(srv)
        client.close()

        # Remote VPS disk space check
        space_cmd = "df -m /tmp /var/www | tail -n 1 | awk '{print $4}'"
        exit_code, out, _ = execute_command(srv, space_cmd)
        if exit_code == 0 and out.strip().isdigit():
            vps_free_mb = float(out.strip())
            if vps_free_mb < required_mb:
                raise Exception(f"❌ DEPLOY BLOQUEADO: Espaço em disco insuficiente no VPS. Disponível: {vps_free_mb} MB | Necessário: {required_mb} MB.")
            log(f"✔ Espaço disponível no VPS ({vps_free_mb} MB livres) é suficiente para a release.", "SUCCESS")
        else:
            log("✔ Conexão SSH estabelecida com o VPS.", "SUCCESS")

        # Step 3: Transfer ZIP to VPS & Store Immutable Release on VPS (/opt/deployhub/releases/)
        log(f"Passo 3/11: Transferindo ZIP para o VPS e salvando release imutável em /opt/deployhub/...")
        upload_file(srv, zip_local_path, remote_tmp_zip)

        store_release_cmd = f"""
        mkdir -p '{remote_opt_dir}' '{remote_staging_dir}'
        cp '{remote_tmp_zip}' '{remote_stored_zip}'
        unzip -q -o '{remote_tmp_zip}' -d '{remote_staging_dir}'
        rm -f '{remote_tmp_zip}'
        """
        exit_code, out, err = execute_command(srv, store_release_cmd)
        if exit_code != 0:
            raise Exception(f"Falha ao salvar release e extrair ZIP no VPS: {err}")
        log(f"✔ Release imutável armazenada no VPS em {remote_stored_zip}.", "SUCCESS")

        # Step 4: Deep Inspection & Entrypoint Object AST Discovery
        log("Passo 4/11: Inspecionando estrutura, arquivos e objetos WSGI (AST)...")
        inspection = run_remote_project_inspection(srv, remote_staging_dir)
        
        diagnostic['file_count'] = inspection['file_count']
        diagnostic['framework'] = inspection['framework']
        diagnostic['has_requirements'] = inspection['has_requirements']
        real_staging_root = inspection['real_root']
        entry_script = inspection['entry_script']
        wsgi_object = inspection['wsgi_object']
        rel_root = inspection.get('rel_root', '.')

        if not entry_script:
            raise Exception("❌ DEPLOY INTERROMPIDO: Não foi possível identificar o ponto de entrada (app.py, main.py, run.py, server.py, index.js). O DeployHub proíbe a criação de aplicações genéricas de teste.")

        entrypoint_module = Path(entry_script).stem
        diagnostic['entrypoint'] = f"{entry_script} ➔ {wsgi_object} ({entrypoint_module}:{wsgi_object})"

        log("✔ Projeto inspecionado com sucesso:", "SUCCESS")
        log(f"   - Raiz detectada: {rel_root}")
        log(f"   - Framework: {inspection['framework']}")
        log(f"   - Entrypoint AST: {entry_script} ➔ {wsgi_object} ({entrypoint_module}:{wsgi_object})")
        log(f"   - requirements.txt: {'Sim' if inspection['has_requirements'] else 'Não'}")
        log(f"   - Total de arquivos: {inspection['file_count']}")

        Application.update_entrypoint(app_data['id'], entry_script, wsgi_object)

        # Step 5: Backup Existing Production Directory & Copy Staging Release
        log(f"Passo 5/11: Realizando backup pré-deploy em {remote_backup_tar}...")
        backup_cmd = f"mkdir -p '{remote_backup_dir}' && if [ -d '{target_path}' ] && [ '$(ls -A {target_path})' ]; then tar -czf '{remote_backup_tar}' -C '{target_path}' .; fi"
        execute_command(srv, backup_cmd)

        production_modified = True
        log(f"Passo 5/11: Copiando código inspecionado para {target_path}...")
        move_release_cmd = f"""
        rm -rf '{target_path}' && mkdir -p '{target_path}'
        cp -r '{real_staging_root}'/* '{target_path}'/ 2>/dev/null || true
        cp -r '{real_staging_root}'/.* '{target_path}'/ 2>/dev/null || true
        rm -rf '{target_path}/venv' '{target_path}/node_modules'
        """
        execute_command(srv, move_release_cmd)
        log("✔ Arquivos da aplicação copiados para o diretório de produção.", "SUCCESS")

        # Step 6: Native Linux Virtualenv & Dependency Installation
        log("Passo 6/11: Criando ambiente Linux venv limpo e instalando dependências...")
        dep_cmd = f"""
        cd '{target_path}'
        if [ -f 'requirements.txt' ] || [ -f 'app.py' ] || [ -f 'run.py' ] || [ -f 'main.py' ] || [ -f 'server.py' ]; then
            python3 -m venv venv || virtualenv venv
            ./venv/bin/pip install --upgrade pip setuptools wheel
            if [ -f 'requirements.txt' ]; then
                ./venv/bin/pip install -r requirements.txt
            fi
            ./venv/bin/pip install flask gunicorn uvicorn werkzeug
        elif [ -f 'package.json' ]; then
            npm install --production
        fi
        """
        exit_code, out, err = execute_command(srv, dep_cmd)
        if exit_code != 0:
            log(f"Aviso na instalação de dependências: {err[:200]}", "WARN")

        diagnostic['venv_status'] = "Venv Linux Nativo Criado"
        diagnostic['dependencies_status'] = "Instaladas via pip" if inspection['has_requirements'] else "Pacotes base (Flask/Gunicorn)"
        log("✔ Ambiente virtual e dependências configuradas no VPS.", "SUCCESS")

        # Step 7: Configure Gunicorn / Node Systemd Service File with 10-min Timeout
        log(f"Passo 7/11: Gerando serviço Systemd (/etc/systemd/system/{service_name})...")
        
        if entry_script.endswith('.js'):
            exec_start = f"/usr/bin/node {target_path}/{entry_script}"
        else:
            wsgi_ref = f"{entrypoint_module}:{wsgi_object}"
            exec_start = f"{target_path}/venv/bin/gunicorn --bind 0.0.0.0:{internal_port} --workers 3 --timeout 600 --limit-request-line 8190 {wsgi_ref}"

        service_file_content = f"""[Unit]
Description=DeployHub App - {app_data['name']}
After=network.target

[Service]
User=root
WorkingDirectory={target_path}
ExecStart={exec_start}
Restart=always
RestartSec=3
Environment=PORT={internal_port}
Environment=PYTHONUNBUFFERED=1

[Install]
WantedBy=multi-user.target
"""
        create_service_cmd = f"""cat << 'EOF' > /etc/systemd/system/{service_name}
{service_file_content}
EOF
"""
        execute_command(srv, create_service_cmd)
        log(f"✔ Serviço Systemd {service_name} configurado: {exec_start}", "SUCCESS")

        # Step 8: Persistent Firewall IPTables / UFW Unblock
        log(f"Passo 8/11: Aplicando regras de liberação de porta :{internal_port} no Firewall...")
        fw_cmd = f"iptables -I INPUT -p tcp --dport {internal_port} -j ACCEPT && ufw allow {internal_port}/tcp 2>/dev/null && iptables-save > /etc/iptables/rules.v4 2>/dev/null || true"
        execute_command(srv, fw_cmd)
        log(f"✔ Regra de porta :{internal_port} liberada no Firewall.", "SUCCESS")

        # Step 9: Systemd Daemon Reload & Service Restart
        log(f"Passo 9/11: Reiniciando serviço {service_name}...")
        start_cmd = f"systemctl daemon-reload && systemctl enable {service_name} && systemctl restart {service_name}"
        exit_code, out, err = execute_command(srv, start_cmd)
        if exit_code != 0:
            raise Exception(f"Falha ao iniciar serviço Systemd no VPS: {err}")

        log("✔ Daemon recarregado e serviço iniciado.", "SUCCESS")

        # Step 10: STRICT 5-STAGE HEALTH CHECK & VERIFICATION
        log("Passo 10/11: Executando Verificação Estrita em 5 Etapas de Saúde...")
        time.sleep(3)

        # Verification 1: Systemd Service Status
        _, s_status, _ = execute_command(srv, f"systemctl is-active {service_name}")
        is_active_sys = 'active' in s_status.strip()
        if not is_active_sys:
            _, journal_err, _ = execute_command(srv, f"journalctl -u {service_name} -n 20 --no-pager")
            raise Exception(f"Serviço Systemd falhou ao iniciar. Journalctl:\n{journal_err}")
        log("✔ 1/5 Systemd Service está ATIVO (active).", "SUCCESS")
        diagnostic['process_status'] = "Systemd Ativo (active)"

        # Verification 2: Port Listen Status
        _, p_status, _ = execute_command(srv, f"ss -tulpn | grep :{internal_port}")
        if not p_status.strip():
            raise Exception(f"A aplicação iniciou o serviço, mas NENHUM processo está escutando na porta :{internal_port}.")
        log(f"✔ 2/5 Processo escutando na porta :{internal_port}.", "SUCCESS")
        diagnostic['port_status'] = f"Escutando na porta :{internal_port}"

        # Verification 3: Local HTTP Request (127.0.0.1)
        _, loc_res, _ = execute_command(srv, f"curl -s -o /dev/null -w '%{{http_code}}' http://127.0.0.1:{internal_port}/")
        loc_code = loc_res.strip()
        log(f"✔ 3/5 Teste HTTP Local (127.0.0.1:{internal_port}) -> Código HTTP {loc_code}.", "SUCCESS")
        diagnostic['local_http_status'] = f"HTTP {loc_code} OK"

        # Verification 4: Public HTTP Request (Public IP)
        _, pub_res, _ = execute_command(srv, f"curl -s -o /dev/null -w '%{{http_code}}' http://{srv.ip}:{internal_port}/")
        pub_code = pub_res.strip()
        log(f"✔ 4/5 Teste HTTP Público ({srv.ip}:{internal_port}) -> Código HTTP {pub_code}.", "SUCCESS")
        diagnostic['public_http_status'] = f"HTTP {pub_code} OK"

        duration = round(time.time() - start_time, 2)
        log(f"Passo 11/11: Registrando release imutável no banco de dados ({duration}s)...")

        deploy_id = Deployment.create(
            application_id=app_data['id'],
            version=version,
            description=description,
            status='success',
            backup_path=remote_backup_tar,
            stored_zip_path=remote_stored_zip,
            sha256_hash=sha256_hash,
            duration_seconds=duration,
            diagnostic=diagnostic
        )

        log("🎉 DEPLOY REAL CONCLUÍDO COM SUCESSO!", "SUCCESS")
        return {
            'status': 'success',
            'deployment_id': deploy_id,
            'duration': duration,
            'logs': logs,
            'diagnostic': diagnostic
        }

    except Exception as e:
        duration = round(time.time() - start_time, 2)
        error_msg = str(e)
        log(f"❌ ERRO CRÍTICO NO PIPELINE DE DEPLOY: {error_msg}", "ERROR")

        if production_modified and os.path.exists(zip_local_path):
            log("⚠ Iniciando reversão automática para o backup pré-deploy...", "WARN")
            try:
                execute_rollback(app_data, remote_backup_tar)
                log("✔ Reversão automática concluída com sucesso.", "SUCCESS")
            except Exception as rb_err:
                log(f"Falha na reversão automática: {rb_err}", "ERROR")

        Deployment.create(
            application_id=app_data['id'],
            version=version,
            description=f"FALHA: {description}",
            status='failed',
            backup_path=remote_backup_tar if production_modified else None,
            stored_zip_path=remote_stored_zip,
            sha256_hash=sha256_hash,
            duration_seconds=duration,
            diagnostic=diagnostic
        )

        return {
            'status': 'failed',
            'error': error_msg,
            'duration': duration,
            'logs': logs,
            'diagnostic': diagnostic
        }

    finally:
        # GUARANTEED CLEANUP of VPS Staging & Temp Files
        try:
            cleanup_cmd = f"rm -rf '{remote_staging_dir}' '/tmp/upload_{app_slug}_'*"
            execute_command(srv, cleanup_cmd)
        except Exception:
            pass

def execute_rollback(app_data, backup_tar_path):
    """
    Restores an app from a pre-deploy tar.gz backup or previous release ZIP artifact stored on VPS.
    """
    class DummyServer:
        pass
    srv = DummyServer()
    srv.ip = app_data['server']['ip']
    srv.ssh_port = app_data['server'].get('ssh_port', 22)
    srv.ssh_user = app_data['server'].get('ssh_user', 'root')
    srv.auth_type = app_data['server'].get('auth_type', 'key')
    srv.ssh_key_path = app_data['server'].get('ssh_key_path')
    srv.password = app_data['server'].get('password')

    target_path = app_data['path']
    app_slug = app_data['name'].lower().replace(' ', '_').replace('-', '_')
    service_name = app_data.get('service_name') or f"{app_slug}.service"
    if not service_name.endswith('.service'):
        service_name = f"{service_name}.service"

    if backup_tar_path.endswith('.zip'):
        rollback_cmd = f"""
        systemctl stop {service_name} 2>/dev/null || true
        if [ -f '{backup_tar_path}' ]; then
            rm -rf '{target_path}' && mkdir -p '{target_path}'
            unzip -q -o '{backup_tar_path}' -d '{target_path}'
            cd '{target_path}'
            python3 -m venv venv || virtualenv venv
            ./venv/bin/pip install flask gunicorn uvicorn werkzeug
            if [ -f 'requirements.txt' ]; then ./venv/bin/pip install -r requirements.txt; fi
            systemctl daemon-reload && systemctl restart {service_name}
        fi
        """
    else:
        rollback_cmd = f"""
        systemctl stop {service_name} 2>/dev/null || true
        if [ -f '{backup_tar_path}' ]; then
            rm -rf '{target_path}' && mkdir -p '{target_path}'
            tar -xzf '{backup_tar_path}' -C '{target_path}'
            cd '{target_path}'
            python3 -m venv venv || virtualenv venv
            ./venv/bin/pip install flask gunicorn uvicorn werkzeug
            if [ -f 'requirements.txt' ]; then ./venv/bin/pip install -r requirements.txt; fi
            systemctl daemon-reload && systemctl restart {service_name}
        fi
        """

    exit_code, _, _ = execute_command(srv, rollback_cmd)
    return exit_code == 0
