#!/usr/bin/env python3
import os
import shutil
import signal
import socket
import subprocess
import sys
import time
import urllib.request
from pathlib import Path

HOST = subprocess.check_output(['tailscale', 'ip', '-4'], text=True).splitlines()[0].strip()
ROOT = Path('/home/agent/projects/Jarvis')
TMP = ROOT / '.tmp/operator-handoff'
TMP.mkdir(parents=True, exist_ok=True)
STATIC = TMP / 'static_proxy_server.py'
PROCS = []

PORTS = {
    'jarvis_api': 8011,
    'jarvis_dashboard': 5175,
    'finance_api': 8012,
    'finance_dashboard': 5176,
    'autoshorts_api': 8013,
    'autoshorts_dashboard': 5177,
    'health_dashboard': 8014,
}

def port_free(host, port):
    s = socket.socket()
    try:
        s.bind((host, port)); return True
    except OSError:
        return False
    finally:
        s.close()

def wait_url(url, label, ok_status=(200,)):
    last = ''
    for _ in range(160):
        try:
            with urllib.request.urlopen(url, timeout=2) as r:
                if r.status in ok_status:
                    print(f'OK: {label}: {url}', flush=True); return True
                last = f'HTTP {r.status}'
        except Exception as e:
            last = repr(e)
        time.sleep(0.25)
    print(f'ERROR: {label} not reachable: {url} ({last})', flush=True)
    return False

def start(name, cwd, cmd, env=None):
    logfile = open(TMP / f'{name}.log', 'ab', buffering=0)
    full_env = os.environ.copy()
    if env: full_env.update(env)
    p = subprocess.Popen(cmd, cwd=str(cwd), env=full_env, stdout=logfile, stderr=subprocess.STDOUT, start_new_session=True)
    (TMP / f'{name}.pid').write_text(str(p.pid))
    PROCS.append((name, p, logfile))
    print(f'START: {name} pid={p.pid}', flush=True)
    return p

def copytree(src, dst):
    if dst.exists(): shutil.rmtree(dst)
    shutil.copytree(src, dst)

def patch_text_files(root, replacements):
    for path in root.rglob('*'):
        if path.is_file() and path.suffix in {'.js','.html','.css'}:
            data = path.read_text(errors='ignore')
            new = data
            for old, repl in replacements.items():
                new = new.replace(old, repl)
            if new != data: path.write_text(new)

def cleanup_stale():
    # stop only processes recorded by the handoff tmp pid files, never random ports
    for f in TMP.glob('*.pid'):
        try:
            pid = int(f.read_text().strip())
            os.kill(pid, signal.SIGTERM)
        except Exception:
            pass
        try: f.unlink()
        except Exception: pass

