import { afterEach, describe, expect, it, vi } from "vitest";
import { createActor, waitFor } from "xstate";

import { OAUTH_LAUNCH_TIMEOUT_MS } from "../machine/oauth-flow";
import { createTestAuthMachine } from "./auth-machine.test-utils";

afterEach(() => {
  vi.useRealTimers();
});

describe("auth OAuth flow", () => {
  it("applies Google backend sync status", async () => {
    const actor = createActor(createTestAuthMachine()).start();
    actor.send({ type: "AuthGoogleSyncSubmitted", idToken: "google-token" });
    await waitFor(actor, (snapshot) => snapshot.matches("idle"));
    expect(actor.getSnapshot().context.loginStatus).toBe("google");
    actor.stop();
  });

  it("applies Facebook backend sync status", async () => {
    const actor = createActor(createTestAuthMachine()).start();
    actor.send({
      type: "AuthFacebookSyncSubmitted",
      accessToken: "facebook-token",
    });
    await waitFor(actor, (snapshot) => snapshot.matches("idle"));
    expect(actor.getSnapshot().context.loginStatus).toBe("facebook");
    actor.stop();
  });

  it("keeps loading after OAuth launch until the callback arrives", async () => {
    const actor = createActor(createTestAuthMachine()).start();

    actor.send({ type: "AuthFacebookLoginSubmitted", provider: "facebook" });
    await waitFor(actor, (snapshot) => snapshot.matches("loadingOAuth"));
    await Promise.resolve();

    expect(actor.getSnapshot().matches("loadingOAuth")).toBe(true);

    actor.send({
      type: "AuthFacebookSyncSubmitted",
      accessToken: "facebook-token",
    });
    await waitFor(actor, (snapshot) => snapshot.matches("idle"));

    expect(actor.getSnapshot().context.loginStatus).toBe("facebook");
    actor.stop();
  });

  it("accepts an OAuth callback while auth initialization is running", async () => {
    const actor = createActor(createTestAuthMachine()).start();

    actor.send({ type: "AuthInit" });
    expect(actor.getSnapshot().matches("initializing")).toBe(true);
    actor.send({
      type: "AuthFacebookSyncSubmitted",
      accessToken: "facebook-token",
    });
    await waitFor(actor, (snapshot) => snapshot.matches("idle"));

    expect(actor.getSnapshot().context.loginStatus).toBe("facebook");
    actor.stop();
  });

  it("returns to idle with an error when OAuth navigation times out", async () => {
    vi.useFakeTimers();
    const actor = createActor(createTestAuthMachine()).start();

    actor.send({ type: "AuthFacebookLoginSubmitted", provider: "facebook" });
    expect(actor.getSnapshot().matches("loadingOAuth")).toBe(true);

    await vi.advanceTimersByTimeAsync(OAUTH_LAUNCH_TIMEOUT_MS);

    expect(actor.getSnapshot().matches("idle")).toBe(true);
    expect(actor.getSnapshot().context.errorMessage).toBe(
      "Sign-in did not open. Please try again.",
    );
    actor.stop();
  });

  it("returns to idle with an error when OAuth launch fails", async () => {
    const actor = createActor(
      createTestAuthMachine({ oauthSignInError: new Error("oauth failed") }),
    ).start();
    actor.send({ type: "AuthGoogleLoginSubmitted", provider: "google" });
    await waitFor(actor, (snapshot) => snapshot.matches("idle"));
    expect(actor.getSnapshot().context.errorMessage).toBe("oauth failed");
    actor.stop();
  });
});
