import json
import subprocess
from pathlib import Path


CREATE_AND_START = Path(__file__).parents[1] / "frontend" / "create-and-start.js"
HTML = Path(__file__).parents[1] / "frontend" / "index.html"
DASHBOARD = Path(__file__).parents[1] / "frontend" / "dashboard.js"
CSS = Path(__file__).parents[1] / "frontend" / "dashboard.css"
WORKER = Path(__file__).parents[1] / "frontend" / "service-worker.js"


def run_node(script):
    return json.loads(subprocess.run(
        ["node", "-e", script], check=True, capture_output=True, text=True
    ).stdout)


def test_confirmed_issue_persists_and_syncs_estimate_before_starting_once():
    script = f"""
const fs = require('fs');
if (!fs.existsSync({json.dumps(str(CREATE_AND_START))})) {{
  process.stdout.write(JSON.stringify({{available:false}}));
}} else {{
  const createCreateAndStart = require({json.dumps(str(CREATE_AND_START))});
  const calls = [];
  const issue = {{kind:'issue', repository:'stackchain/dashboard', number:387}};
  const flow = createCreateAndStart({{
    todayWork: {{
      limit:5, read:()=>[], identity:item=>'issue:'+item.repository+':'+item.number+':',
      add:()=>{{calls.push('add');return 'added';}},
      planning:()=>({{capacity_minutes:120,estimates:{{}}}}),
      replacePlanning:plan=>{{calls.push('plan:'+JSON.stringify(plan));return true;}},
    }},
    todaySync: {{
      enqueue:(action,id)=>{{calls.push(action+':'+id);return true;}},
      enqueueConfiguration:(capacity,estimates)=>{{calls.push('configure:'+capacity+':'+JSON.stringify(estimates));return true;}},
      flush:()=>calls.push('flush')
    }},
    refresh:()=>calls.push('refresh'), warm:()=>calls.push('warm'), start:item=>calls.push('start:'+item.number),
    announce:message=>calls.push('announce:'+message),
  }});
  const result = flow.complete(issue, 45);
  flow.complete(issue, 45);
  process.stdout.write(JSON.stringify({{available:true,result,calls}}));
}}
"""

    assert run_node(script) == {
        "available": True,
        "result": "started",
        "calls": [
            "add",
            'plan:{"capacity_minutes":120,"estimates":{"issue:stackchain/dashboard:387:":45}}',
            "add:issue:stackchain/dashboard:387:",
            'configure:120:{"issue:stackchain/dashboard:387:":45}',
            "refresh",
            "flush",
            "warm",
            "start:387",
            "announce:Created, added to Today, and ready to work.",
        ],
    }


def test_mobile_capture_wires_distinct_create_and_start_intent_into_the_offline_shell():
    html = HTML.read_text()
    dashboard = DASHBOARD.read_text()
    css = CSS.read_text()
    worker = WORKER.read_text()

    assert '<button id="create-and-start-issue" type="submit">Create &amp; start</button>' in html
    assert '<script src="static/create-and-start.js"></script>' in html
    assert "const createAndStart = createCreateAndStart({" in dashboard
    assert "event.submitter.id === 'create-and-start-issue'" in dashboard
    assert "review.intent === 'create-and-start'" in dashboard
    assert 'id="create-issue-estimate"' in html
    assert 'min="5" max="1440" step="5"' in html
    assert "createAndStart.capacity(qs('#create-issue-estimate').value)" in dashboard
    assert "estimateMinutes: startCapacity.minutes" in dashboard
    assert "createAndStart.complete(created, completion.estimateMinutes)" in dashboard
    assert ".create-issue-estimate" in css
    assert ".create-issue-actions" in css and "grid-template-columns" in css
    assert "BASE + 'static/create-and-start.js'" in worker


def test_create_and_start_intent_is_durable_and_has_a_one_tap_relaunch_continuation():
    dashboard = DASHBOARD.read_text()

    assert "completionIntent: 'create-and-start'" in dashboard
    assert "result.completions || []" in dashboard
    assert "(result.completions || []).forEach(completion =>" in dashboard
    assert "issueOutbox.pendingCompletions(activeFlushLogin)" in dashboard
    assert "issueOutbox.completeIntent(completion.id, activeFlushLogin)" in dashboard
    assert 'class="draft-continue"' in dashboard
    assert "Continue created work" in dashboard


def test_create_and_start_stops_before_session_when_today_sync_cannot_be_queued():
    script = f"""
const createCreateAndStart = require({json.dumps(str(CREATE_AND_START))});
const calls = [];
const issue = {{kind:'issue', repository:'stackchain/dashboard', number:388}};
const flow = createCreateAndStart({{
  todayWork: {{limit:5, read:()=>[], identity:()=> 'issue:stackchain/dashboard:388:', add:()=> 'added'}},
  todaySync: {{enqueue:()=>false, flush:()=>calls.push('flush')}},
  refresh:()=>calls.push('refresh'), warm:()=>calls.push('warm'), start:()=>calls.push('start'),
  announce:message=>calls.push(message),
}});
process.stdout.write(JSON.stringify({{result:flow.complete(issue), calls}}));
"""

    assert run_node(script) == {
        "result": "sync-unavailable",
        "calls": ["Created and saved to Today on this device, but account sync is unavailable."],
    }


