import LRU from "lru-cache";
import { UpListing, UpObject } from "upend";
import type {
  Address,
  IJob,
  InEntry,
  IValue,
  ListingResult,
  PutResult,
  VaultInfo,
  StoreInfo,
  AttributeListingResult,
} from "upend/types";
import type { EntityListing } from "./entity";

export const API_URL = "api";

export async function fetchEntity(address: string): Promise<UpObject> {
  const entityFetch = await fetch(`${API_URL}/obj/${address}`);
  const entityResult = (await entityFetch.json()) as EntityListing;
  const entityListing = new UpListing(entityResult.entries);
  return entityListing.getObject(address);
}

export async function fetchEntry(address: string) {
  const response = await fetch(`${API_URL}/raw/${address}`);
  const data = await response.json();
  const listing = new UpListing({ address: data });
  return listing.entries[0];
}

const queryOnceLRU = new LRU<string, UpListing>(128);
const inFlightRequests: { [key: string]: Promise<UpListing> } = {};

export async function queryOnce(query: string): Promise<UpListing> {
  const cacheResult = queryOnceLRU.get(query);
  if (!cacheResult) {
    if (!inFlightRequests[query]) {
      console.debug(`Querying: ${query}`);
      inFlightRequests[query] = new Promise((resolve, reject) => {
        fetch(`${API_URL}/query`, {
          method: "POST",
          body: query,
          keepalive: true,
        })
          .then(async (response) => {
            inFlightRequests[query] = null;
            resolve(new UpListing(await response.json()));
          })
          .catch((err) => reject(err));
      });
    } else {
      console.debug(`Chaining request for ${query}...`);
    }
    return await inFlightRequests[query];
  } else {
    console.debug(`Returning cached: ${query}`);
    return cacheResult;
  }
}

export async function putEntry(entry: InEntry): Promise<PutResult> {
  const response = await fetch(`${API_URL}/obj`, {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(entry),
  });

  return await response.json();
}

export async function putEntityAttribute(
  entity: Address,
  attribute: string,
  value: IValue
): Promise<Address> {
  const response = await fetch(`${API_URL}/obj/${entity}/${attribute}`, {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(value),
  });

  return await response.json();
}

export async function uploadFile(file: File): Promise<PutResult> {
  const formData = new FormData();
  formData.append("file", file);

  const response = await fetch(`${API_URL}/obj`, {
    method: "PUT",
    body: formData,
  });

  if (!response.ok) {
    throw Error(await response.text());
  }

  return await response.json();
}

export async function deleteEntry(address: Address): Promise<void> {
  await fetch(`${API_URL}/obj/${address}`, { method: "DELETE" });
}

export async function getRaw(address: Address, preview = false) {
  return await fetch(`${API_URL}/${preview ? "thumb" : "raw"}/${address}`);
}

export async function refreshVault() {
  return await fetch(`${API_URL}/refresh`, { method: "POST" });
}

export async function nativeOpen(address: Address) {
  return fetch(`${API_URL}/raw/${address}?native=1`);
}

export async function fetchRoots(): Promise<ListingResult> {
  const response = await fetch(`${API_URL}/hier_roots`);
  return await response.json();
}

export async function fetchJobs(): Promise<IJob[]> {
  const response = await fetch(`${API_URL}/jobs`);
  return await response.json();
}

export async function fetchAllAttributes(): Promise<AttributeListingResult> {
  const response = await fetch(`${API_URL}/all/attributes`);
  return await response.json();
}

export async function fetchInfo(): Promise<VaultInfo> {
  const response = await fetch(`${API_URL}/info`);
  return await response.json();
}

export async function fetchStoreInfo(): Promise<{ [key: string]: StoreInfo }> {
  const response = await fetch(`${API_URL}/store`);
  return await response.json();
}

export async function getAddress(input: { attribute: string }): Promise<string> {
  const response = await fetch(
    `${API_URL}/address?attribute=${input.attribute}`
  );
  return await response.json();
}