def main():
    cleanup_stale()
    for name, port in PORTS.items():
        if not port_free(HOST, port):
            raise SystemExit(f'Port busy on {HOST}: {name} {port}')

    jarvis_static = TMP / 'jarvis-dist'
    finance_static = TMP / 'finance-dist'
    autoshorts_static = TMP / 'autoshorts-dist'
    health_static = TMP / 'health-dist'
    copytree(ROOT / 'apps/dashboard/dist', jarvis_static)
    copytree(Path('/home/agent/.hermes/repos/FinanceManager/frontend/dist'), finance_static)
    copytree(Path('/home/agent/projects/AutoShorts_Dashboard/frontend/dist'), autoshorts_static)
    health_static.mkdir(exist_ok=True)
    health_src = Path('/home/agent/.hermes/assets/Gesundheit/reports/health_dashboard.html')
    if health_src.exists():
        shutil.copy2(health_src, health_static / 'index.html')
        shutil.copy2(health_src, health_static / 'health-dashboard')
    else:
        (health_static / 'index.html').write_text('<h1>Health Dashboard nicht gefunden</h1>')
    patch_text_files(jarvis_static, {'http://127.0.0.1:8080': f'http://{HOST}:{PORTS["jarvis_api"]}'})
    patch_text_files(finance_static, {'http://127.0.0.1:8000': f'http://{HOST}:{PORTS["finance_api"]}'})

    finance_repo = Path('/home/agent/.hermes/repos/FinanceManager')
    finance_venv = Path('/home/agent/jarvis_runtime/finance-system/venv/bin/python')
    finance_python = str(finance_venv if finance_venv.exists() else sys.executable)
    start('finance-api', finance_repo, [finance_python, '-m', 'uvicorn', 'jarvis_finance.api.main:app', '--host', HOST, '--port', str(PORTS['finance_api'])], {'PYTHONPATH': 'src'})
    if not wait_url(f'http://{HOST}:{PORTS["finance_api"]}/api/health', 'Finance API'): raise SystemExit(1)
    start('finance-dashboard', TMP, [sys.executable, str(STATIC), '--host', HOST, '--port', str(PORTS['finance_dashboard']), '--root', str(finance_static), '--api-base', f'http://{HOST}:{PORTS["finance_api"]}', '--single-page'])
    if not wait_url(f'http://{HOST}:{PORTS["finance_dashboard"]}/', 'Finance Dashboard'): raise SystemExit(1)

    autos_tmp = TMP / 'autoshorts-runtime'; (autos_tmp / 'storage').mkdir(parents=True, exist_ok=True)
    start('autoshorts-api', Path('/home/agent/projects/AutoShorts_Dashboard/backend'), ['uv', 'run', 'uvicorn', 'app.main:app', '--host', HOST, '--port', str(PORTS['autoshorts_api'])], {
        'AUTOSHORTS_DATABASE_URL': f'sqlite:///{autos_tmp / "autoshorts_dashboard.db"}',
        'AUTOSHORTS_STORAGE_ROOT': str(autos_tmp / 'storage'),
        'AUTOSHORTS_SECRET_KEY': 'operator-handoff-dev-only',
        'AUTOSHORTS_REDIS_URL': 'redis://127.0.0.1:0/0',
        'AUTOSHORTS_CORS_ORIGINS': f'["http://{HOST}:{PORTS["autoshorts_dashboard"]}"]',
    })
    if not wait_url(f'http://{HOST}:{PORTS["autoshorts_api"]}/api/health', 'AutoShorts API'): raise SystemExit(1)
    start('autoshorts-dashboard', TMP, [sys.executable, str(STATIC), '--host', HOST, '--port', str(PORTS['autoshorts_dashboard']), '--root', str(autoshorts_static), '--api-base', f'http://{HOST}:{PORTS["autoshorts_api"]}', '--single-page'])
    if not wait_url(f'http://{HOST}:{PORTS["autoshorts_dashboard"]}/', 'AutoShorts Dashboard'): raise SystemExit(1)

    start('health-dashboard', TMP, [sys.executable, str(STATIC), '--host', HOST, '--port', str(PORTS['health_dashboard']), '--root', str(health_static), '--single-page'])
    if not wait_url(f'http://{HOST}:{PORTS["health_dashboard"]}/health-dashboard', 'Health Dashboard'): raise SystemExit(1)

    start('jarvis-api', ROOT / 'apps/api-gateway', [sys.executable, '-m', 'uvicorn', 'jarvis_gateway.main:app', '--host', HOST, '--port', str(PORTS['jarvis_api'])], {
        'PYTHONPATH': '.', 'JARVIS_ENV': 'development', 'JARVIS_DEMO_MODE': '1', 'JARVIS_OPERATOR_DEMO_PROFILE': 'mixed_readonly_handoff', 'JARVIS_ACTIONS_ENABLED': '0',
        'JARVIS_PUBLIC_BASE_URL': f'http://{HOST}:{PORTS["jarvis_dashboard"]}', 'JARVIS_OPERATOR_PUBLIC_ORIGIN': f'http://{HOST}:{PORTS["jarvis_dashboard"]}', 'JARVIS_ALLOW_TAILNET_LINKS': '1',
        'JARVIS_DEV_CORS_ORIGINS': f'http://{HOST}:{PORTS["jarvis_dashboard"]}',
        'FINANCE_ADAPTER_MODE': 'mock', 'HEALTH_ADAPTER_MODE': 'mock', 'AUTOSHORTS_ADAPTER_MODE': 'mock',
        'FINANCE_LEGACY_DASHBOARD_URL': f'http://{HOST}:{PORTS["finance_dashboard"]}', 'HEALTH_LEGACY_DASHBOARD_URL': f'http://{HOST}:{PORTS["health_dashboard"]}/health-dashboard', 'AUTOSHORTS_LEGACY_DASHBOARD_URL': f'http://{HOST}:{PORTS["autoshorts_dashboard"]}',
        'ALLOW_EXACT_FINANCE_VALUES': '0', 'ALLOW_HEALTH_DETAIL_LINKS': '0',
    })
    if not wait_url(f'http://{HOST}:{PORTS["jarvis_api"]}/api/healthz', 'JARVIS API'): raise SystemExit(1)
    start('jarvis-dashboard', TMP, [sys.executable, str(STATIC), '--host', HOST, '--port', str(PORTS['jarvis_dashboard']), '--root', str(jarvis_static), '--api-base', f'http://{HOST}:{PORTS["jarvis_api"]}', '--single-page'])
    if not wait_url(f'http://{HOST}:{PORTS["jarvis_dashboard"]}/', 'JARVIS Dashboard'): raise SystemExit(1)

    env = TMP / 'handoff.env'
    env.write_text('\n'.join([
        f'HOST={HOST}',
        f'JARVIS_DASHBOARD_URL=http://{HOST}:{PORTS["jarvis_dashboard"]}',
        f'HEALTH_DASHBOARD_URL=http://{HOST}:{PORTS["health_dashboard"]}/health-dashboard',
        f'FINANCE_DASHBOARD_URL=http://{HOST}:{PORTS["finance_dashboard"]}',
        f'AUTOSHORTS_DASHBOARD_URL=http://{HOST}:{PORTS["autoshorts_dashboard"]}',
        f'FAMILYDASHBOARD_URL=http://{HOST}:5173',
        ''
    ]))
    print('READY', flush=True)
    print(env.read_text(), flush=True)
    while True:
        dead = [(n,p.returncode) for n,p,_ in PROCS if p.poll() is not None]
        if dead:
            print(f'ERROR: child exited {dead}', flush=True)
            break
        time.sleep(5)

try:
    main()
finally:
    for name, p, log in PROCS:
        if p.poll() is None:
            try: os.killpg(p.pid, signal.SIGTERM)
            except Exception: pass
        try: log.close()
        except Exception: pass
