import assert from "node:assert/strict";
import { afterEach, beforeEach, describe, it } from "node:test";
import {
  clearAuthTokens,
  getAccessToken,
  getAccessTokenExpiresAt,
  getRefreshToken,
  isAccessTokenExpired,
  storeAuthTokens,
} from "./token.ts";

type CookieEntry = {
  value: string;
};

function installCookieDocument() {
  const cookies = new Map<string, CookieEntry>();
  const previous = Object.getOwnPropertyDescriptor(globalThis, "document");

  Object.defineProperty(globalThis, "document", {
    configurable: true,
    value: {
      get cookie() {
        return Array.from(cookies.entries())
          .map(([name, entry]) => `${name}=${entry.value}`)
          .join("; ");
      },
      set cookie(rawCookie: string) {
        const [nameValue, ...attributes] = rawCookie.split(";").map((item) => item.trim());
        const [name, value] = nameValue.split("=");
        const maxAge = attributes.find((item) => item.toLowerCase().startsWith("max-age="));

        if (maxAge?.toLowerCase() === "max-age=0") {
          cookies.delete(name);
          return;
        }

        cookies.set(name, { value });
      },
    },
  });

  return () => {
    if (previous) {
      Object.defineProperty(globalThis, "document", previous);
      return;
    }

    Reflect.deleteProperty(globalThis, "document");
  };
}

describe("auth token cookies", () => {
  let restoreDocument: () => void;

  beforeEach(() => {
    restoreDocument = installCookieDocument();
  });

  afterEach(() => {
    clearAuthTokens();
    restoreDocument();
  });

  it("stores oauth tokens in cookies with the access token expiry", () => {
    const stored = storeAuthTokens(
      {
        access_token: "access.token",
        refresh_token: "refresh.token",
        expires_in: "120",
      },
      { nowMs: 1_000, requireRefreshToken: true },
    );

    assert.equal(stored.accessToken, "access.token");
    assert.equal(stored.refreshToken, "refresh.token");
    assert.equal(stored.expiresAt, 121_000);
    assert.equal(getAccessToken(), "access.token");
    assert.equal(getRefreshToken(), "refresh.token");
    assert.equal(getAccessTokenExpiresAt(), 121_000);
    assert.equal(isAccessTokenExpired(120_999, 0), false);
    assert.equal(isAccessTokenExpired(121_000, 0), true);
  });

  it("uses exp when the oauth response includes an absolute expiry", () => {
    const stored = storeAuthTokens(
      {
        access_token: "access.from.exp",
        refresh_token: "refresh.from.exp",
        exp: "2025-09-09T06:06:22.155256377Z",
      },
      { nowMs: 1_000, requireRefreshToken: true },
    );

    assert.equal(stored.expiresAt, Date.parse("2025-09-09T06:06:22.155Z"));
  });

  it("keeps the previous refresh token when a refresh response only returns access_token", () => {
    storeAuthTokens(
      {
        access_token: "access.old",
        refresh_token: "refresh.keep",
        expires_in: "120",
      },
      { nowMs: 1_000, requireRefreshToken: true },
    );

    storeAuthTokens(
      {
        access_token: "access.new",
        expires_in: "240",
      },
      { nowMs: 2_000 },
    );

    assert.equal(getAccessToken(), "access.new");
    assert.equal(getRefreshToken(), "refresh.keep");
    assert.equal(getAccessTokenExpiresAt(), 242_000);
  });
});
