import json
import subprocess
from pathlib import Path

import pytest

from tests.dashboard_bundle import dashboard


SESSION = Path(__file__).resolve().parents[1] / "frontend" / "update-triage-session.js"


def run_session(script):
    source = f"const createSession = require({json.dumps(str(SESSION))});\n" + script
    result = subprocess.run(["node", "-e", source], capture_output=True, text=True)
    assert result.returncode == 0, result.stderr
    return json.loads(result.stdout)


def test_update_triage_progress_exposes_the_current_actionability_reason():
    result = run_session("""
const values = new Map();
const items = [
  {notification_id:7, update_reason:'Review requested'},
  {notification_id:8, update_reason:''},
];
const progress = [];
const session = createSession({
  storage:{getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)},
  getLogin:()=> 'timmy', getItems:()=>items, onOpen:()=>{},
  onProgress:value=>progress.push(value), onFinish:()=>{},
});
session.start();
session.completeAndNext();
process.stdout.write(JSON.stringify(progress));
""")

    assert result == [
        {"index": 1, "total": 2, "reason": "Review requested"},
        {"index": 2, "total": 2},
    ]


def test_update_triage_persists_account_bound_pass_and_resumes_by_identity():
    result = run_session("""
const values = new Map();
let login = 'timmy';
let items = [1,2,3].map(id => ({notification_id:id,title:'Update '+id}));
const opened = [], progress = [];
const options = {
  storage:{getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)},
  getLogin:()=>login, getItems:()=>items,
  onOpen:item=>opened.push(item.notification_id), onProgress:value=>progress.push(value), onFinish:()=>opened.push('done'),
};
let session = createSession(options);
session.start();
session.keepUnreadAndNext();
const saved = JSON.parse(values.get('stackchain.update-triage.v1'));
session = createSession(options);
const resumable = session.resumable();
session.resume();
login = 'other';
const isolated = session.resumable();
process.stdout.write(JSON.stringify({opened, progress, saved, resumable, isolated}));
""")

    assert result["opened"] == [1, 2, 2]
    assert result["progress"] == [
        {"index": 1, "total": 3},
        {"index": 2, "total": 3},
        {"index": 2, "total": 3},
    ]
    assert result["saved"] == {
        "version": 2,
        "login": "timmy",
        "identities": ["1", "2", "3"],
        "current": "2",
        "completed": ["1"],
        "kept": ["1"],
    }
    assert result["resumable"] is True
    assert result["isolated"] is False


def test_update_triage_reconciles_removed_items_and_does_not_admit_new_arrivals():
    result = run_session("""
const values = new Map();
let items = [1,2,3].map(notification_id => ({notification_id}));
const opened = [], progress = [];
const session = createSession({
  storage:{getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)},
  getLogin:()=> 'timmy', getItems:()=>items,
  onOpen:item=>opened.push(item.notification_id), onProgress:value=>progress.push(value), onFinish:()=>opened.push('done'),
});
session.start();
items = [1,3,4].map(notification_id => ({notification_id}));
session.completeAndNext();
session.completeAndNext();
process.stdout.write(JSON.stringify({opened, progress, stored:values.get('stackchain.update-triage.v1') || null}));
""")

    assert result == {
        "opened": [1, 3, "done"],
        "progress": [{"index": 1, "total": 3}, {"index": 3, "total": 3}],
        "stored": None,
    }


def test_update_triage_exposes_only_the_next_surviving_snapshot_item():
    result = run_session("""
const values = new Map();
let items = [1,2,3].map(notification_id => ({notification_id}));
const session = createSession({
  storage:{getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)},
  getLogin:()=> 'timmy', getItems:()=>items,
  onOpen:()=>{}, onProgress:()=>{}, onFinish:()=>{},
});
session.start();
const first = session.next()?.notification_id;
items = [1,3,4].map(notification_id => ({notification_id}));
const afterRemoval = session.next()?.notification_id;
session.keepUnreadAndNext();
const afterAdvance = session.next();
process.stdout.write(JSON.stringify({first, afterRemoval, afterAdvance:afterAdvance || null}));
""")

    assert result == {"first": 2, "afterRemoval": 3, "afterAdvance": None}


def test_update_triage_reports_truthful_outcomes_and_reopens_only_surviving_kept_items():
    result = run_session("""
const values = new Map();
let items = [1,2,3].map(notification_id => ({notification_id}));
const opened = [], outcomes = [];
const session = createSession({
  storage:{getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)},
  getLogin:()=> 'timmy', getItems:()=>items,
  onOpen:item=>opened.push(item.notification_id), onProgress:()=>{}, onFinish:value=>outcomes.push(value),
});
session.start();
session.keepUnreadAndNext();
session.completeAndNext();
items = [1,3].map(notification_id => ({notification_id}));
session.keepUnreadAndNext();
const reviewed = session.reviewKept();
process.stdout.write(JSON.stringify({opened, outcomes, reviewed}));
""")

    assert result == {
        "opened": [1, 2, 3, 1],
        "outcomes": [{"reviewed": 3, "resolved": 1, "kept": 2, "keptIdentities": ["1", "3"]}],
        "reviewed": True,
    }


def test_update_triage_reconciles_kept_outcomes_before_reporting_cleared():
    result = run_session("""
const values = new Map();
let items = [1,2].map(notification_id => ({notification_id}));
const outcomes = [];
const session = createSession({
  storage:{getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)},
  getLogin:()=> 'timmy', getItems:()=>items,
  onOpen:()=>{}, onProgress:()=>{}, onFinish:value=>outcomes.push(value),
});
session.start();
session.keepUnreadAndNext();
items = [2].map(notification_id => ({notification_id}));
session.completeAndNext();
process.stdout.write(JSON.stringify(outcomes[0]));
""")

    assert result == {"reviewed": 2, "resolved": 2, "kept": 0, "keptIdentities": []}


@pytest.mark.anyio
async def test_dashboard_wires_resumable_updates_triage_mobile_flow():
    html = await dashboard()

    assert '<script src="static/update-triage-session.js"></script>' in html
    assert 'id="update-triage-progress"' in html
    assert 'id="keep-update-unread" type="button" aria-label="Keep unread and open next update">Keep unread</button>' in html
    assert "openUpdates: openUpdateTriage" in html
    assert "updateTriage.acceptCompleted()" in html
    assert "triage:updateTriage" in html
    assert "updateTriage.reconcile()" in html
    assert 'id="mobile-update-outcome"' in html
    assert 'id="review-kept-updates"' in html
    assert "updateTriage.reviewKept(keptUpdateIdentities)" in html
    assert "showUpdateTriageOutcome(outcome)" in html
    assert ".update-triage-progress" in html
