import json
import subprocess
from pathlib import Path
import pytest

from tests.dashboard_bundle import dashboard


START_DAY = Path(__file__).resolve().parents[1] / "frontend" / "mobile-start-day.js"


def run_node(script: str) -> dict:
    result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
    assert result.returncode == 0, result.stderr
    return json.loads(result.stdout)


def test_start_day_briefing_guides_urgent_review_before_today():
    script = f"""
const createStartDay = require({json.dumps(str(START_DAY))});
let counts = {{agenda:2, attention:3, update:4, filed:1, today:5}};
const opened = [];
const controller = createStartDay({{
  getCounts: () => counts,
  openQueue: name => opened.push(name),
}});
const first = controller.briefing();
controller.startNext();
counts = {{agenda:0, attention:3, update:4, filed:1, today:5}};
const second = controller.briefing();
controller.startNext();
counts = {{agenda:0, attention:0, update:0, filed:0, today:5}};
const ready = controller.briefing();
controller.startNext();
process.stdout.write(JSON.stringify({{first, second, ready, opened}}));
"""

    result = run_node(script)

    assert result == {
        "first": {
            "total": 10,
            "next": "agenda",
            "label": "Review Agenda",
            "summary": "10 items before Today · 5 planned",
            "phases": [
                {"name": "agenda", "label": "Agenda", "count": 2},
                {"name": "attention", "label": "Attention", "count": 3},
                {"name": "update", "label": "Updates", "count": 4},
                {"name": "filed", "label": "Filed", "count": 1},
            ],
        },
        "second": {
            "total": 8,
            "next": "attention",
            "label": "Review Attention",
            "summary": "8 items before Today · 5 planned",
            "phases": [
                {"name": "attention", "label": "Attention", "count": 3},
                {"name": "update", "label": "Updates", "count": 4},
                {"name": "filed", "label": "Filed", "count": 1},
            ],
        },
        "ready": {
            "total": 0,
            "next": "today",
            "label": "Continue Today",
            "summary": "Review clear · 5 planned",
            "phases": [],
        },
        "opened": ["agenda", "attention", "today"],
    }


def test_prepare_today_puts_actionable_delivery_recovery_before_other_work():
    script = f"""
const createStartDay = require({json.dumps(str(START_DAY))});
let counts = {{delivery:2, agenda:1, attention:0, update:0, filed:0, today:4}};
const opened = [];
const handoffs = [];
const saved = new Map();
const controller = createStartDay({{
  getCounts: () => counts,
  getLogin: () => 'timmy',
  getDay: () => '2026-08-17',
  storage: {{getItem:key=>saved.get(key)||null,setItem:(key,value)=>saved.set(key,value),removeItem:key=>saved.delete(key)}},
  openQueue: name => opened.push(name),
  onHandoff: state => handoffs.push(state.next),
}});
const blocked = controller.briefing();
controller.startNext();
counts = {{delivery:0, agenda:1, attention:0, update:0, filed:0, today:4}};
const handed = controller.reconcile({{authoritative:true}});
process.stdout.write(JSON.stringify({{blocked, handed, opened, handoffs}}));
"""

    output = run_node(script)
    assert output["blocked"] == {
        "total": 3,
        "next": "delivery",
        "label": "Review Delivery",
        "summary": "2 deliveries need action before Today · 1 other item · 4 planned",
        "phases": [
            {"name": "delivery", "label": "Delivery recovery", "count": 2},
            {"name": "agenda", "label": "Agenda", "count": 1},
        ],
    }
    assert output["handed"] is True
    assert output["opened"] == ["delivery"]
    assert output["handoffs"] == ["agenda"]


def test_prepare_today_counts_each_work_identity_in_only_its_highest_priority_phase():
    script = f"""
const createStartDay = require({json.dumps(str(START_DAY))});
const agendaUpdate = {{kind:'issue', key:'lab#1'}};
const attentionUpdate = {{kind:'pull', key:'lab#2'}};
const filed = {{kind:'issue', key:'lab#3'}};
const delivery = {{outbox_id:'draft-4'}};
const controller = createStartDay({{
  getCounts: () => ({{today:2}}),
  getPhaseItems: () => ({{
    delivery:[delivery],
    agenda:[agendaUpdate],
    attention:[agendaUpdate, attentionUpdate],
    update:[agendaUpdate, attentionUpdate],
    filed:[filed],
  }}),
  openQueue: () => {{}},
}});
process.stdout.write(JSON.stringify(controller.briefing()));
"""

    output = run_node(script)
    assert output == {
        "total": 4,
        "next": "delivery",
        "label": "Review Delivery",
        "summary": "1 delivery needs action before Today · 3 other items · 2 planned",
        "phases": [
            {"name": "delivery", "label": "Delivery recovery", "count": 1},
            {"name": "agenda", "label": "Agenda", "count": 1},
            {"name": "attention", "label": "Attention", "count": 1},
            {"name": "filed", "label": "Filed", "count": 1},
        ],
    }


