import json
import subprocess
from pathlib import Path


ROOT = Path(__file__).parents[1]
APP_BADGE = ROOT / "frontend" / "mobile-app-badge.js"
INDEX = ROOT / "frontend" / "index.html"
DASHBOARD = ROOT / "frontend" / "dashboard.js"
CSS = ROOT / "frontend" / "dashboard.css"
DEVICE_SETUP = ROOT / "frontend" / "mobile-device-setup.js"


def run_badge(scenario: str) -> dict:
    assert APP_BADGE.exists(), "mobile app badge controller is not implemented"
    script = f"""
const createMobileAppBadge = require({json.dumps(str(APP_BADGE))});
(async () => {{ {scenario} }})().catch(error => {{ console.error(error); process.exit(1); }});
"""
    completed = subprocess.run(
        ["node", "-e", script], check=True, capture_output=True, text=True
    )
    return json.loads(completed.stdout)


def test_app_badge_opt_in_reconciles_only_authoritative_unread_totals_without_churn():
    result = run_badge("""
const listeners = {};
const control = {checked:false, disabled:false, addEventListener(name, callback) { listeners[name] = callback; }};
const status = {textContent:''};
const container = {hidden:false};
const values = [];
let clears = 0;
const storage = new Map();
const controller = createMobileAppBadge({
  control, status, container,
  navigator: {
    async setAppBadge(value) { values.push(value); },
    async clearAppBadge() { clears++; },
  },
  storage: {
    getItem(key) { return storage.has(key) ? storage.get(key) : null; },
    setItem(key, value) { storage.set(key, value); },
    removeItem(key) { storage.delete(key); },
  },
});
controller.start();
await controller.reconcile(7, {authoritative:true});
await controller.reconcile(12, {authoritative:false});
control.checked = true;
await listeners.change();
await controller.reconcile(7, {authoritative:true});
await controller.reconcile(0, {authoritative:true});
console.log(JSON.stringify({values, clears, saved:storage.get('stackchain.app-badge.enabled.v1'), status:status.textContent}));
""")
    assert result == {
        "values": [7],
        "clears": 1,
        "saved": "true",
        "status": "App icon badge is on and up to date.",
    }


def test_app_badge_hides_unsupported_device_control_without_touching_storage():
    result = run_badge("""
const control = {checked:false, disabled:false, addEventListener() { throw new Error('must not wire'); }};
const status = {textContent:''};
const container = {hidden:false};
let reads = 0;
const controller = createMobileAppBadge({
  control, status, container, navigator:{},
  storage:{getItem() { reads++; return 'true'; }},
});
const started = controller.start();
console.log(JSON.stringify({started, hidden:container.hidden, disabled:control.disabled, reads, status:status.textContent}));
""")
    assert result == {
        "started": False,
        "hidden": True,
        "disabled": True,
        "reads": 0,
        "status": "App icon badges are unavailable in this browser.",
    }


def test_app_badge_is_packaged_touch_safe_and_reconciled_from_server_total():
    index = INDEX.read_text()
    dashboard = DASHBOARD.read_text()
    css = CSS.read_text()

    assert '<script src="static/mobile-app-badge.js"></script>' in index
    assert 'id="app-badge-control"' in index
    assert 'id="app-badge-status" role="status"' in index
    assert ".app-badge-control { min-height:44px;" in css
    assert "const mobileAppBadge = createMobileAppBadge({" in dashboard
    assert "mobileAppBadge.start();" in dashboard
    assert "mobileAppBadge.reconcile(pagination.total, {authoritative:true});" in dashboard


