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

import type { LoginStatus } from "@/data/dto/auth";
import type { AuthProvider } from "@/lib/auth/auth_platform";
import { authMachine } from "@/stores/auth/auth-machine";

function createTestAuthMachine(
  overrides: Partial<{
    checkAuthStatus: LoginStatus;
    guestLogin: LoginStatus;
    emailLogin: LoginStatus;
    logoutSpy: () => void;
  }> = {},
) {
  const logoutSpy = overrides.logoutSpy ?? vi.fn<() => void>();

  return authMachine.provide({
    actors: {
      checkAuthStatus: fromPromise<LoginStatus, void>(
        async () => overrides.checkAuthStatus ?? "notLoggedIn",
      ),
      guestLogin: fromPromise<LoginStatus, void>(
        async () => overrides.guestLogin ?? "guest",
      ),
      emailLogin: fromPromise<
        LoginStatus,
        { email: string; password: string }
      >(async () => overrides.emailLogin ?? "email"),
      emailRegisterThenLogin: fromPromise<
        LoginStatus,
        {
          email: string;
          password: string;
          username: string;
          confirmPassword: string;
        }
      >(async () => "email"),
      oauthSignIn: fromPromise<void, AuthProvider>(async () => {}),
      syncGoogleBackend: fromPromise<LoginStatus, { idToken: string }>(
        async () => "google",
      ),
      syncFacebookBackend: fromPromise<
        LoginStatus,
        { accessToken: string }
      >(async () => "facebook"),
      logout: fromPromise<void>(async () => {
        logoutSpy();
      }),
    },
  });
}

describe("authMachine", () => {
  it("updates panel mode, auth mode, and unsupported Apple login errors", () => {
    const actor = createActor(createTestAuthMachine()).start();

    actor.send({ type: "AuthPanelModeChanged", mode: "email" });
    actor.send({ type: "AuthModeChanged", mode: "register" });

    expect(actor.getSnapshot().context.authPanelMode).toBe("email");
    expect(actor.getSnapshot().context.authMode).toBe("register");

    actor.send({ type: "AuthAppleLoginSubmitted" });
    expect(actor.getSnapshot().context.errorMessage).toBe(
      "Apple login not implemented",
    );

    actor.send({ type: "AuthFormCleared" });
    expect(actor.getSnapshot().context.errorMessage).toBeNull();

    actor.stop();
  });

  it("initializes from storage without triggering a pending redirect", async () => {
    const actor = createActor(
      createTestAuthMachine({ checkAuthStatus: "facebook" }),
    ).start();

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

    const context = actor.getSnapshot().context;
    expect(context.loginStatus).toBe("facebook");
    expect(context.hasInitialized).toBe(true);
    expect(context.pendingRedirect).toBe(false);

    actor.stop();
  });

  it("guest login marks initialized but does not set pending redirect", async () => {
    const actor = createActor(createTestAuthMachine()).start();

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

    const context = actor.getSnapshot().context;
    expect(context.loginStatus).toBe("guest");
    expect(context.hasInitialized).toBe(true);
    expect(context.pendingRedirect).toBe(false);

    actor.stop();
  });

  it("email login sets pending redirect until it is cleared", async () => {
    const actor = createActor(createTestAuthMachine()).start();

    actor.send({
      type: "AuthEmailLoginSubmitted",
      email: "user@example.com",
      password: "12345678",
    });
    await waitFor(actor, (snapshot) => snapshot.matches("idle"));

    expect(actor.getSnapshot().context.loginStatus).toBe("email");
    expect(actor.getSnapshot().context.pendingRedirect).toBe(true);

    actor.send({ type: "AuthClearPendingRedirect" });
    expect(actor.getSnapshot().context.pendingRedirect).toBe(false);

    actor.stop();
  });

  it("logout resets auth state and keeps initialization completed", 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"));

    const context = actor.getSnapshot().context;
    expect(logoutSpy).toHaveBeenCalledOnce();
    expect(context.loginStatus).toBe("notLoggedIn");
    expect(context.pendingRedirect).toBe(false);
    expect(context.hasInitialized).toBe(true);

    actor.stop();
  });
});