def test_start_day_view_renders_refreshed_phases_and_launches_primary_action():
    script = f"""
const createStartDay = require({json.dumps(str(START_DAY))});
const elements = {{
  summary: {{textContent:''}},
  phases: {{textContent:''}},
  action: {{textContent:'', listeners:{{}}, addEventListener(name, fn) {{ this.listeners[name] = fn; }}}},
}};
const opened = [];
let counts = {{agenda:0, attention:2, update:1, filed:0, today:3}};
const controller = createStartDay({{
  getCounts: () => counts,
  openQueue: name => opened.push(name),
  elements,
}});
controller.start();
const first = {{summary:elements.summary.textContent, phases:elements.phases.textContent, action:elements.action.textContent}};
elements.action.listeners.click();
counts = {{agenda:0, attention:0, update:0, filed:0, today:3}};
controller.render();
const ready = {{summary:elements.summary.textContent, phases:elements.phases.textContent, action:elements.action.textContent}};
process.stdout.write(JSON.stringify({{first, ready, opened}}));
"""

    assert run_node(script) == {
        "first": {
            "summary": "3 items before Today · 3 planned",
            "phases": "Attention 2 · Updates 1",
            "action": "Review Attention",
        },
        "ready": {
            "summary": "Review clear · 3 planned",
            "phases": "All urgent queues reviewed",
            "action": "Continue Today",
        },
        "opened": ["attention"],
    }


def test_prepare_today_pass_persists_per_account_and_hands_off_using_live_counts():
    script = f"""
const createStartDay = require({json.dumps(str(START_DAY))});
const saved = new Map();
const storage = {{
  getItem: key => saved.has(key) ? saved.get(key) : null,
  setItem: (key, value) => saved.set(key, value),
  removeItem: key => saved.delete(key),
}};
let login = 'timmy';
let day = '2026-08-15';
let counts = {{agenda:2, attention:1, update:1, filed:1, today:3}};
const opened = [];
const handoffs = [];
const controller = createStartDay({{
  storage,
  getLogin: () => login,
  getDay: () => day,
  getCounts: () => counts,
  openQueue: name => opened.push(name),
  onHandoff: state => handoffs.push({{next:state.next, label:state.label}}),
}});
const started = controller.startNext();
counts = {{agenda:0, attention:0, update:1, filed:1, today:3}};
const handed = controller.completePhase('agenda');
const resumed = createStartDay({{
  storage,
  getLogin: () => login,
  getDay: () => day,
  getCounts: () => counts,
  openQueue: name => opened.push(name),
}}).state();
login = 'alexander';
const isolated = createStartDay({{
  storage,
  getLogin: () => login,
  getDay: () => day,
  getCounts: () => counts,
  openQueue: name => opened.push(name),
}}).state();
login = 'timmy';
day = '2026-08-16';
const expired = createStartDay({{
  storage,
  getLogin: () => login,
  getDay: () => day,
  getCounts: () => counts,
  openQueue: name => opened.push(name),
}}).state();
day = '2026-08-15';
const finished = controller.finish();
const cleared = controller.state();
process.stdout.write(JSON.stringify({{started, handed, resumed, isolated, expired, finished, cleared, opened, handoffs}}));
"""

    assert run_node(script) == {
        "started": "agenda",
        "handed": True,
        "resumed": {"active": True, "next": "update", "label": "Review Updates"},
        "isolated": {"active": False, "next": "update", "label": "Review Updates"},
        "expired": {"active": False, "next": "update", "label": "Review Updates"},
        "finished": True,
        "cleared": {"active": False, "next": "update", "label": "Review Updates"},
        "opened": ["agenda"],
        "handoffs": [{"next": "update", "label": "Review Updates"}],
    }


