import os
import json
import shutil
import tempfile
import zipfile
from datetime import datetime
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, send_file, send_from_directory
from config import Config
from database import init_db
from models import Server, Application, Deployment, DatabaseConfig, Backup
from modules.ssh_manager import ensure_ssh_key, test_connection, execute_command, download_file
from modules.system_monitor import fetch_server_metrics, check_app_live_health
from modules.vps_scanner import run_deep_vps_scan
from modules.app_discovery import discover_applications
from modules.deployer import execute_deployment, execute_rollback
from modules.backups import create_database_backup
from modules.nginx_manager import setup_nginx_reverse_proxy
from modules.scheduler import start_background_monitor
from modules.port_manager import get_next_available_port, get_server_ports_summary
from modules.db_detector import detect_app_database
from modules.vps_file_manager import list_remote_dir, read_remote_file_content, create_live_vps_snapshot

app = Flask(__name__)
app.config.from_object(Config)

# Remove limit on upload content length (allow unlimited/2GB ZIP uploads)
app.config['MAX_CONTENT_LENGTH'] = None

# Initialize Database & Background Monitor
init_db()
start_background_monitor(interval_seconds=60)

@app.errorhandler(413)
def request_entity_too_large(error):
    return jsonify({
        'status': 'failed',
        'error': 'O arquivo ZIP enviado excede o limite permitido pelo servidor. Remova pastas venv/node_modules do ZIP.',
        'logs': [f"[{datetime.now().strftime('%H:%M:%S')}] [ERROR] 413 Request Entity Too Large: O arquivo ZIP ultrapassou a capacidade do upload."]
    }), 413

@app.route('/favicon.ico')
def favicon():
    return '', 204

@app.route('/')
def dashboard():
    servers = Server.get_all()
    pub_key = ensure_ssh_key()
    return render_template('dashboard.html', servers=servers, pub_key=pub_key)

@app.route('/api/servers')
def api_servers():
    servers = Server.get_all()
    return jsonify(servers)

@app.route('/server/<int:server_id>')
def server_detail(server_id):
    server = Server.get_by_id(server_id)
    if not server:
        flash('Servidor não encontrado.', 'danger')
        return redirect(url_for('dashboard'))
    
    port_summary = get_server_ports_summary(server)
    scan_timestamp = datetime.now().strftime('%d/%m/%Y %H:%M:%S')
    return render_template('server_detail.html', server=server, port_summary=port_summary, scan_timestamp=scan_timestamp)

@app.route('/api/server/<int:server_id>/port-summary')
def port_summary_route(server_id):
    server_data = Server.get_by_id(server_id)
    if not server_data:
        return jsonify({'error': 'Servidor não encontrado'}), 404
    summary = get_server_ports_summary(server_data)
    return jsonify(summary)

@app.route('/server/add', methods=['POST'])
def add_server():
    name = request.form.get('name')
    hostname = request.form.get('hostname')
    ip = request.form.get('ip')
    ipv6 = request.form.get('ipv6')
    ssh_port = int(request.form.get('ssh_port', 22))
    ssh_user = request.form.get('ssh_user', 'root')
    auth_type = request.form.get('auth_type', 'key')
    password = request.form.get('password')
    os_info = request.form.get('os_info', 'Ubuntu 24.04 LTS')

    sid = Server.create(
        name=name, hostname=hostname, ip=ip, ipv6=ipv6, ssh_port=ssh_port,
        ssh_user=ssh_user, auth_type=auth_type, password=password, os_info=os_info
    )
    
    server_data = Server.get_by_id(sid)
    fetch_server_metrics(server_data)
    
    flash(f'Servidor {name} cadastrado com sucesso!', 'success')
    return redirect(url_for('server_detail', server_id=sid))

@app.route('/server/<int:server_id>/refresh', methods=['POST'])
def refresh_server(server_id):
    server_data = Server.get_by_id(server_id)
    if not server_data:
        return jsonify({'error': 'Servidor não encontrado'}), 404
    updated = fetch_server_metrics(server_data)
    return jsonify(updated)

@app.route('/server/<int:server_id>/deep-scan', methods=['POST'])
def deep_scan_route(server_id):
    server_data = Server.get_by_id(server_id)
    if not server_data:
        return jsonify({'error': 'Servidor não encontrado'}), 404
    try:
        scan_results = run_deep_vps_scan(server_data)
        fetch_server_metrics(server_data)
        return jsonify({'status': 'success', 'data': scan_results})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/server/<int:server_id>/discover', methods=['POST'])
