import createClient from 'openapi-fetch';
import type { FetchResponse } from 'openapi-fetch';
import type { HttpMethod, PathsWithMethod } from 'openapi-typescript-helpers';
import { useMemo } from 'react';
import { v4 as uuidv4 } from 'uuid';

import { paths } from '../utils/gen';

let sunoSessionId: string | null = null;
let sunoSessionIdTTL: number = new Date().getTime();

const deviceId: string | null = ('default-' + uuidv4()).replace(/"/g, '');
export function getDeviceId() {
  // we are using the segment anonymous id as the device id to identify a browser for web
  if (typeof window === 'undefined' || !window.localStorage) {
    return deviceId;
  }
  const segmentAnonymousId =
    localStorage.getItem('ajs_anonymous_id') || uuidv4();
  localStorage.setItem('ajs_anonymous_id', segmentAnonymousId);
  return segmentAnonymousId.replace(/"/g, '');
}

async function getBrowserToken() {
  try {
    const browserData = { timestamp: new Date().getTime() };
    // Making this semi-structured so we can pass some sealed client intel
    // in a future PR.
    return JSON.stringify({ token: btoa(JSON.stringify(browserData)) });
  } catch (e) {
    return JSON.stringify({ token: 'error' });
  }
}

export function getSunoSessionId() {
  const currentTimestamp = new Date().getTime();
  if (currentTimestamp >= sunoSessionIdTTL!) return null;
  return sunoSessionId;
}

function updateSunoSessionId(res: Response) {
  const currentTimestamp = new Date().getTime();
  const newSessionId = res.headers.get('Session-Id');
  if (newSessionId) {
    if (newSessionId != sunoSessionId) {
      sunoSessionId = newSessionId;
      sunoSessionIdTTL = currentTimestamp + 15 * 60 * 1000;
    } else if (currentTimestamp >= sunoSessionIdTTL!) {
      sunoSessionId = newSessionId;
      sunoSessionIdTTL = currentTimestamp + 15 * 60 * 1000;
    }
  }
}

export function clearSunoSessionId() {
  sunoSessionId = null;
  sunoSessionIdTTL = new Date().getTime();
}

export function useApiClient() {
  const apiBase = process.env.NEXT_PUBLIC_API_BASE;
  const deviceId = getDeviceId();
  return useMemo(
    () =>
      createClient<paths>({
        baseUrl: apiBase || '/',
        fetch: async (url: any, options?: RequestInit) => {
          const browserToken = await getBrowserToken();
          return fetch(url, {
            ...options,
            headers: {
              ...options?.headers,
              Authorization: `Bearer ${process.env.NEXT_PUBLIC_API_USER_TOKEN}`,
              ...(deviceId ? { 'Device-Id': deviceId } : {}),
              ...(browserToken ? { 'Browser-Token': browserToken } : {}),
            },
          }).then(res => {
            if (
              res.status === 429 &&
              res.url.endsWith('/update_reaction_type/')
            ) {
              alert(
                'Rate limit exceeded. Please wait before retrying actions.'
              );
            }
            updateSunoSessionId(res);
            return res;
          });
        },
      }),
    [apiBase, deviceId]
  );
}

export type ApiClient = ReturnType<typeof useApiClient>;

/**
 * Helper type to get the shape of a successful API response
 */
export type ApiResponse<
  M extends HttpMethod | Uppercase<HttpMethod>,
  P extends PathsWithMethod<paths, Lowercase<M>>,
> =
  Lowercase<M> extends infer T
    ? T extends keyof paths[P]
      ? T extends HttpMethod
        ? P extends PathsWithMethod<paths, T>
          ? T extends keyof paths[P]
            ? NonNullable<FetchResponse<paths[P][T] & Record<string, any>, any, 'application/json'>['data']>
            : unknown
          : never
        : never
      : never
    : never;
