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

import { AuthRepository } from "@/data/repositories/auth_repository";
import {
  GuestLoginResponseSchema,
  LoginResponseSchema,
  LoginStatus,
  RefreshTokenResponseSchema,
} from "@/data/schemas/auth";
import type { AuthApi } from "@/data/services/api";
import type { IAuthStorage } from "@/data/storage/auth";
import type { IUserStorage } from "@/data/storage/user";
import { Result } from "@/utils/result";

function createRepository(
  input: {
    api?: Partial<AuthApi>;
    storage?: Partial<IAuthStorage>;
    userStorage?: Partial<IUserStorage>;
  } = {},
) {
  const storage = {
    getRefreshToken: vi.fn(async () => Result.ok("refresh-token")),
    setLoginToken: vi.fn(async () => Result.ok(undefined)),
    setRefreshToken: vi.fn(async () => Result.ok(undefined)),
    setGuestToken: vi.fn(async () => Result.ok(undefined)),
    setLoginProvider: vi.fn(async () => Result.ok(undefined)),
    setDeviceId: vi.fn(async () => Result.ok(undefined)),
    clearBusinessAuthData: vi.fn(async () => Result.ok(undefined)),
    hasGuestToken: vi.fn(async () => Result.ok(false)),
    ...input.storage,
  } as unknown as IAuthStorage;
  const userStorage = {
    setUser: vi.fn(async () => Result.ok(undefined)),
    setUserId: vi.fn(async () => Result.ok(undefined)),
    clearUserData: vi.fn(async () => Result.ok(undefined)),
    ...input.userStorage,
  } as unknown as IUserStorage;
  const api = {
    logout: vi.fn(async () => undefined),
    ...input.api,
  } as AuthApi;

  return {
    repository: new AuthRepository(api, storage, userStorage),
    api,
    storage,
    userStorage,
  };
}

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