def discover_apps_route(server_id):
    server_data = Server.get_by_id(server_id)
    if not server_data:
        return jsonify({'error': 'Servidor não encontrado'}), 404
    apps = discover_applications(server_data)
    return jsonify({'applications': apps})

@app.route('/server/<int:server_id>/import-discovered-port', methods=['POST'])
def import_discovered_port_route(server_id):
    try:
        port_raw = request.form.get('port')
        if not port_raw or not port_raw.isdigit():
            flash('Porta inválida para importação.', 'danger')
            return redirect(url_for('server_detail', server_id=server_id))

        port = int(port_raw)
        name = request.form.get('name', f'Aplicação na Porta :{port}').strip()
        path = request.form.get('path', f'/var/www/app_{port}').strip()

        server_data = Server.get_by_id(server_id)
        if not server_data:
            flash('Servidor não encontrado.', 'danger')
            return redirect(url_for('dashboard'))

        public_ip = server_data['ip']

        aid = Application.create(
            server_id=server_id,
            name=name,
            access_type='ip_direct',
            current_access_url=f"http://{public_ip}:{port}",
            active_domain=None,
            detected_alias=None,
            external_port=port,
            internal_port=port,
            bind_ip='0.0.0.0',
            path=path,
            framework='Python Flask' if 'app.py' in path or 'python' in path else 'Node.js',
            service_name=f"app_{port}.service",
            nginx_usage='Não utilizado para o acesso atual'
        )
        flash(f'Aplicação "{name}" (:{port}) importada com sucesso para o DeployHub!', 'success')
        return redirect(url_for('app_detail', app_id=aid))
    except Exception as e:
        app.logger.exception("Falha ao importar aplicação descoberta")
        flash(f'Erro ao importar aplicação: {str(e)}', 'danger')
        return redirect(url_for('server_detail', server_id=server_id))

@app.route('/app/<int:app_id>')
def app_detail(app_id):
    app_data = Application.get_by_id(app_id)
    if not app_data:
        flash('Aplicação não encontrada.', 'danger')
        return redirect(url_for('dashboard'))

    health = check_app_live_health(app_data['server'], app_data)
    app_data['status'] = health['status']
    app_data['status_color'] = health['status_color']
    app_data['badge_cls'] = health['badge_cls']

    deployments = app_data['deployments']
    valid_releases = [d for d in deployments if d['status'] == 'success']
    
    total_artifact_mb = 0.0
    for d in deployments:
        if d.get('stored_zip_path') and os.path.exists(d['stored_zip_path']):
            total_artifact_mb += round(os.path.getsize(d['stored_zip_path']) / (1024 * 1024), 1)

    impact = {
        'release_count': len(deployments),
        'valid_release_count': len(valid_releases),
        'artifact_storage_mb': round(total_artifact_mb, 1),
        'db_backup_count': len(app_data.get('backups', []))
    }

    return render_template('app_detail.html', app=app_data, health=health, impact=impact)

@app.route('/app/<int:app_id>/api/files')
def api_list_files(app_id):
    app_data = Application.get_by_id(app_id)
    if not app_data:
        return jsonify({'error': 'Aplicação não encontrada'}), 404

    subpath = request.args.get('subpath', '')
    res = list_remote_dir(app_data['server'], app_data['path'], subpath)
    return jsonify(res)

@app.route('/app/<int:app_id>/api/file/view')
def api_view_file(app_id):
    app_data = Application.get_by_id(app_id)
    if not app_data:
        return jsonify({'error': 'Aplicação não encontrada'}), 404

    rel_path = request.args.get('rel_path', '')
    res = read_remote_file_content(app_data['server'], app_data['path'], rel_path)
    return jsonify(res)

@app.route('/app/<int:app_id>/snapshot/create', methods=['POST'])
def create_snapshot_route(app_id):
    app_data = Application.get_by_id(app_id)
    if not app_data:
        return jsonify({'error': 'Aplicação não encontrada'}), 404

    try:
        snap = create_live_vps_snapshot(app_data['server'], app_data)
        Backup.create(
            application_id=app_id,
            database_id=None,
            backup_type='snapshot',
            file_name=snap['file_name'],
            file_size_mb=snap['size_mb'],
            remote_path=snap['remote_path'],
            local_path=None
        )
        flash(f'Snapshot do estado atual do VPS criado com sucesso ({snap["size_mb"]} MB)!', 'success')
        return jsonify({'status': 'success', 'snapshot': snap})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/app/<int:app_id>/export-full')
