import test from "node:test";
import assert from "node:assert/strict";
import { createConnectionRecoveryCoordinator } from "./connection-recovery.js";

function createFakeTimer() {
  let callback = null;
  return {
    schedule(next) { callback = next; return 1; },
    cancel() { callback = null; },
    async flush() {
      const pending = callback;
      callback = null;
      await pending?.();
    }
  };
}

test("brief offline signal is ignored when the network returns within the grace period", async () => {
  const states = [];
  let verifies = 0;
  const timer = createFakeTimer();
  const coordinator = createConnectionRecoveryCoordinator({
    verify: async () => { verifies += 1; },
    recover: () => {},
    onState: (state) => states.push(state),
    schedule: timer.schedule,
    cancel: timer.cancel
  });

  coordinator.networkChanged(false);
  await coordinator.networkChanged(true);
  await timer.flush();

  assert.deepEqual(states, []);
  assert.equal(verifies, 0);
});

test("sustained offline signal is verified before the warning appears", async () => {
  const states = [];
  let verifies = 0;
  const timer = createFakeTimer();
  const coordinator = createConnectionRecoveryCoordinator({
    verify: async () => { verifies += 1; throw new Error("offline"); },
    recover: () => {},
    onState: (state) => states.push(state),
    schedule: timer.schedule,
    cancel: timer.cancel
  });

  coordinator.networkChanged(false);
  assert.deepEqual(states, []);
  await timer.flush();

  assert.deepEqual(states, ["offline"]);
  assert.equal(verifies, 1);
});

test("false native offline signal stays hidden when PC health is reachable", async () => {
  const states = [];
  const timer = createFakeTimer();
  const coordinator = createConnectionRecoveryCoordinator({
    verify: async () => {},
    recover: () => {},
    onState: (state) => states.push(state),
    schedule: timer.schedule,
    cancel: timer.cancel
  });

  coordinator.networkChanged(false);
  await timer.flush();

  assert.deepEqual(states, []);
});

test("foreground resume verifies and recovers after background pause", async () => {
  let recoveries = 0;
  const coordinator = createConnectionRecoveryCoordinator({
    verify: async () => {},
    recover: () => { recoveries += 1; },
    onState: () => {}
  });

  coordinator.appStateChanged(false);
  await coordinator.appStateChanged(true);

  assert.equal(recoveries, 1);
});

test("failed foreground verification does not claim the phone is offline", async () => {
  const states = [];
  const coordinator = createConnectionRecoveryCoordinator({
    verify: async () => { throw new Error("private network detail"); },
    recover: () => assert.fail("must not recover"),
    onState: (state) => states.push(state)
  });

  coordinator.appStateChanged(false);
  await coordinator.appStateChanged(true);

  assert.deepEqual(states, []);
});

test("concurrent recovery events share one health check", async () => {
  let release;
  let verifies = 0;
  const coordinator = createConnectionRecoveryCoordinator({
    verify: () => { verifies += 1; return new Promise((resolve) => { release = resolve; }); },
    recover: () => {},
    onState: () => {}
  });

  coordinator.appStateChanged(false);
  const first = coordinator.appStateChanged(true);
  const second = coordinator.appStateChanged(true);
  await Promise.resolve();
  release();
  await Promise.all([first, second]);

  assert.equal(verifies, 1);
});

test("confirmed offline state recovers only after PC health succeeds", async () => {
  const states = [];
  let shouldFail = true;
  let recoveries = 0;
  const timer = createFakeTimer();
  const coordinator = createConnectionRecoveryCoordinator({
    verify: async () => { if (shouldFail) throw new Error("offline"); },
    recover: () => { recoveries += 1; },
    onState: (state) => states.push(state),
    schedule: timer.schedule,
    cancel: timer.cancel
  });

  coordinator.networkChanged(false);
  await timer.flush();
  shouldFail = false;
  await coordinator.networkChanged(true);

  assert.deepEqual(states, ["offline", "recovering", "online"]);
  assert.equal(recoveries, 1);
});
