import * as z from "zod";
import { SDK_METADATA } from "../lib/config.js";
import { Security } from "../sdk/models/shared/security.js";

const tokenResponseSchema = z.object({
  access_token: z.string(),
  expires_in: z.number().positive(),
  refresh_token: z.string(),
  token_type: z.string().optional(),
  scope: z.string().optional(),
});

const tolerance = 5 * 60;

export function withAuthorization({
  clientId,
  clientSecret,
  code,
  redirectUri,
  tokenStore,
}: {
  clientId: string,
  clientSecret: string,
  code: string,
  redirectUri: string,
  tokenStore: TokenStore,
}) {
  const url = "https://fathom.video/external/v1/oauth2/token"

  return async (): Promise<Security> => {
    const session = await tokenStore.get();
    const now_seconds = Date.now() / 1000;

    if (session && session.token !== '' && session.expires > now_seconds) {
      return { bearerAuth: session.token };
    }

    if (session?.token === '') {
      try {
        const response = await fetch(url, {
          method: "POST",
          headers: {
            "content-type": "application/x-www-form-urlencoded",
            "user-agent": SDK_METADATA.userAgent,
          },
          body: new URLSearchParams({
            client_id: clientId,
            client_secret: clientSecret,
            code: code,
            redirect_uri: redirectUri,
            grant_type: "authorization_code",
          }),
        });

        if (!response.ok) {
          throw new Error("Unexpected status code: " + response.status);
        }

        const json = await response.json();
        const data = tokenResponseSchema.parse(json);

        await tokenStore.set(
          data.access_token,
          data.refresh_token,
          now_seconds + data.expires_in - tolerance,
        );

        return { bearerAuth: data.access_token };
      } catch (error) {
        throw new Error("Failed to obtain OAuth token: " + error);
      }
    }

    try {
      const response = await fetch(url, {
        method: "POST",
        headers: {
          "content-type": "application/x-www-form-urlencoded",
          "user-agent": SDK_METADATA.userAgent,
        },
        body: new URLSearchParams({
          client_id: clientId,
          client_secret: clientSecret,
          refresh_token: session?.refresh_token || "",
          grant_type: "refresh_token",
        }),
      });

      if (!response.ok) {
        throw new Error("Unexpected status code: " + response.status);
      }

      const json = await response.json();
      const data = tokenResponseSchema.parse(json);

      await tokenStore.set(
        data.access_token,
        data.refresh_token,
        now_seconds + data.expires_in - tolerance,
      );

      return { bearerAuth: data.access_token };
    } catch (error) {
      throw new Error("Failed to obtain OAuth token: " + error);
    }
  };
}

export interface TokenStore {
  get(): Promise<{ token: string; refresh_token: string; expires: number } | undefined>;
  set(token: string, refresh_token: string, expires: number): Promise<void>;
}

export class InMemoryTokenStore implements TokenStore {
  private token = "";
  private refresh_token = "";
  private expires = Date.now();

  constructor() {}

  async get() {
    return { token: this.token, refresh_token: this.refresh_token, expires: this.expires };
  }

  async set(token: string, refresh_token: string, expires: number) {
    this.token = token;
    this.refresh_token = refresh_token;
    this.expires = expires;
  }
}
