import json
import subprocess
from pathlib import Path


SOURCE = Path(__file__).parents[1] / "frontend" / "today-lock-screen.js"
ROOT = SOURCE.parents[1]


def run_scenario(scenario: str) -> dict:
    script = SOURCE.read_text() + r"""
const values = new Map();
const messages = [];
const listeners = {};
const control = {checked:false, disabled:false, addEventListener:(name, fn)=>listeners[name]=fn};
const status = {textContent:''};
const serviceWorker = {
  controller:{postMessage:message=>messages.push(message)},
  ready:Promise.resolve({active:{postMessage:message=>messages.push(message)}}),
  addEventListener:(name, fn)=>listeners['sw-' + name]=fn,
};
const storage = {getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)};
let permission = 'default';
const NotificationRef = {get permission(){return permission;},requestPermission:async()=>{permission='granted';return permission;}};
const actions = [];
const locationRef = {href:'https://forge.example/dashboard/#/my-work/today'};
const historyRef = {replaceState:(_a,_b,url)=>{locationRef.href=new URL(url, locationRef.href).href;}};
let tokenCounter = 0;
const tokens = ['opaque-token-1234567890', 'new-opaque-token-0987654321'];
(async()=>{
  const lockScreen = createTodayLockScreen({storage,getLogin:()=> 'Timmy',serviceWorker,NotificationRef,control,status,locationRef,historyRef,randomToken:()=> tokens[tokenCounter++],fingerprint:async (_token, identity)=>identity.includes('break:1000000') ? 'opaque-break-fingerprint' : identity.startsWith('issue:') ? 'opaque-issue-fingerprint' : 'opaque-pull-fingerprint',onAction:(action, identity)=>actions.push([action, identity])});
  %SCENARIO%
})().catch(error=>{console.error(error);process.exit(1)});
""".replace("%SCENARIO%", scenario)
    completed = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
    return json.loads(completed.stdout)


def test_operator_opt_in_syncs_one_privacy_safe_session_notification():
    result = run_scenario(r"""
  await lockScreen.enable();
  await lockScreen.sync({identity:'issue:secret/repo:42:',running:true,elapsed_ms:125000}, true);
  process.stdout.write(JSON.stringify({checked:control.checked,status:status.textContent,messages,stored:[...values.entries()]}));
""")

    assert result["checked"] is True
    assert result["status"] == "Lock-screen Today controls are on."
    assert result["stored"] == [
        ["stackchain.today-lock-screen.v1.timmy", "1"],
        ["stackchain.today-lock-screen-action.v1.timmy", '{"token":"opaque-token-1234567890","fingerprint":"opaque-issue-fingerprint"}'],
    ]
    assert result["messages"][-1] == {
        "type": "stackchain-today-lock-screen",
        "active": True,
        "running": True,
        "actionToken": "opaque-token-1234567890",
    }
    assert "secret" not in json.dumps(result)


def test_disabling_or_ending_session_removes_notification():
    result = run_scenario(r"""
  await lockScreen.enable();
  await lockScreen.sync({identity:'issue:r:42:',running:false,elapsed_ms:0}, false);
  await lockScreen.disable();
  process.stdout.write(JSON.stringify({checked:control.checked,messages,stored:[...values.entries()]}));
""")

    assert result["checked"] is False
    assert result["stored"] == []
    assert result["messages"][-2:] == [
        {"type": "stackchain-today-lock-screen", "active": False, "running": False},
        {"type": "stackchain-today-lock-screen", "active": False, "running": False},
    ]


def test_cold_launch_requests_one_private_action_claim_without_url_capabilities():
    result = run_scenario(r"""
  values.set('stackchain.today-lock-screen.v1.timmy','1');
  const consumed = await lockScreen.consumeLaunchAction();
  listeners['sw-message']({data:{type:'stackchain-today-timer-action',action:'resume'}});
  process.stdout.write(JSON.stringify({consumed,actions,href:locationRef.href,messages}));
""")

    assert result == {
        "consumed": True,
        "actions": [["resume", None]],
        "href": "https://forge.example/dashboard/#/my-work/today",
        "messages": [{"type": "stackchain-claim-today-action"}],
    }