def export_full_route(app_id):
    app_data = Application.get_by_id(app_id)
    if not app_data:
        flash('Aplicação não encontrada.', 'danger')
        return redirect(url_for('dashboard'))

    app_slug = app_data['name'].lower().replace(' ', '_').replace('-', '_')
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    export_filename = f"export_full_{app_slug}_{timestamp}.zip"
    export_dir = os.path.join(Config.UPLOADS_DIR, 'exports')
    os.makedirs(export_dir, exist_ok=True)
    export_path = os.path.join(export_dir, export_filename)

    with zipfile.ZipFile(export_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
        manifest = {
            'application_name': app_data['name'],
            'framework': app_data['framework'],
            'exported_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
            'access_url': app_data['current_access_url'],
            'internal_port': app_data['internal_port']
        }
        zipf.writestr('manifest.json', json.dumps(manifest, indent=2))

        for dep in app_data['deployments']:
            if dep.get('stored_zip_path') and os.path.exists(dep['stored_zip_path']):
                zipf.write(dep['stored_zip_path'], f"releases/{os.path.basename(dep['stored_zip_path'])}")

    return send_file(
        os.path.abspath(export_path),
        as_attachment=True,
        download_name=export_filename
    )

@app.route('/app/<int:app_id>/restart', methods=['POST'])
def restart_app_route(app_id):
    app_data = Application.get_by_id(app_id)
    if not app_data:
        return jsonify({'error': 'Aplicação não encontrada'}), 404

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

    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')

    exit_code, out, err = execute_command(srv, f"systemctl restart {service_name}")
    if exit_code == 0:
        flash(f'Serviço {service_name} reiniciado com sucesso no VPS!', 'success')
        return jsonify({'status': 'success'})
    else:
        return jsonify({'error': f'Falha ao reiniciar serviço: {err}'}), 500

@app.route('/app/<int:app_id>/live-logs')
def app_live_logs_route(app_id):
    app_data = Application.get_by_id(app_id)
    if not app_data:
        return jsonify({'error': 'Aplicação não encontrada'}), 404

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

    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')

    exit_code, out, err = execute_command(srv, f"journalctl -u {service_name} -n 100 --no-pager")
    return jsonify({'service_name': service_name, 'logs': out if exit_code == 0 else f"Erro ao ler logs: {err}"})

@app.route('/app/<int:app_id>/detect-db', methods=['POST'])
def detect_db_route(app_id):
    app_data = Application.get_by_id(app_id)
    if not app_data:
        return jsonify({'error': 'Aplicação não encontrada'}), 404

    detected = detect_app_database(app_data['server'], app_data)
    if detected and detected['db_type'] != 'none':
        DatabaseConfig.create(
            server_id=app_data['server_id'],
            application_id=app_id,
            db_type=detected['db_type'],
            name=detected['name'],
            user='postgres' if detected['db_type'] == 'postgresql' else 'root',
            password='',
            host=detected['host'],
            port=detected['port']
        )
        flash(f'Banco {detected["db_type"].upper()} detectado e vinculado com sucesso!', 'success')
        return jsonify({'status': 'success', 'detected': detected})

    flash('Nenhum banco de dados configurado nos arquivos da aplicação.', 'warning')
    return jsonify({'status': 'none'})

@app.route('/app/add', methods=['POST'])
def add_app():
    server_id = int(request.form.get('server_id'))
    name = request.form.get('name')
    domain = request.form.get('domain', '').strip()
    port_input = request.form.get('internal_port', '').strip()
    path = request.form.get('path')
    framework = request.form.get('framework', 'Python Flask')
    db_type = request.form.get('db_type', 'none')

    server_data = Server.get_by_id(server_id)
    public_ip = server_data['ip'] if server_data else '31.97.147.114'

    if port_input and port_input.isdigit() and int(port_input) > 0:
        internal_port = int(port_input)
    else:
        internal_port = get_next_available_port(server_data)

    if domain:
        access_type = 'nginx_proxy'
        current_access_url = f"https://{domain}"
        active_domain = domain
        detected_alias = None
        external_port = 443
        nginx_usage = f"Nginx Reverse Proxy (:443 ➔ :{internal_port})"
        service_name = f"{domain.split('.')[0]}.service"
    else:
        access_type = 'ip_direct'
        current_access_url = f"http://{public_ip}:{internal_port}"
        active_domain = None
        detected_alias = None
        external_port = internal_port
        nginx_usage = "Não utilizado para o acesso atual"
        service_name = f"{name.lower().replace(' ', '_')}.service"

    aid = Application.create(
        server_id=server_id,
        name=name,
        access_type=access_type,
        current_access_url=current_access_url,
        active_domain=active_domain,
        detected_alias=detected_alias,
        external_port=external_port,
        internal_port=internal_port,
        bind_ip='0.0.0.0' if access_type == 'ip_direct' else '127.0.0.1',
        path=path,
        framework=framework,
        service_name=service_name,
        nginx_usage=nginx_usage
    )

    if db_type != 'none':
        DatabaseConfig.create(
            server_id=server_id,
            application_id=aid,
            db_type=db_type,
            name=f"{name.lower().replace(' ', '_')}_db",
            user='postgres' if db_type == 'postgresql' else 'root',
            password='',
            host='localhost',
            port=5432 if db_type == 'postgresql' else 3306
        )

    if domain:
        setup_nginx_reverse_proxy(server_data, domain, internal_port)

    flash(f'Aplicação {name} cadastrada com sucesso na porta :{internal_port}!', 'success')
    return redirect(url_for('app_detail', app_id=aid))

@app.route('/app/<int:app_id>/update-entrypoint', methods=['POST'])
def update_entrypoint_route(app_id):
    entry_file = request.form.get('entrypoint_file', 'app.py').strip()
    entry_obj = request.form.get('entrypoint_object', 'app').strip()
    entry_cmd = request.form.get('entrypoint_cmd', '').strip()
    
    Application.update_entrypoint(app_id, entry_file, entry_obj, entry_cmd or None)
    flash('Configuração de Entrypoint e Execução atualizada!', 'success')
    return redirect(url_for('app_detail', app_id=app_id))

@app.route('/app/<int:app_id>/deploy', methods=['POST'])
def deploy_app(app_id):
    upload_path = None
    try:
        # Pre-deploy local disk space check
        _, _, free_bytes = shutil.disk_usage('.')
        local_free_mb = round(free_bytes / (1024 * 1024), 1)
        if local_free_mb < 300.0:
            return jsonify({
                'status': 'failed',
                'error': f'❌ DEPLOY BLOQUEADO: Espaço crítico no disco local ({local_free_mb} MB livres). Libere espaço no servidor DeployHub.'
            }), 400

        app_data = Application.get_by_id(app_id)
        if not app_data:
            return jsonify({'status': 'failed', 'error': 'Aplicação não encontrada'}), 404

        if 'zip_file' not in request.files:
            return jsonify({'status': 'failed', 'error': 'Nenhum arquivo ZIP foi enviado'}), 400

        zip_file = request.files['zip_file']
        version = request.form.get('version', 'v1.0')
        description = request.form.get('description', 'Atualização via DeployHub')

        if zip_file.filename == '':
            return jsonify({'status': 'failed', 'error': 'Arquivo de upload inválido ou vazio'}), 400

        os.makedirs(Config.UPLOADS_DIR, exist_ok=True)
        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        upload_path = os.path.join(Config.UPLOADS_DIR, f"temp_{app_id}_{version}_{timestamp}.zip")
        zip_file.save(upload_path)

        res = execute_deployment(app_data, upload_path, version, description)
        
        if res.get('status') == 'success':
            return jsonify(res), 200
        else:
            return jsonify(res), 500

    except Exception as e:
        app.logger.exception("Exceção não tratada durante pipeline de deploy")
        return jsonify({
            'status': 'failed',
            'error': f"Exceção no backend do DeployHub: {str(e)}",
            'logs': [f"[{datetime.now().strftime('%H:%M:%S')}] [ERROR] Exceção no servidor Flask: {str(e)}"]
        }), 500
    finally:
        # Automatically clean up temporary upload file from local disk to prevent Errno 28
        if upload_path and os.path.exists(upload_path):
            try:
                os.remove(upload_path)
            except Exception:
                pass

@app.route('/app/<int:app_id>/release/<int:deployment_id>/download')
def download_release_zip(app_id, deployment_id):
    app_data = Application.get_by_id(app_id)
    if not app_data:
        flash('Aplicação não encontrada.', 'danger')
        return redirect(url_for('dashboard'))

    target = next((d for d in app_data['deployments'] if d['id'] == deployment_id), None)
    if not target or not target.get('stored_zip_path'):
        flash('Artefato ZIP original não encontrado para esta release.', 'danger')
        return redirect(url_for('app_detail', app_id=app_id))

    stored_path = target['stored_zip_path']

    if os.path.exists(stored_path):
        return send_file(
            os.path.abspath(stored_path),
            as_attachment=True,
            download_name=f"release_{target['release_number']}_{target['version']}.zip"
        )

    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')

    temp_dl = os.path.join(Config.UPLOADS_DIR, f"download_{deployment_id}.zip")
    try:
        download_file(srv, stored_path, temp_dl)
        return send_file(
            os.path.abspath(temp_dl),
            as_attachment=True,
            download_name=f"release_{target['release_number']}_{target['version']}.zip"
        )
    except Exception as e:
        flash(f'Falha ao baixar release do VPS: {str(e)}', 'danger')
        return redirect(url_for('app_detail', app_id=app_id))

@app.route('/app/<int:app_id>/rollback/<int:deployment_id>', methods=['POST'])
def rollback_app(app_id, deployment_id):
    try:
        app_data = Application.get_by_id(app_id)
        if not app_data:
            return jsonify({'success': False, 'error': 'Aplicação não encontrada'}), 404

        deployments = app_data['deployments']
        target = next((d for d in deployments if d['id'] == deployment_id), None)
        
        target_artifact = target.get('stored_zip_path') or target.get('backup_path')
        if not target or not target_artifact:
            return jsonify({'success': False, 'error': f'Arquivo de release não encontrado para a Release #{target.get("release_number", deployment_id)}'}), 400

        success = execute_rollback(app_data, target_artifact)
        if success:
            Deployment.set_active_release(app_id, deployment_id)
            return jsonify({'success': True, 'message': f'Reversão para a Release #{target["release_number"]} ({target["version"]}) concluída com sucesso!'})
        else:
            return jsonify({'success': False, 'error': 'Falha ao restaurar release e reiniciar o serviço Systemd no VPS'}), 500
    except Exception as e:
        app.logger.exception("Erro durante execução do Rollback")
        return jsonify({'success': False, 'error': f'Erro durante rollback: {str(e)}'}), 500

@app.route('/app/<int:app_id>/delete', methods=['POST'])
def delete_app_route(app_id):
    mode = request.form.get('delete_mode', 'remove_db_only')
    app_data = Application.get_by_id(app_id)
    if not app_data:
        flash('Aplicação não encontrada.', 'danger')
        return redirect(url_for('dashboard'))

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

    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')

    if mode in ('uninstall_vps', 'uninstall_vps_and_backups'):
        uninstall_cmd = f"""
        systemctl stop {service_name} 2>/dev/null || true
        systemctl disable {service_name} 2>/dev/null || true
        rm -f /etc/systemd/system/{service_name}
        systemctl daemon-reload
        rm -rf '{app_data['path']}'
        iptables -D INPUT -p tcp --dport {app_data['internal_port']} -j ACCEPT 2>/dev/null || true
        """
        execute_command(srv, uninstall_cmd)

    if mode == 'uninstall_vps_and_backups':
        app_slug = app_data['name'].lower().replace(' ', '_').replace('-', '_')
        local_rel_dir = os.path.join('storage', 'releases', app_slug)
        if os.path.exists(local_rel_dir):
            shutil.rmtree(local_rel_dir, ignore_errors=True)
        execute_command(srv, f"rm -rf /var/backups/deploys/{app_slug} /opt/deployhub/releases/{app_slug} 2>/dev/null || true")

    Application.delete(app_id)
    flash(f'Aplicação {app_data["name"]} removida com sucesso ({mode})!', 'success')
    return redirect(url_for('server_detail', server_id=server_id))

@app.route('/app/<int:app_id>/backup-db', methods=['POST'])
def backup_db_route(app_id):
    app_data = Application.get_by_id(app_id)
    if not app_data or not app_data.get('database'):
        return jsonify({'error': 'Banco de dados não configurado para esta aplicação'}), 400

    keep_local = request.form.get('keep_local', 'true') == 'true'
    try:
        res = create_database_backup(app_data['database'], keep_local=keep_local)
        return jsonify({'status': 'success', 'backup': res})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/security')
def security_page():
    pub_key = ensure_ssh_key()
    servers = Server.get_all()
    return render_template('security.html', pub_key=pub_key, servers=servers)

@app.route('/security/generate-key', methods=['POST'])
def generate_key_route():
    pub_key = ensure_ssh_key()
    return jsonify({'status': 'success', 'public_key': pub_key})

if __name__ == '__main__':
    print("[DeployHub] Servidor rodando em http://127.0.0.1:5000")
    app.run(host='0.0.0.0', port=5000, debug=True)
