import json
import re
import os
from database import get_db_connection
from modules.ssh_manager import execute_command

SYSTEM_BLACKLIST_PORTS = {22, 25, 53, 80, 110, 143, 443, 465, 587, 993, 995, 3306, 5432, 6379, 8080, 27017, 11211, 33060}

def get_vps_listening_ports(server_data):
    """
    Executes deep SSH inspection to extract:
    - Real listening ports (ss -tulpn)
    - PID and process name
    - REAL working directory via /proc/<PID>/cwd
    - Command line via /proc/<PID>/cmdline
    - Reads package.json / requirements.txt to discover TRUE application name and confidence rating.
    """
    class DummyServer:
        pass
    srv = DummyServer()
    srv.ip = server_data['ip']
    srv.ssh_port = server_data.get('ssh_port', 22)
    srv.ssh_user = server_data.get('ssh_user', 'root')
    srv.auth_type = server_data.get('auth_type', 'key')
    srv.ssh_key_path = server_data.get('ssh_key_path')
    srv.password = server_data.get('password')

    inspection_script = """python3 - << 'EOF'
import os, sys, re, json, subprocess

ports = []

try:
    res = subprocess.run(['ss', '-tulpn', '-n', '-H'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    lines = res.stdout.splitlines()
    
    for line in lines:
        parts = line.split()
        if len(parts) >= 5:
            proto = parts[0]
            local_addr = parts[4]
            proc_info = parts[6] if len(parts) > 6 else ""
            
            if ':' in local_addr:
                port_str = local_addr.split(':')[-1]
                bind_ip = local_addr.rsplit(':', 1)[0]
                
                if port_str.isdigit():
                    port = int(port_str)
                    exposure = "public" if bind_ip in ('0.0.0.0', '*', '::') else "internal"
                    
                    pid = None
                    proc_name = "Unknown"
                    cwd = None
                    cmdline = None
                    app_name = None
                    confidence = "Média"
                    
                    if 'users:' in proc_info:
                        try:
                            proc_name = proc_info.split('(("')[1].split('"')[0]
                            pid_str = proc_info.split('pid=')[1].split(',')[0]
                            pid = int(pid_str)
                        except:
                            pass
                    
                    if pid:
                        try:
                            cwd = os.readlink(f'/proc/{pid}/cwd')
                        except:
                            cwd = None
                        
                        try:
                            with open(f'/proc/{pid}/cmdline', 'rb') as f:
                                raw_cmd = f.read()
                                cmdline = raw_cmd.replace(b'\\x00', b' ').decode('utf-8', errors='ignore').strip()
                        except:
                            cmdline = None
                    
                    if cwd and os.path.exists(cwd):
                        pkg_json = os.path.join(cwd, 'package.json')
                        if os.path.exists(pkg_json):
                            try:
                                with open(pkg_json, 'r', encoding='utf-8', errors='ignore') as f:
                                    pkg_data = json.load(f)
                                    app_name = pkg_data.get('name')
                            except:
                                pass
                        
                        if not app_name:
                            app_name = os.path.basename(cwd).replace('_', ' ').title()
                        
                        confidence = "Alta"
                    else:
                        cwd = f"/var/www/app_{port}"
                    
                    ports.append({
                        'port': port,
                        'protocol': proto,
                        'bind_ip': bind_ip,
                        'exposure': exposure,
                        'process': proc_name,
                        'pid': pid,
                        'cwd': cwd,
                        'cmdline': cmdline,
                        'discovered_name': app_name or f"Aplicação na Porta :{port}",
                        'confidence': confidence
                    })
except Exception as e:
    pass

print(json.dumps(ports))
EOF
"""
    exit_code, out, _ = execute_command(srv, inspection_script)
    if exit_code == 0 and out.strip():
        try:
            return json.loads(out.strip())
        except Exception:
            pass
    return []

