import { beforeEach, describe, expect, expectTypeOf, it, vi } from "vitest";
import { HttpError } from "../../../shared/api/errors";
import type { Id } from "../../../shared/api/types";
import {
  DMS_ACCESS_TOKEN_KEY,
  tokenStore,
} from "../../../shared/auth/tokenStore";
import type { AuthApi } from "../authApi";
import {
  getFirstAllowedPage,
  getNavigationItems,
  isPageAllowed,
  loginSession,
  logoutSession,
  restoreSession,
} from "../authSession";
import type { LoginRequest, UserSummary } from "../authTypes";

class MemoryStorage implements Storage {
  private readonly values = new Map<string, string>();
  get length(): number { return this.values.size; }
  clear(): void { this.values.clear(); }
  getItem(key: string): string | null { return this.values.get(key) ?? null; }
  key(index: number): string | null { return [...this.values.keys()][index] ?? null; }
  removeItem(key: string): void { this.values.delete(key); }
  setItem(key: string, value: string): void { this.values.set(key, value); }
}

const user: UserSummary = {
  id: "1",
  username: "user",
  realName: "普通用户",
  organizationId: null,
  organizationName: "",
  roleCode: "USER",
  securityLevel: "INTERNAL",
  status: "ENABLED",
  allowedModules: ["DOCUMENT_BROWSER"],
};

const request: LoginRequest = {
  username: "user",
  password: "secret",
  keepSignedIn: false,
};

function installStorage(): void {
  Object.defineProperty(globalThis, "sessionStorage", {
    configurable: true,
    value: new MemoryStorage(),
  });
  Object.defineProperty(globalThis, "localStorage", {
    configurable: true,
    value: new MemoryStorage(),
  });
}

function api(overrides: Partial<AuthApi> = {}): AuthApi {
  return {
    login: vi.fn().mockResolvedValue({
      accessToken: "jwt",
      tokenType: "Bearer",
      expiresIn: 7200,
      user,
    }),
    logout: vi.fn().mockResolvedValue(undefined),
    getCurrentUser: vi.fn().mockResolvedValue(user),
    ...overrides,
  };
}

describe("authSession", () => {
  beforeEach(installStorage);

  it("登录成功且 keepSignedIn=false 时只保存到 sessionStorage", async () => {
    await expect(loginSession(request, api())).resolves.toEqual(user);
    expect(sessionStorage.getItem(DMS_ACCESS_TOKEN_KEY)).toBe("jwt");
    expect(localStorage.getItem(DMS_ACCESS_TOKEN_KEY)).toBeNull();
  });

  it("登录成功且 keepSignedIn=true 时只保存到 localStorage", async () => {
    await loginSession({ ...request, keepSignedIn: true }, api());
    expect(localStorage.getItem(DMS_ACCESS_TOKEN_KEY)).toBe("jwt");
    expect(sessionStorage.getItem(DMS_ACCESS_TOKEN_KEY)).toBeNull();
  });

  it("登录失败不保存 Token，也不保存用户名和密码", async () => {
    const failingApi = api({
      login: vi.fn().mockRejectedValue(
        new HttpError({
          httpStatus: 401,
          code: "INVALID_CREDENTIALS",
          message: "用户名或密码错误",
          requestId: "login-error",
        }),
      ),
    });

    await expect(loginSession(request, failingApi)).rejects.toMatchObject({
      code: "INVALID_CREDENTIALS",
    });
    expect(sessionStorage.length).toBe(0);
    expect(localStorage.length).toBe(0);
    expect(JSON.stringify([...Object.keys(sessionStorage)])).not.toContain("user");
    expect(JSON.stringify([...Object.keys(localStorage)])).not.toContain("secret");
  });

  it("存在 Token 时通过 users/me 恢复用户，无 Token 时不发请求", async () => {
    const getCurrentUser = vi.fn().mockResolvedValue(user);
    const restoreApi = api({ getCurrentUser });

    await expect(restoreSession(restoreApi)).resolves.toBeNull();
    expect(getCurrentUser).not.toHaveBeenCalled();

    tokenStore.saveAccessToken("jwt", false);
    await expect(restoreSession(restoreApi)).resolves.toEqual(user);
    expect(getCurrentUser).toHaveBeenCalledTimes(1);
  });

  it("非401恢复错误不会清除仍可能有效的 Token", async () => {
    tokenStore.saveAccessToken("jwt", false);
    const restoreApi = api({
      getCurrentUser: vi.fn().mockRejectedValue(
        new HttpError({
          httpStatus: 0,
          code: "INTERNAL_ERROR",
          message: "网络请求失败",
          requestId: "network-id",
        }),
      ),
    });

    await expect(restoreSession(restoreApi)).rejects.toMatchObject({
      httpStatus: 0,
    });
    expect(tokenStore.getAccessToken()).toBe("jwt");
  });

  it("allowedModules 按正式界面顺序生成导航，不补全后端未授权模块", () => {
    const modules = ["AUDIT_LOG", "DOCUMENT_BROWSER"] as const;
    const labels: Record<string, string> = {
      AUDIT_LOG: "日志审计",
      DOCUMENT_BROWSER: "文档浏览",
    };
    expect(getNavigationItems(modules, (module) => labels[module])).toEqual([
      { module: "DOCUMENT_BROWSER", page: "main", label: "文档浏览" },
      { module: "AUDIT_LOG", page: "audit", label: "日志审计" },
    ]);
    expect(getFirstAllowedPage(modules)).toBe("main");
    expect(isPageAllowed("admin", modules)).toBe(false);
    expect(isPageAllowed("main", modules)).toBe(true);
  });

  it("没有 allowedModules 时没有默认页面", () => {
    expect(getNavigationItems([], () => "未使用")).toEqual([]);
    expect(getFirstAllowedPage([])).toBeNull();
  });

  it("退出成功清理本地 Token", async () => {
    tokenStore.saveAccessToken("jwt", true);
    await expect(logoutSession(api())).resolves.toEqual({
      serverConfirmed: true,
      error: null,
    });
    expect(tokenStore.getAccessToken()).toBeNull();
  });

  it("退出失败仍清理本地 Token并返回未确认结果", async () => {
    tokenStore.saveAccessToken("jwt", false);
    const error = new Error("offline");
    const result = await logoutSession(
      api({ logout: vi.fn().mockRejectedValue(error) }),
    );
    expect(result).toEqual({ serverConfirmed: false, error });
    expect(tokenStore.getAccessToken()).toBeNull();
  });

  it("认证DTO的ID类型保持字符串", () => {
    expectTypeOf<Id>().toEqualTypeOf<string>();
    expectTypeOf(user.id).toEqualTypeOf<string>();
  });
});