def test_prepare_today_attention_hands_off_once_only_after_authoritative_refresh():
    script = f"""
const createStartDay = require({json.dumps(str(START_DAY))});
const saved = new Map();
const storage = {{
  getItem: key => saved.has(key) ? saved.get(key) : null,
  setItem: (key, value) => saved.set(key, value),
  removeItem: key => saved.delete(key),
}};
let counts = {{agenda:0, attention:1, update:2, filed:0, today:3}};
const opened = [];
const handoffs = [];
const controller = createStartDay({{
  storage,
  getLogin: () => 'timmy',
  getDay: () => '2026-08-15',
  getCounts: () => counts,
  openQueue: name => opened.push(name),
  onHandoff: current => handoffs.push({{next:current.next, label:current.label}}),
}});
const launchedAttention = controller.startNext();
counts = {{agenda:0, attention:0, update:2, filed:0, today:3}};
const ignoredPartial = controller.reconcile({{authoritative:false}});
const handedToUpdates = controller.reconcile({{authoritative:true}});
const duplicateRefresh = controller.reconcile({{authoritative:true}});
const launchedUpdates = controller.startNext();
counts = {{agenda:0, attention:0, update:0, filed:0, today:3}};
const handedToToday = controller.reconcile({{authoritative:true}});
const duplicateReadyRefresh = controller.reconcile({{authoritative:true}});
process.stdout.write(JSON.stringify({{
  launchedAttention, ignoredPartial, handedToUpdates, duplicateRefresh,
  launchedUpdates, handedToToday, duplicateReadyRefresh, opened, handoffs,
}}));
"""

    assert run_node(script) == {
        "launchedAttention": "attention",
        "ignoredPartial": False,
        "handedToUpdates": True,
        "duplicateRefresh": False,
        "launchedUpdates": "update",
        "handedToToday": True,
        "duplicateReadyRefresh": False,
        "opened": ["attention", "update"],
        "handoffs": [
            {"next": "update", "label": "Review Updates"},
            {"next": "today", "label": "Continue Today"},
        ],
    }


def test_prepare_today_checkpoint_uses_the_device_local_day():
    script = f"""
process.env.TZ = 'Pacific/Honolulu';
const RealDate = Date;
global.Date = class extends RealDate {{
  constructor(...args) {{ super(...(args.length ? args : ['2026-08-16T01:00:00Z'])); }}
}};
const createStartDay = require({json.dumps(str(START_DAY))});
const saved = new Map();
const storage = {{
  getItem: key => saved.has(key) ? saved.get(key) : null,
  setItem: (key, value) => saved.set(key, value),
  removeItem: key => saved.delete(key),
}};
createStartDay({{
  storage,
  getLogin: () => 'timmy',
  getCounts: () => ({{agenda:1}}),
  openQueue: () => {{}},
}}).startNext();
process.stdout.write(JSON.stringify(JSON.parse(saved.get('stackchain.mobile-start-day.v1'))));
"""

    assert run_node(script) == {"login": "timmy", "day": "2026-08-15", "phase": "agenda"}


@pytest.mark.anyio
async def test_dashboard_wires_thumb_safe_start_day_briefing_into_offline_mobile_bundle():
    html = await dashboard()
    service_worker = (START_DAY.parent / "service-worker.js").read_text()

    assert 'class="mobile-start-day"' in html
    assert 'id="mobile-start-day-summary"' in html
    assert 'id="mobile-start-day-phases"' in html
    assert 'id="mobile-start-day-action"' in html
    assert 'id="finish-mobile-start-day"' in html
    assert '<script src="static/mobile-start-day.js"></script>' in html
    assert "const mobileStartDay = createMobileStartDay({" in html
    assert "getLogin: () => confirmedOwnerLogin" in html
    assert "onHandoff: current =>" in html
    assert "mobileStartDay.completePhase('agenda')" in html
    assert "mobileStartDay.completePhase('update')" in html
    assert "mobileStartDay.completePhase('filed')" in html
    assert "authoritative:authoritativeMyWorkRefresh" in html
    assert "authoritativePhases:['delivery']" in html
    assert "mobileStartDay.finish()" in html
    assert "openQueue: name =>" in html
    assert "if (sheet.open) sheet.close();" in html
    assert "name === 'find' ? qs('#find-work').click() : mobileQueueLauncher.open(name)" in html
    assert "mobileStartDay.render();" in html
    assert ".mobile-start-day-action { width:100%; min-height:48px;" in html
    assert ".mobile-start-day-finish { min-height:44px;" in html
    assert "max-width:100%; overflow-wrap:anywhere;" in html
    assert "BASE + 'static/mobile-start-day.js'" in service_worker
    assert "stackchain-dashboard-shell-v127" in service_worker


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

    assert "getPreparationState: () => mobileStartDay.state()" in html
    assert "prepareToday: openMobileStartDay" in html
    assert "function openMobileStartDay()" in html
    assert "qs('#mobile-queue-heading').textContent = state.active ? 'Resume Prepare Today' : 'Prepare Today';" in html
    assert "qs('#mobile-queue-sheet').showModal();" in html
    assert "qs('#mobile-start-day-action').focus();" in html
    assert "let mobilePreparationItems = {};" in html
    assert "getPhaseItems: () => mobilePreparationItems" in html
    assert "mobilePreparationItems = {" in html
    assert "delivery:draftInbox.partition(lastDrafts).deliveries" in html
    assert "agenda:agendaMyWork(activeMyWork)" in html
    assert "attention:activeMyWork.filter(item => item.needs_attention)" in html
    assert "update:activeMyWork.filter(item => item.has_update)" in html
    assert "filed:activeMyWork.filter(item => item.is_filed)" in html
