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

import type { BindFacebookIdentityInput } from "@/stores/auth/auth-events";

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

describe("auth session flow", () => {
  it("updates panel mode", () => {
    const actor = createActor(createTestAuthMachine()).start();
    actor.send({ type: "AuthPanelModeChanged", mode: "email" });
    expect(actor.getSnapshot().context.authPanelMode).toBe("email");
    actor.stop();
  });

  it("initializes from storage without triggering guest login", async () => {
    const guestLoginSpy = vi.fn<() => void>();
    const bindFacebookIdentitySpy =
      vi.fn<(input: BindFacebookIdentityInput) => void>();
    const actor = createActor(
      createTestAuthMachine({
        checkAuthStatus: "facebook",
        guestLoginSpy,
        bindFacebookIdentitySpy,
      }),
    ).start();

    actor.send({ type: "AuthInit" });
    await waitFor(actor, (snapshot) => snapshot.matches("idle"));

    expect(actor.getSnapshot().context).toMatchObject({
      loginStatus: "facebook",
      hasInitialized: true,
    });
    expect(guestLoginSpy).not.toHaveBeenCalled();
    expect(bindFacebookIdentitySpy).not.toHaveBeenCalled();
    actor.stop();
  });

  it("binds identity only after the external-entry event", async () => {
    const bindFacebookIdentitySpy =
      vi.fn<(input: BindFacebookIdentityInput) => void>();
    const actor = createActor(
      createTestAuthMachine({
        checkAuthStatus: "email",
        bindFacebookIdentitySpy,
      }),
    ).start();

    actor.send({ type: "AuthInit" });
    await waitFor(actor, (snapshot) => snapshot.matches("idle"));
    actor.send({
      type: "AuthExternalFacebookIdentityBindRequested",
      asid: "asid-1",
      psid: "psid-1",
    });
    await waitFor(actor, (snapshot) => snapshot.matches("idle"));

    expect(bindFacebookIdentitySpy).toHaveBeenCalledWith({
      asid: "asid-1",
      psid: "psid-1",
    });
    expect(actor.getSnapshot().context.loginStatus).toBe("email");
    actor.stop();
  });

  it("keeps login status when identity binding fails", async () => {
    const actor = createActor(
      createTestAuthMachine({
        checkAuthStatus: "google",
        bindFacebookIdentitySpy: () => {
          throw new Error("bind failed");
        },
      }),
    ).start();

    actor.send({ type: "AuthInit" });
    await waitFor(actor, (snapshot) => snapshot.matches("idle"));
    actor.send({
      type: "AuthExternalFacebookIdentityBindRequested",
      psid: "psid-1",
    });
    await waitFor(actor, (snapshot) => snapshot.matches("idle"));

    expect(actor.getSnapshot().context.loginStatus).toBe("google");
    expect(actor.getSnapshot().context.errorMessage).toBeNull();
    actor.stop();
  });

  it("does not automatically create a guest login during init", async () => {
    const guestLoginSpy = vi.fn<() => void>();
    const actor = createActor(createTestAuthMachine({ guestLoginSpy })).start();
    actor.send({ type: "AuthInit" });
    await waitFor(actor, (snapshot) => snapshot.matches("idle"));

    expect(actor.getSnapshot().context.loginStatus).toBe("notLoggedIn");
    expect(guestLoginSpy).not.toHaveBeenCalled();
    actor.stop();
  });

  it("guest logs in only after explicit submission", async () => {
    const guestLoginSpy = vi.fn<() => void>();
    const actor = createActor(createTestAuthMachine({ guestLoginSpy })).start();
    actor.send({ type: "AuthInit" });
    await waitFor(actor, (snapshot) => snapshot.matches("idle"));
    actor.send({ type: "AuthGuestLoginSubmitted" });
    await waitFor(actor, (snapshot) => snapshot.matches("idle"));

    expect(actor.getSnapshot().context.loginStatus).toBe("guest");
    expect(guestLoginSpy).toHaveBeenCalledOnce();
    actor.stop();
  });

  it("logs authenticated users out to guest", async () => {
    const logoutSpy = vi.fn<() => void>();
    const actor = createActor(createTestAuthMachine({ logoutSpy })).start();
    actor.send({
      type: "AuthEmailLoginSubmitted",
      email: "user@example.com",
      password: "12345678",
    });
    await waitFor(actor, (snapshot) => snapshot.context.loginStatus === "email");
    actor.send({ type: "AuthLogoutSubmitted" });
    await waitFor(actor, (snapshot) => snapshot.matches("idle"));

    expect(logoutSpy).toHaveBeenCalledOnce();
    expect(actor.getSnapshot().context.loginStatus).toBe("guest");
    actor.stop();
  });
});
