# modules/sites.py — Managed application sites registry + live status
import re
import time
from urllib.request import urlopen, Request
from urllib.error import URLError

from config import MAIN_SERVER_IP, RUNNING_ON_MAIN_SERVER
from modules.backups import _ssh_main, get_all_root_containers, get_container_status


# ────────────────────────────────────────────────────────────────
# STATIC SITE REGISTRY (source of truth for Application Sites UI)
# ────────────────────────────────────────────────────────────────

PLATFORM = {
    'id': 'cloudops',
    'name': 'CloudOps Platform',
    'tagline': 'Navitrends ops dashboard',
    'domain': 'cloudops.nav.ovh',
    'domain_protocol': 'https',
    'ssl_configured': True,
    'port': 8088,
    'internal_port': 5000,
    'container': 'management-platform',
    'health_path': '/',
    'brand_color': '#3b82f6',
    'icon': 'fa-gauge-high',
}

SITES = [
    {
        'id': 'erpnext',
        'name': 'ERPNext',
        'tagline': 'Enterprise resource planning',
        'category': 'ERP',
        'icon': 'erpnext',
        'brand_color': '#0089FF',
        'compose_dir': 'frappe-setup',
        'main_container': 'frappe-erpnext',
        'containers': [
            {'name': 'frappe-erpnext', 'role': 'App', 'image': 'frappe/erpnext:latest'},
            {'name': 'frappe-mariadb', 'role': 'Database', 'image': 'mariadb:10.6'},
            {'name': 'frappe-redis', 'role': 'Cache / Queue', 'image': 'redis:alpine'},
        ],
        'port': 8080,
        'internal_port': 8000,
        'domain': 'erpnext.navitrends.ovh',
        'domain_protocol': 'http',
        'ssl_configured': False,
        'health_path': '/',
        'volumes': ['frappe-setup_frappe-sites', 'frappe-setup_mariadb-data'],
    },
    {
        'id': 'odoo',
        'name': 'Odoo',
        'tagline': 'Business management suite',
        'category': 'ERP',
        'icon': 'odoo',
        'brand_color': '#714B67',
        'compose_dir': 'odoo-clean',
        'main_container': 'odoo-clean-odoo-1',
        'containers': [
            {'name': 'odoo-clean-odoo-1', 'role': 'App', 'image': 'odoo:18'},
            {'name': 'odoo-clean-db-1', 'role': 'Database', 'image': 'postgres:15'},
        ],
        'port': 8069,
        'internal_port': 8069,
        'domain': 'odooo.nav.ovh',
        'domain_protocol': 'https',
        'ssl_configured': True,
        'health_path': '/web',
        'volumes': ['odoo-clean_db-data', 'odoo-clean_odoo-etc'],
    },
    {
        'id': 'nextcloud',
        'name': 'Nextcloud',
        'tagline': 'File sync & collaboration',
        'category': 'Storage',
        'icon': 'nextcloud',
        'brand_color': '#0082C9',
        'compose_dir': 'nextcloud-setup',
        'main_container': 'nextcloud-app',
        'containers': [
            {'name': 'nextcloud-app', 'role': 'App', 'image': 'nextcloud:latest'},
            {'name': 'nextcloud-postgres', 'role': 'Database', 'image': 'postgres:15'},
        ],
        'port': 8082,
        'internal_port': 80,
        'domain': 'next.cloud.nav.ovh',
        'domain_protocol': 'https',
        'ssl_configured': True,
        'health_path': '/status.php',
        'volumes': ['nextcloud-setup_nextcloud-data', 'nextcloud-setup_nextcloud-db-data'],
    },
    {
        'id': 'mautic',
        'name': 'Mautic',
        'tagline': 'Marketing automation',
        'category': 'Marketing',
        'icon': 'mautic',
        'brand_color': '#4E5E9E',
        'compose_dir': 'mautic-setup',
        'main_container': 'mautic-app',
        'containers': [
            {'name': 'mautic-app', 'role': 'App', 'image': 'mautic/mautic:latest'},
            {'name': 'mautic-mariadb', 'role': 'Database', 'image': 'mariadb:10.11'},
        ],
        'port': 8081,
        'internal_port': 80,
        'domain': None,
        'domain_protocol': 'http',
        'ssl_configured': False,
        'health_path': '/',
        'volumes': ['mautic-setup_mautic-data', 'mautic-setup_mautic-db-data'],
        'networks': ['mautic-network'],
    },
    {
        'id': 'n8n',
        'name': 'n8n',
        'tagline': 'Workflow automation',
        'category': 'Automation',
        'icon': 'n8n',
        'brand_color': '#EA4B71',
        'compose_dir': 'n8n-setup',
        'main_container': 'n8n-app',
        'containers': [
            {'name': 'n8n-app', 'role': 'App', 'image': 'n8nio/n8n:latest'},
            {'name': 'n8n-postgres', 'role': 'Database', 'image': 'postgres:15'},
        ],
        'port': 5678,
        'internal_port': 5678,
        'domain': None,
        'domain_protocol': 'http',
        'ssl_configured': False,
        'health_path': '/healthz',
        'volumes': ['n8n-setup_n8n-data', 'n8n-setup_n8n-db-data'],
        'networks': ['n8n-network', 'integration-network'],
    },
]

SITE_BY_ID = {s['id']: s for s in SITES}