def test_app_badge_exposes_device_setup_readiness_and_enable_action():
    result = run_badge("""
const control = {checked:false, disabled:false, addEventListener() {}};
const status = {textContent:''};
const saved = new Map();
const values = [];
const controller = createMobileAppBadge({
  control, status, container:{hidden:false},
  navigator:{async setAppBadge(value) { values.push(value); }, async clearAppBadge() {}},
  storage:{
    getItem(key) { return saved.get(key) || null; },
    setItem(key, value) { saved.set(key, value); },
    removeItem(key) { saved.delete(key); },
  },
});
controller.start();
await controller.reconcile(4, {authoritative:true});
const before = controller.readiness();
await controller.enable();
const after = controller.readiness();
console.log(JSON.stringify({before, after, checked:control.checked, values}));
""")
    assert result == {
        "before": {
            "state": "incomplete",
            "detail": "Show the confirmed unread Updates count without opening Stackchain.",
        },
        "after": {
            "state": "complete",
            "detail": "The app icon shows the confirmed unread Updates count.",
        },
        "checked": True,
        "values": [4],
    }


def test_device_setup_offers_app_badge_as_an_optional_supported_step():
    index = INDEX.read_text()
    setup = DEVICE_SETUP.read_text()
    dashboard = DASHBOARD.read_text()

    assert 'id="device-setup-app-badge-status" role="status"' in index
    assert 'id="device-setup-app-badge" type="button"' in index
    assert "['appBadge', options.appBadgeButton, options.appBadgeStatus, options.enableAppBadge, 'Enable']" in setup
    assert "appBadge:options.appBadgeReadiness()," in setup
    assert "appBadgeReadiness:() => mobileAppBadge.readiness()," in dashboard
    assert "enableAppBadge:() => mobileAppBadge.enable()," in dashboard


def test_app_badge_api_failure_is_reported_without_rejecting_live_refresh():
    result = run_badge("""
const control = {checked:true, disabled:false, addEventListener() {}};
const status = {textContent:''};
const controller = createMobileAppBadge({
  control, status, container:{hidden:false},
  navigator:{async setAppBadge() { throw new Error('platform failure'); }, async clearAppBadge() {}},
  storage:{getItem() { return 'true'; }, setItem() {}, removeItem() {}},
});
controller.start();
const reconciled = await controller.reconcile(3, {authoritative:true});
console.log(JSON.stringify({reconciled, status:status.textContent}));
""")
    assert result == {
        "reconciled": False,
        "status": "Could not update the app icon badge. Stackchain still has the confirmed count.",
    }


def test_disabling_badge_keeps_preference_off_when_platform_clear_fails():
    result = run_badge("""
const listeners = {};
const control = {checked:true, disabled:false, addEventListener(name, callback) { listeners[name] = callback; }};
const status = {textContent:''};
const saved = new Map([['stackchain.app-badge.enabled.v1', 'true']]);
const controller = createMobileAppBadge({
  control, status, container:{hidden:false},
  navigator:{async setAppBadge() {}, async clearAppBadge() { throw new Error('platform failure'); }},
  storage:{getItem(key) { return saved.get(key) || null; }, setItem(key, value) { saved.set(key, value); }, removeItem(key) { saved.delete(key); }},
});
controller.start();
control.checked = false;
const changed = await listeners.change();
console.log(JSON.stringify({changed, saved:saved.has('stackchain.app-badge.enabled.v1'), status:status.textContent}));
""")
    assert result == {
        "changed": False,
        "saved": False,
        "status": "App icon badge is off, but the browser could not clear the old count.",
    }


def test_app_badge_synchronizes_opt_in_with_the_active_service_worker():
    result = run_badge("""
const listeners = {};
const messages = [];
const control = {checked:false, disabled:false, addEventListener(name, callback) { listeners[name] = callback; }};
const controller = createMobileAppBadge({
  control, status:{textContent:''}, container:{hidden:false},
  navigator:{async setAppBadge() {}, async clearAppBadge() {}},
  storage:{getItem() { return null; }, setItem() {}, removeItem() {}},
  serviceWorker:{ready:Promise.resolve({active:{postMessage(message) { messages.push(message); }}})},
});
controller.start();
control.checked = true;
await listeners.change();
control.checked = false;
await listeners.change();
console.log(JSON.stringify(messages));
""")
    assert result == [
        {"type": "stackchain-app-badge-preference", "enabled": True},
        {"type": "stackchain-app-badge-preference", "enabled": False},
    ]