describe("AuthRepository session Result handling", () => {
  const loginResponse = LoginResponseSchema.parse({
    token: "login-token",
    refreshToken: "refresh-token",
    user: { id: "user-1", username: "Elio" },
  });

  it("returns a failed auth cleanup Result while still attempting user cleanup", async () => {
    const clearError = new Error("auth cleanup failed");
    const { repository, userStorage } = createRepository({
      storage: {
        clearBusinessAuthData: vi.fn(async () => Result.err(clearError)),
      },
    });

    const result = await repository.logout();

    expect(Result.isErr(result)).toBe(true);
    expect(Result.isErr(result) && result.error.message).toBe(
      clearError.message,
    );
    expect(userStorage.clearUserData).toHaveBeenCalledOnce();
  });

  it("returns a failed user cleanup Result from logout", async () => {
    const clearError = new Error("user cleanup failed");
    const { repository } = createRepository({
      userStorage: {
        clearUserData: vi.fn(async () => Result.err(clearError)),
      },
    });

    const result = await repository.logout();

    expect(Result.isErr(result)).toBe(true);
    expect(Result.isErr(result) && result.error.message).toBe(
      clearError.message,
    );
  });

  it("propagates refresh-token storage read failures", async () => {
    const storageError = new Error("refresh token read failed");
    const refreshToken = vi.fn();
    const { repository } = createRepository({
      api: { refreshToken },
      storage: {
        getRefreshToken: vi.fn(async () => Result.err(storageError)),
      },
    });

    const result = await repository.refreshToken();

    expect(Result.isErr(result)).toBe(true);
    expect(Result.isErr(result) && result.error.message).toBe(
      storageError.message,
    );
    expect(refreshToken).not.toHaveBeenCalled();
  });

  it("does not report refresh success when a new token cannot be stored", async () => {
    const storageError = new Error("login token write failed");
    const response = RefreshTokenResponseSchema.parse({
      token: "new-login-token",
      refreshToken: "new-refresh-token",
      userId: "user-1",
    });
    const { repository, storage } = createRepository({
      api: { refreshToken: vi.fn(async () => response) },
      storage: {
        setLoginToken: vi.fn(async () => Result.err(storageError)),
      },
    });

    const result = await repository.refreshToken();

    expect(Result.isErr(result)).toBe(true);
    expect(Result.isErr(result) && result.error.message).toBe(
      storageError.message,
    );
    expect(storage.setRefreshToken).not.toHaveBeenCalled();
  });

  it("does not report guest login success when session persistence fails", async () => {
    const storageError = new Error("guest token write failed");
    const response = GuestLoginResponseSchema.parse({
      token: "guest-token",
      deviceId: "device-1",
      userId: "guest-1",
      isDeviceUser: true,
      expiresIn: 3600,
      user: null,
    });
    const { repository, storage } = createRepository({
      api: { guestLogin: vi.fn(async () => response) },
      storage: {
        setGuestToken: vi.fn(async () => Result.err(storageError)),
      },
    });

    const result = await repository.guestLogin("device-1");

    expect(Result.isErr(result)).toBe(true);
    expect(Result.isErr(result) && result.error.message).toBe(
      storageError.message,
    );
    expect(storage.setLoginProvider).not.toHaveBeenCalled();
  });

  it("does not report email login success when the login token cannot be stored", async () => {
    const storageError = new Error("login token write failed");
    const { repository, storage, userStorage } = createRepository({
      api: { emailLogin: vi.fn(async () => loginResponse) },
      storage: {
        setLoginToken: vi.fn(async () => Result.err(storageError)),
      },
    });

    const result = await repository.emailLogin({
      email: "elio@example.test",
      password: "password",
    });

    expect(Result.isErr(result)).toBe(true);
    expect(Result.isErr(result) && result.error.message).toBe(
      storageError.message,
    );
    expect(storage.setRefreshToken).not.toHaveBeenCalled();
    expect(userStorage.setUserId).not.toHaveBeenCalled();
    expect(storage.setLoginProvider).not.toHaveBeenCalled();
  });

  it("writes the provider only after all other session identity fields", async () => {
    const storageError = new Error("user id write failed");
    const { repository, storage, userStorage } = createRepository({
      api: { emailLogin: vi.fn(async () => loginResponse) },
      userStorage: {
        setUserId: vi.fn(async () => Result.err(storageError)),
      },
    });

    const result = await repository.emailLogin({
      email: "elio@example.test",
      password: "password",
    });

    expect(Result.isErr(result)).toBe(true);
    expect(storage.setLoginToken).toHaveBeenCalledWith("login-token");
    expect(storage.setRefreshToken).toHaveBeenCalledWith("refresh-token");
    expect(userStorage.setUserId).toHaveBeenCalledWith("user-1");
    expect(storage.setLoginProvider).not.toHaveBeenCalled();
    expect(userStorage.setUser).not.toHaveBeenCalled();
  });

  it("keeps login successful when the non-critical user cache fails", async () => {
    const cacheError = new Error("user cache write failed");
    const { repository, storage, userStorage } = createRepository({
      api: { emailLogin: vi.fn(async () => loginResponse) },
      userStorage: {
        setUser: vi.fn(async () => Result.err(cacheError)),
      },
    });

    const result = await repository.emailLogin({
      email: "elio@example.test",
      password: "password",
    });

    expect(result).toEqual(Result.ok(loginResponse));
    expect(storage.setLoginProvider).toHaveBeenCalledWith(LoginStatus.Email);
    expect(userStorage.setUser).toHaveBeenCalledOnce();
  });

  it("keeps a current-user response successful when its cache write throws", async () => {
    const { repository } = createRepository({
      api: { getCurrentUser: vi.fn(async () => loginResponse.user) },
      userStorage: {
        setUser: vi.fn(async () => {
          throw new Error("storage unavailable");
        }),
      },
    });

    const result = await repository.getCurrentUser();

    expect(result).toEqual(Result.ok(loginResponse.user));
  });

  it("persists a top-up handoff as a formal Messenger session", async () => {
    const handoffResponse = {
      ...loginResponse,
      loginStatus: LoginStatus.FacebookMessenger,
      merged: false,
    } as const;
    const { repository, storage, userStorage } = createRepository({
      api: {
        consumeTopUpHandoff: vi.fn(async () => handoffResponse),
      },
    });

    const result = await repository.consumeTopUpHandoff("x".repeat(32));

    expect(result).toEqual(Result.ok(LoginStatus.FacebookMessenger));
    expect(storage.setLoginToken).toHaveBeenCalledWith("login-token");
    expect(storage.setRefreshToken).toHaveBeenCalledWith("refresh-token");
    expect(userStorage.setUserId).toHaveBeenCalledWith("user-1");
    expect(storage.setLoginProvider).toHaveBeenCalledWith(
      LoginStatus.FacebookMessenger,
    );
  });
});