def test_finish_action_is_bound_to_the_exact_active_identity_and_consumed_once():
    result = run_scenario(r"""
  await lockScreen.enable();
  await lockScreen.sync({identity:'issue:private/repo:42:',running:true}, true);
  const first = await lockScreen.consumeAction('complete', 'opaque-token-1234567890');
  const replay = await lockScreen.consumeAction('complete', 'opaque-token-1234567890');
  await lockScreen.sync({identity:'pull:private/repo:9:',running:true}, true);
  const stale = await lockScreen.consumeAction('complete', 'opaque-token-1234567890');
  process.stdout.write(JSON.stringify({first,replay,stale,actions,messages,stored:[...values.entries()]}));
""")

    assert result["first"] is True
    assert result["replay"] is False
    assert result["stale"] is False
    assert result["actions"] == [["complete", "issue:private/repo:42:"]]
    assert all("private/repo" not in json.dumps(message) for message in result["messages"])


def test_break_sync_carries_deadline_and_resume_is_bound_to_that_exact_break():
    result = run_scenario(r"""
  await lockScreen.enable();
  await lockScreen.sync({identity:'issue:private/repo:42:',running:false,break_deadline_at:1000000}, true);
  const resumed = await lockScreen.consumeAction('resume', 'opaque-token-1234567890');
  await lockScreen.sync({identity:'issue:private/repo:42:',running:false,break_deadline_at:2000000}, true);
  const stale = await lockScreen.consumeAction('resume', 'opaque-token-1234567890');
  process.stdout.write(JSON.stringify({resumed,stale,actions,messages,stored:[...values.entries()]}));
""")

    assert result["resumed"] is True
    assert result["stale"] is False
    assert result["actions"] == [["resume", "issue:private/repo:42:"]]
    assert result["messages"][0] == {
        "type": "stackchain-today-lock-screen",
        "active": True,
        "running": False,
        "breakDeadlineAt": 1_000_000,
        "actionToken": "opaque-token-1234567890",
    }
    assert "private/repo" not in json.dumps(result["messages"])


def test_private_cold_launch_finish_action_is_bound_to_the_active_identity():
    result = run_scenario(r"""
  await lockScreen.enable();
  await lockScreen.sync({identity:'issue:private/repo:42:',running:true}, true);
  await lockScreen.consumeLaunchAction();
  await listeners['sw-message']({data:{type:'stackchain-today-timer-action',action:'complete',actionToken:'opaque-token-1234567890'}});
  process.stdout.write(JSON.stringify({actions,href:locationRef.href,messages}));
""")

    assert result["actions"] == [["complete", "issue:private/repo:42:"]]
    assert result["href"] == "https://forge.example/dashboard/#/my-work/today"
    assert result["messages"][-1] == {"type": "stackchain-claim-today-action"}


def test_permission_denial_is_truthful_and_does_not_persist_opt_in():
    result = run_scenario(r"""
  NotificationRef.requestPermission=async()=>{permission='denied';return permission;};
  const enabled = await lockScreen.enable();
  process.stdout.write(JSON.stringify({enabled,checked:control.checked,status:status.textContent,stored:[...values.entries()]}));
""")

    assert result == {
        "enabled": False,
        "checked": False,
        "status": "Lock-screen controls are blocked in browser settings.",
        "stored": [],
    }


def test_lock_screen_flow_is_wired_into_the_packaged_today_journey():
    index = (ROOT / "frontend" / "index.html").read_text()
    dashboard = (ROOT / "frontend" / "dashboard.js").read_text()
    bundle = (ROOT / "src" / "frontend_bundle.py").read_text()

    assert 'id="today-lock-screen"' in index
    assert 'id="today-lock-screen-status"' in index
    assert '<script src="static/today-lock-screen.js"></script>' in index
    assert index.index('static/today-lock-screen.js') < index.index('static/dashboard.js')
    assert 'createTodayLockScreen({' in dashboard
    assert "action === 'complete'" in dashboard
    assert "completeTodayItem(item)" in dashboard
    assert "todayLockScreen.consumeLaunchAction()" in dashboard
    assert dashboard.index("const completeTodayItem = createTodayCompletion({") < dashboard.index("todayLockScreen.consumeLaunchAction()")
    assert "todayLockScreen.sync(snapshot, workSession.checkpointed())" in dashboard
    assert "todayLockScreen.consumeLaunchAction()" in dashboard
    assert '"static/today-lock-screen.js"' in bundle
