import json
import subprocess
from pathlib import Path

import pytest

from tests.dashboard_bundle import dashboard


INSTALL_APP = Path(__file__).resolve().parents[1] / "frontend" / "install-app.js"


def run_install_scenario(scenario: str) -> dict:
    script = f"""
const createInstallApp = require({json.dumps(str(INSTALL_APP))});
class FakeTarget {{
  constructor() {{ this.listeners = {{}}; }}
  addEventListener(name, callback) {{ (this.listeners[name] ||= []).push(callback); }}
  dispatch(name, event = {{}}) {{ for (const callback of this.listeners[name] || []) callback(event); }}
}}
class FakeButton extends FakeTarget {{
  constructor() {{ super(); this.disabled = false; }}
  click() {{ this.dispatch('click', {{currentTarget:this}}); }}
}}
(async () => {{
{scenario}
}})().catch(error => {{ console.error(error); process.exit(1); }});
"""
    completed = subprocess.run(
        ["node", "-e", script], capture_output=True, text=True
    )
    assert completed.returncode == 0, completed.stderr
    return json.loads(completed.stdout)


def test_native_install_prompt_is_revealed_by_eligibility_and_invoked_once():
    result = run_install_scenario(
        """
const windowTarget = new FakeTarget();
const card = {hidden:true};
const install = new FakeButton();
const dismiss = new FakeButton();
const guidance = {hidden:true};
const status = {textContent:''};
let promptCalls = 0;
const app = createInstallApp({
  window:windowTarget, card, installButton:install, dismissButton:dismiss,
  guidance, status, storage:{getItem:()=>null, setItem:()=>{}},
  isStandalone:()=>false, isIosSafari:()=>false,
});
app.start();
windowTarget.dispatch('beforeinstallprompt', {
  preventDefault(){},
  prompt:async () => { promptCalls += 1; },
  userChoice:Promise.resolve({outcome:'accepted'}),
});
const shown = !card.hidden;
install.click();
await new Promise(resolve => setImmediate(resolve));
install.click();
await new Promise(resolve => setImmediate(resolve));
process.stdout.write(JSON.stringify({shown, promptCalls, hidden:card.hidden, status:status.textContent}));
"""
    )

    assert result == {
        "shown": True,
        "promptCalls": 1,
        "hidden": True,
        "status": "Stackchain was added to your device.",
    }


def test_declined_native_install_hides_the_spent_choice_and_announces_it():
    result = run_install_scenario(
        """
const windowTarget = new FakeTarget();
const card = {hidden:true};
const install = new FakeButton();
const status = {textContent:''};
const app = createInstallApp({
  window:windowTarget, card, installButton:install, dismissButton:new FakeButton(),
  guidance:{hidden:true}, status, storage:{getItem:()=>null, setItem:()=>{}},
  isStandalone:()=>false, isIosSafari:()=>false,
});
app.start();
windowTarget.dispatch('beforeinstallprompt', {
  preventDefault(){}, prompt:async()=>{}, userChoice:Promise.resolve({outcome:'dismissed'}),
});
install.click();
await new Promise(resolve => setImmediate(resolve));
process.stdout.write(JSON.stringify({hidden:card.hidden, disabled:install.disabled, status:status.textContent}));
"""
    )

    assert result == {
        "hidden": True,
        "disabled": False,
        "status": "Installation was not completed.",
    }


def test_ios_safari_gets_manual_add_to_home_screen_guidance():
    result = run_install_scenario(
        """
const windowTarget = new FakeTarget();
const card = {hidden:true};
const install = new FakeButton(); install.hidden = false;
const dismiss = new FakeButton();
const guidance = {hidden:true};
const status = {textContent:''};
const app = createInstallApp({
  window:windowTarget, card, installButton:install, dismissButton:dismiss,
  guidance, status, storage:{getItem:()=>null, setItem:()=>{}},
  isStandalone:()=>false, isIosSafari:()=>true,
});
app.start();
process.stdout.write(JSON.stringify({cardHidden:card.hidden, installHidden:install.hidden, guidanceHidden:guidance.hidden}));
"""
    )

    assert result == {
        "cardHidden": False,
        "installHidden": True,
        "guidanceHidden": False,
    }


def test_standalone_launch_never_promotes_installation():
    result = run_install_scenario(
        """
const windowTarget = new FakeTarget();
const card = {hidden:true};
const app = createInstallApp({
  window:windowTarget, card, installButton:new FakeButton(), dismissButton:new FakeButton(),
  guidance:{hidden:true}, status:{textContent:''}, storage:{getItem:()=>null, setItem:()=>{}},
  isStandalone:()=>true, isIosSafari:()=>true,
});
app.start();
windowTarget.dispatch('beforeinstallprompt', {preventDefault(){}, prompt:async()=>{}, userChoice:Promise.resolve({outcome:'accepted'})});
process.stdout.write(JSON.stringify({hidden:card.hidden}));
"""
    )

    assert result == {"hidden": True}