def _build_urls(site):
    ip = MAIN_SERVER_IP
    port = site['port']
    proto = site.get('domain_protocol', 'http')
    path = site.get('health_path', '/') or '/'
    ssl_configured = site.get('ssl_configured', proto == 'https')

    domain = site.get('domain')
    has_domain = bool(domain)

    if has_domain:
        if ssl_configured:
            access_url = f"{proto}://{domain}"
            health_url = f"{access_url}{path}"
        else:
            access_url = f"http://{domain}:{port}"
            health_url = f"{access_url}{path}"
    else:
        access_url = f"http://{ip}:{port}"
        health_url = f"{access_url}{path}"

    return {
        'has_domain': has_domain,
        'domain': domain,
        'domain_protocol': proto,
        'ssl_configured': ssl_configured,
        'ip_url': f"http://{ip}:{port}",
        'access_url': access_url.rstrip('/'),
        'health_url': health_url,
    }


def _parse_port_mapping(ports_str, host_port, internal_port=None):
    """Extract host:container mapping from docker ports string."""
    if not ports_str:
        if internal_port and internal_port != host_port:
            return f"{host_port}→{internal_port}"
        return str(host_port) if host_port else None
    m = re.search(rf'0\.0\.0\.0:{host_port}->(\d+)/', ports_str)
    if m:
        return f"{host_port}→{m.group(1)}"
    m = re.search(r'0\.0\.0\.0:(\d+)->(\d+)/', ports_str)
    if m:
        return f"{m.group(1)}→{m.group(2)}"
    return ports_str[:60] if ports_str else None


def _probe_http(url, timeout=5):
    """HTTP health probe — local urllib or remote curl via SSH."""
    if RUNNING_ON_MAIN_SERVER:
        try:
            req = Request(url, method='GET', headers={'User-Agent': 'CloudOps-HealthCheck/1.0'})
            with urlopen(req, timeout=timeout) as resp:
                code = resp.getcode()
                return {
                    'reachable': 200 <= code < 400,
                    'http_code': code,
                    'latency_ms': None,
                    'error': None,
                }
        except URLError as e:
            return {'reachable': False, 'http_code': None, 'latency_ms': None, 'error': str(e.reason)[:120]}
        except Exception as e:
            return {'reachable': False, 'http_code': None, 'latency_ms': None, 'error': str(e)[:120]}
    else:
        safe_url = url.replace("'", "'\\''")
        t0 = time.time()
        out, _ = _ssh_main(
            f"curl -sf -o /dev/null -w '%{{http_code}}' --connect-timeout {timeout} "
            f"-m {timeout} '{safe_url}' 2>/dev/null || echo '000'",
            timeout=timeout + 5,
        )
        latency = int((time.time() - t0) * 1000)
        code_str = (out or '000').strip()
        try:
            code = int(code_str)
        except ValueError:
            code = 0
        return {
            'reachable': 200 <= code < 400,
            'http_code': code if code else None,
            'latency_ms': latency,
            'error': None if 200 <= code < 400 else f'HTTP {code_str}',
        }


def _container_map():
    ctrs = get_all_root_containers()
    return {c['name']: c for c in ctrs}


def get_sites_list(include_health=False):
    """Return all sites with live container + URL metadata."""
    ctr_map = _container_map()
    results = []

    for site in SITES:
        urls = _build_urls(site)
        main_name = site['main_container']
        main_ctr = ctr_map.get(main_name, {})
        main_status = get_container_status(main_name) if main_name else {'status': 'unknown'}

        containers_live = []
        running_count = 0
        for ctr_def in site.get('containers', []):
            name = ctr_def['name']
            live = ctr_map.get(name, {})
            status_raw = live.get('status', '')
            is_up = 'Up' in status_raw
            if is_up:
                running_count += 1
            containers_live.append({
                **ctr_def,
                'status': 'running' if is_up else ('stopped' if live else 'not_found'),
                'status_raw': status_raw or 'not found',
                'image_live': live.get('image', ctr_def.get('image', '—')),
                'ports': live.get('ports', '—'),
            })

        entry = {
            'id': site['id'],
            'name': site['name'],
            'tagline': site['tagline'],
            'category': site['category'],
            'icon': site['icon'],
            'brand_color': site['brand_color'],
            'compose_dir': site['compose_dir'],
            'main_container': main_name,
            'port': site['port'],
            'internal_port': site.get('internal_port', site['port']),
            'port_mapping': _parse_port_mapping(
                main_ctr.get('ports', ''), site['port'], site.get('internal_port')
            ),
            'ports_raw': main_ctr.get('ports', '—'),
            'volumes': site.get('volumes', []),
            'networks': site.get('networks', ['default']),
            'container_status': main_status['status'],
            'container_running': main_status['status'] == 'running',
            'containers': containers_live,
            'containers_running': running_count,
            'containers_total': len(site.get('containers', [])),
            **urls,
        }

        if include_health:
            if entry['container_running']:
                probe = _probe_http(urls['health_url'])
                entry['health'] = {
                    'status': 'up' if probe['reachable'] else 'down',
                    **probe,
                }
            else:
                entry['health'] = {
                    'status': 'offline',
                    'reachable': False,
                    'http_code': None,
                    'latency_ms': None,
                    'error': 'Container not running',
                }

        results.append(entry)

    return results


def get_site_health(site_id):
    site = SITE_BY_ID.get(site_id)
    if not site:
        return None
    urls = _build_urls(site)
    status = get_container_status(site['main_container'])
    if status['status'] != 'running':
        return {
            'site_id': site_id,
            'status': 'offline',
            'reachable': False,
            'http_code': None,
            'latency_ms': None,
            'error': 'Container not running',
            'checked_at': time.time(),
        }
    t0 = time.time()
    probe = _probe_http(urls['health_url'])
    probe['latency_ms'] = probe.get('latency_ms') or int((time.time() - t0) * 1000)
    probe['site_id'] = site_id
    probe['status'] = 'up' if probe['reachable'] else 'down'
    probe['checked_at'] = time.time()
    return probe
