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

import {
  GuestLoginResponse,
  RefreshTokenResponse,
} from "@/data/dto/auth";
import { AuthRepository } from "@/data/repositories/auth_repository";
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 = {
    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", () => {
  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 = RefreshTokenResponse.from({
      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 = GuestLoginResponse.from({
      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();
  });
});
