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;
    guestLoginSpy: () => void;
    emailLogin: LoginStatus;
    logoutSpy: () => void;
  }> = {},
) {
  const logoutSpy = overrides.logoutSpy ?? vi.fn<() => void>();
  const guestLoginSpy = overrides.guestLoginSpy ?? vi.fn<() => void>();

  return authMachine.provide({
    actors: {
      checkAuthStatus: fromPromise<LoginStatus, void>(
        async () => overrides.checkAuthStatus ?? "notLoggedIn",
      ),
      guestLogin: fromPromise<LoginStatus, void>(
        async () => {
          guestLoginSpy();
          return 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<LoginStatus>(async () => {
        logoutSpy();
        return "guest";
      }),
    },
  });
}

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 guest login", async () => {
    const guestLoginSpy = vi.fn<() => void>();
    const actor = createActor(
      createTestAuthMachine({ checkAuthStatus: "facebook", guestLoginSpy }),
    ).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(guestLoginSpy).not.toHaveBeenCalled();

    actor.stop();
  });

  it("initializes as notLoggedIn without automatically guest logging in", async () => {
    const guestLoginSpy = vi.fn<() => void>();
    const actor = createActor(createTestAuthMachine({ guestLoginSpy })).start();

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

    const context = actor.getSnapshot().context;
    expect(context.loginStatus).toBe("notLoggedIn");
    expect(context.hasInitialized).toBe(true);
    expect(guestLoginSpy).not.toHaveBeenCalled();

    actor.stop();
  });

  it("guest logs in only after explicit guest login 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"));

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

    actor.stop();
  });

  it("email login updates login status", 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.hasInitialized).toBe(true);

    actor.stop();
  });

  it("logout transitions authenticated users back 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"));

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

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