def test_create_and_start_can_resume_after_today_sync_admission_recovers():
    script = f"""
const createCreateAndStart = require({json.dumps(str(CREATE_AND_START))});
const calls=[];let admitted=false;let added=false;
const issue={{kind:'issue',repository:'stackchain/dashboard',number:389}};
const flow=createCreateAndStart({{
  todayWork:{{limit:5,read:()=>[],identity:()=> 'issue:stackchain/dashboard:389:',add:()=>{{
    const outcome=added?'exists':'added';added=true;return outcome;
  }}}},
  todaySync:{{enqueue:()=>admitted,flush:()=>calls.push('flush')}},
  refresh:()=>calls.push('refresh'),warm:()=>calls.push('warm'),start:()=>calls.push('start'),
  announce:message=>calls.push(message),
}});
const first=flow.complete(issue);
admitted=true;
const second=flow.complete(issue);
process.stdout.write(JSON.stringify({{first,second,calls}}));
"""
    output = run_node(script)

    assert output["first"] == "sync-unavailable"
    assert output["second"] == "started"
    assert output["calls"][-5:] == [
        "refresh", "flush", "warm", "start",
        "Created, added to Today, and ready to work.",
    ]


def test_create_and_start_reports_validation_and_remaining_today_capacity():
    script = f"""
const createCreateAndStart=require({json.dumps(str(CREATE_AND_START))});
const flow=createCreateAndStart({{
  todayWork:{{limit:5,read:()=>['first','second'],planning:()=>({{
    capacity_minutes:120,estimates:{{first:30,second:40}}
  }})}},
}});
process.stdout.write(JSON.stringify({{
  empty:flow.capacity(''),tooSmall:flow.capacity('4'),fits:flow.capacity('45'),over:flow.capacity('55')
}}));
"""
    assert run_node(script) == {
        "empty": {"valid": False, "reason": "required"},
        "tooSmall": {"valid": False, "reason": "range"},
        "fits": {"valid": True, "minutes": 45, "capacityMinutes": 120, "plannedMinutes": 70,
                 "remainingMinutes": 50, "projectedMinutes": 5, "fits": True},
        "over": {"valid": True, "minutes": 55, "capacityMinutes": 120, "plannedMinutes": 70,
                 "remainingMinutes": 50, "projectedMinutes": -5, "fits": False},
    }


def test_create_and_start_does_not_start_when_estimate_cannot_be_saved():
    script = f"""
const createCreateAndStart=require({json.dumps(str(CREATE_AND_START))});const calls=[];
const flow=createCreateAndStart({{
  todayWork:{{limit:5,read:()=>[],identity:()=> 'issue:o/r:849:',add:()=> 'added',
    planning:()=>({{capacity_minutes:60,estimates:{{}}}}),replacePlanning:()=>false}},
  todaySync:{{enqueue:()=>true,enqueueConfiguration:()=>true,flush:()=>calls.push('flush')}},
  refresh:()=>calls.push('refresh'),warm:()=>calls.push('warm'),start:()=>calls.push('start'),
  announce:message=>calls.push(message),
}});
process.stdout.write(JSON.stringify({{result:flow.complete({{repository:'o/r',number:849}},30),calls}}));
"""
    assert run_node(script) == {
        "result": "estimate-unavailable",
        "calls": ["Created and added to Today, but its estimate could not be saved on this device."],
    }


def test_create_and_start_does_not_start_when_estimate_sync_cannot_be_queued():
    script = f"""
const createCreateAndStart=require({json.dumps(str(CREATE_AND_START))});const calls=[];
const flow=createCreateAndStart({{
  todayWork:{{limit:5,read:()=>[],identity:()=> 'issue:o/r:849:',add:()=> 'added',
    planning:()=>({{capacity_minutes:60,estimates:{{}}}}),replacePlanning:()=>true}},
  todaySync:{{enqueue:()=>true,enqueueConfiguration:()=>false,flush:()=>calls.push('flush')}},
  refresh:()=>calls.push('refresh'),warm:()=>calls.push('warm'),start:()=>calls.push('start'),
  announce:message=>calls.push(message),
}});
process.stdout.write(JSON.stringify({{result:flow.complete({{repository:'o/r',number:849}},30),calls}}));
"""
    assert run_node(script) == {
        "result": "sync-unavailable",
        "calls": ["Created and estimated on this device, but Today plan sync is unavailable."],
    }