def get_next_available_port(server_data, start_range=5000, end_range=5999):
    """
    Guarantees mathematically accurate port allocation by building the complete set of unavailable ports:
    PORTAS INDISPONIVEIS = SYSTEM_BLACKLIST + REAL_SS_LISTENING + DB_REGISTERED_APPS + DB_CONFIGS
    """
    conn = get_db_connection()
    # 1. Registered app internal & external ports across all records
    apps = conn.execute('SELECT internal_port, external_port FROM applications').fetchall()
    db_app_ports = set()
    for a in apps:
        if a['internal_port']:
            db_app_ports.add(int(a['internal_port']))
        if a['external_port']:
            db_app_ports.add(int(a['external_port']))

    # 2. Database configs ports
    db_configs = conn.execute('SELECT port FROM database_configs').fetchall()
    db_config_ports = {int(r['port']) for r in db_configs if r['port']}
    conn.close()

    # 3. Real VPS listening ports via SSH
    try:
        vps_listening_data = get_vps_listening_ports(server_data)
        vps_ports = {int(p['port']) for p in vps_listening_data if 'port' in p}
    except Exception:
        vps_ports = set()

    # Combined set of ALL unavailable ports
    unavailable_ports = SYSTEM_BLACKLIST_PORTS | db_app_ports | db_config_ports | vps_ports

    # Find first free port in 5000-5999 (e.g., 5006 if 5000-5005 are occupied)
    for port in range(start_range, end_range + 1):
        if port not in unavailable_ports:
            return port

    return 5006  # Fallback

def get_server_ports_summary(server_data):
    """
    Generates unified port summary, VPS audit report, and unmanaged app reconciler with real /proc/<PID>/cwd paths.
    """
    vps_ports = get_vps_listening_ports(server_data)
    vps_listening_map = {p['port']: p for p in vps_ports}

    conn = get_db_connection()
    apps = conn.execute('SELECT name, internal_port, external_port, access_type, path FROM applications WHERE server_id = ?', (server_data['id'],)).fetchall()
    conn.close()

    app_port_map = {a['internal_port']: a for a in apps}
    summary = []
    unmanaged_discovered = []

    all_ports_to_list = sorted(list(set(vps_listening_map.keys()) | set(app_port_map.keys()) | {22, 80, 443, 5432, 3306}))

    for p in all_ports_to_list:
        if p in (53, 33060):
            continue

        p_data = vps_listening_map.get(p, {})
        app_info = app_port_map.get(p)

        if p in (22, 80, 443, 5432, 3306):
            type_str = '🔒 Sistema'
            status_str = '🔒 Reservada'
            name_str = {22: 'OpenSSH Server', 80: 'Nginx Reverse Proxy (HTTP)', 443: 'Nginx Reverse Proxy (HTTPS)', 5432: 'PostgreSQL Server', 3306: 'MySQL / MariaDB Server'}.get(p, 'Sistema')
        elif app_info:
            type_str = '🌐 Acesso Direto' if app_info['access_type'] == 'ip_direct' else '🔒 Proxy Interno'
            status_str = '🟢 Ativa (DeployHub)'
            name_str = app_info['name']
        else:
            type_str = '🟡 Não Gerenciada'
            status_str = '🟡 Escutando no VPS'
            name_str = p_data.get('discovered_name') or f'Aplicação na Porta :{p}'
            unmanaged_discovered.append({
                'port': p,
                'name': name_str,
                'path': p_data.get('cwd') or f'/var/www/app_{p}',
                'process': p_data.get('process', 'node'),
                'pid': p_data.get('pid'),
                'cmdline': p_data.get('cmdline'),
                'confidence': p_data.get('confidence', 'Média')
            })

        summary.append({
            'port': p,
            'name': name_str,
            'type': type_str,
            'status': status_str,
            'bind_ip': p_data.get('bind_ip', '0.0.0.0'),
            'exposure': p_data.get('exposure', 'public'),
            'process': p_data.get('process', 'Desconhecido'),
            'pid': p_data.get('pid'),
            'cmdline': p_data.get('cmdline'),
            'cwd': p_data.get('cwd')
        })

    next_free = get_next_available_port(server_data)

    # VPS Audit Metrics
    audit = {
        'managed_count': len(apps),
        'unmanaged_count': len(unmanaged_discovered),
        'nginx_active': 80 in vps_listening_map or 443 in vps_listening_map,
        'postgres_active': 5432 in vps_listening_map,
        'mysql_active': 3306 in vps_listening_map,
        'conflict_warning': next_free in [s['port'] for s in summary if 'Ativa' in s['status']]
    }

    return {
        'ports': summary,
        'unmanaged': unmanaged_discovered,
        'audit': audit,
        'total_occupied': len(summary),
        'total_free_5000': 1000 - len([s for s in summary if 5000 <= s['port'] <= 5999]),
        'next_available': next_free
    }
