import { describe, expect, it, vi } from "vitest";
import {
  buildAuthorizeUrl,
  exchangeCodeForUser,
  FeishuOAuthError,
  type FeishuOAuthConfig,
} from "../../src/admin/auth/feishuOAuth.js";

describe("buildAuthorizeUrl", () => {
  it("includes client_id, redirect, state, and scope", () => {
    const url = buildAuthorizeUrl(
      {
        appId: "cli_test",
        appSecret: "secret",
        redirectUri: "http://localhost:8788/auth/feishu/callback",
        scope: "contact:user.base:readonly",
      },
      "state-token",
    );
    const parsed = new URL(url);
    expect(parsed.origin + parsed.pathname).toBe(
      "https://accounts.feishu.cn/open-apis/authen/v1/authorize",
    );
    expect(parsed.searchParams.get("client_id")).toBe("cli_test");
    expect(parsed.searchParams.get("response_type")).toBe("code");
    expect(parsed.searchParams.get("redirect_uri")).toBe(
      "http://localhost:8788/auth/feishu/callback",
    );
    expect(parsed.searchParams.get("state")).toBe("state-token");
    expect(parsed.searchParams.get("scope")).toBe("contact:user.base:readonly");
  });
});

describe("exchangeCodeForUser", () => {
  it("exchanges code then loads user_info", async () => {
    const fetchImpl = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
      const url = String(input);
      if (url.includes("/oauth/token")) {
        expect(init?.method).toBe("POST");
        const body = JSON.parse(String(init?.body)) as { code: string };
        expect(body.code).toBe("auth-code");
        return new Response(
          JSON.stringify({ code: 0, access_token: "u-token", token_type: "Bearer", expires_in: 7200 }),
          { status: 200, headers: { "Content-Type": "application/json" } },
        );
      }
      if (url.includes("/user_info")) {
        expect((init?.headers as Record<string, string>).Authorization).toBe("Bearer u-token");
        return new Response(
          JSON.stringify({
            code: 0,
            data: { open_id: "ou_alice", name: "Alice", avatar_url: "https://img/a.png" },
          }),
          { status: 200, headers: { "Content-Type": "application/json" } },
        );
      }
      throw new Error(`unexpected url ${url}`);
    });

    const config: FeishuOAuthConfig = {
      appId: "cli_test",
      appSecret: "secret",
      redirectUri: "http://localhost:8788/auth/feishu/callback",
      scope: "contact:user.base:readonly",
      fetchImpl: fetchImpl as unknown as typeof fetch,
    };

    const user = await exchangeCodeForUser(config, "auth-code");
    expect(user).toEqual({
      openId: "ou_alice",
      displayName: "Alice",
      avatarUrl: "https://img/a.png",
    });
    expect(fetchImpl).toHaveBeenCalledTimes(2);
  });

  it("throws on token exchange failure", async () => {
    const fetchImpl = vi.fn(async () =>
      new Response(JSON.stringify({
        code: 20003,
        error: "invalid_grant",
        error_description: "echoed app secret s and authorization code bad",
      }), {
        status: 200,
        headers: { "Content-Type": "application/json" },
      }),
    );
    let failure: unknown;
    try {
      await exchangeCodeForUser(
        {
          appId: "cli",
          appSecret: "s",
          redirectUri: "http://localhost/cb",
          scope: "",
          fetchImpl: fetchImpl as unknown as typeof fetch,
        },
        "bad",
      );
    } catch (error) {
      failure = error;
    }
    expect(failure).toBeInstanceOf(FeishuOAuthError);
    expect(failure).toMatchObject({
      code: "feishu_oauth_rejected",
      category: "provider_rejection",
      upstreamStatus: 200,
    });
    expect(String(failure)).not.toContain("secret s");
    expect(String(failure)).not.toContain("authorization code bad");
  });
});