def test_dismissal_is_remembered_and_suppresses_later_promotion():
    result = run_install_scenario(
        """
const values = new Map();
const storage = {getItem:key => values.get(key) || null, setItem:(key, value) => values.set(key, value)};
const firstWindow = new FakeTarget();
const firstCard = {hidden:true};
const firstDismiss = new FakeButton();
const first = createInstallApp({
  window:firstWindow, card:firstCard, installButton:new FakeButton(), dismissButton:firstDismiss,
  guidance:{hidden:true}, status:{textContent:''}, storage,
  isStandalone:()=>false, isIosSafari:()=>true,
});
first.start();
firstDismiss.click();
const secondCard = {hidden:true};
const second = createInstallApp({
  window:new FakeTarget(), card:secondCard, installButton:new FakeButton(), dismissButton:new FakeButton(),
  guidance:{hidden:true}, status:{textContent:''}, storage,
  isStandalone:()=>false, isIosSafari:()=>true,
});
second.start();
process.stdout.write(JSON.stringify({firstHidden:firstCard.hidden, remembered:values.size, secondHidden:secondCard.hidden}));
"""
    )

    assert result == {"firstHidden": True, "remembered": 1, "secondHidden": True}


def test_setup_api_reports_install_state_without_prompting():
    result = run_install_scenario(
        """
const windowTarget = new FakeTarget();
const app = createInstallApp({
  window:windowTarget, card:{hidden:true}, installButton:new FakeButton(), dismissButton:new FakeButton(),
  guidance:{hidden:true}, status:{textContent:''}, storage:{getItem:()=>null, setItem:()=>{}},
  isStandalone:()=>false, isIosSafari:()=>false,
});
app.start();
const before = app.state();
windowTarget.dispatch('beforeinstallprompt', {preventDefault(){}, prompt:async()=>{}, userChoice:Promise.resolve({outcome:'accepted'})});
const after = app.state();
process.stdout.write(JSON.stringify({before, after}));
"""
    )

    assert result == {
        "before": {"state": "unavailable", "detail": "Installation is available from a supported browser."},
        "after": {"state": "incomplete", "detail": "Stackchain is ready to install."},
    }


def test_captured_install_prompt_remains_available_until_setup_opens():
    result = run_install_scenario(
        """
let prevented = 0;
const prompt = {preventDefault(){prevented += 1;}, prompt:async()=>{}, userChoice:Promise.resolve({outcome:'accepted'})};
const card = {hidden:true};
const app = createInstallApp({
  window:new FakeTarget(), initialPrompt:prompt, card,
  installButton:new FakeButton(), dismissButton:new FakeButton(), guidance:{hidden:true},
  status:{textContent:''}, storage:{getItem:()=>null, setItem:()=>{}},
  isStandalone:()=>false, isIosSafari:()=>false,
});
app.start();
process.stdout.write(JSON.stringify({state:app.state(), shown:!card.hidden, prevented}));
"""
    )

    assert result == {
        "state": {"state": "incomplete", "detail": "Stackchain is ready to install."},
        "shown": True,
        "prevented": 1,
    }


def test_appinstalled_hides_an_visible_native_promotion():
    result = run_install_scenario(
        """
const windowTarget = new FakeTarget();
const card = {hidden:true};
const app = createInstallApp({
  window:windowTarget, card, installButton:new FakeButton(), dismissButton:new FakeButton(),
  guidance:{hidden:true}, status:{textContent:''}, storage:{getItem:()=>null, setItem:()=>{}},
  isStandalone:()=>false, isIosSafari:()=>false,
});
app.start();
windowTarget.dispatch('beforeinstallprompt', {preventDefault(){}, prompt:async()=>{}, userChoice:Promise.resolve({outcome:'accepted'})});
const shown = !card.hidden;
windowTarget.dispatch('appinstalled');
process.stdout.write(JSON.stringify({shown, hidden:card.hidden}));
"""
    )

    assert result == {"shown": True, "hidden": True}


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

    assert 'id="install-app-card"' in html
    assert 'id="install-app"' in html
    assert 'id="dismiss-install-app"' in html
    assert 'id="install-app-guidance"' in html
    assert 'Share, then choose <strong>Add to Home Screen</strong>' in html
    assert '.install-app-card button { min-height:44px;' in html
    assert '<script src="static/install-app.js"></script>' in html
    assert 'createInstallApp({' in html
    assert "window.matchMedia('(display-mode: standalone)').matches" in html
    assert "navigator.standalone === true" in html
    assert "isIosSafari" in html
