import { Bee, PostageBatch } from "@ethersphere/bee-js";
import { PostageBatchCurated, PostageBatchSummary } from "./model";
import { SwarmConfig } from "./config";
import { DEFAULT_GATEWAY_BATCH_ID, NOT_FOUND_STATUS } from "./constants";

export interface ToolResponse {
  [x: string]: unknown;
  tools?: { [x: string]: unknown; name: string /* other properties */ };
  _meta?: { [x: string]: unknown };
}

export function hexToBytes(hex: string): Uint8Array {
  const bytes = new Uint8Array(hex.length / 2);
  for (let i = 0; i < hex.length; i += 2) {
    bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
  }
  return bytes;
}

export const getBatchSummary = (
  batch: PostageBatch | PostageBatchCurated
): PostageBatchSummary => ({
  stampID:
    typeof batch.batchID === "string" ? batch.batchID : batch.batchID.toHex(),
  usage: batch.usageText,
  capacity: `${batch.remainingSize.toFormattedString()} remaining out of ${batch.size.toFormattedString()}`,
  immutable: batch.immutableFlag,
  ttl: `${batch.duration.represent()} (${batch.duration
    .toEndDate()
    .toDateString()})`,
});

export const getResponseWithStructuredContent = <T>(data: T): ToolResponse => ({
  content: [
    {
      type: "text",
      text: JSON.stringify(data, null, 2),
    },
  ],
  structuredContent: data,
});

export const errorHasStatus = (error: unknown, status: number) => {
  if (typeof error === "object" && error !== null && "status" in error) {
    return error.status === status;
  }

  return false;
};

export const getErrorMessage = (error: unknown) => {
  if (
    typeof error === "object" &&
    error !== null &&
    "responseBody" in error &&
    typeof error.responseBody === "object" &&
    error.responseBody !== null &&
    "message" in error.responseBody
  ) {
    return error.responseBody.message as string;
  }

  return "";
};

export const runWithTimeout = async <T>(
  asyncAction: Promise<T>,
  timeout: number
): Promise<[unknown, boolean]> => {
  let hasTimedOut = false;

  const timeoutPromise = new Promise((resolve) =>
    setTimeout(() => {
      hasTimedOut = true;
      resolve(true);
    }, timeout)
  );

  const response = await Promise.race([asyncAction, timeoutPromise]);

  return [response, hasTimedOut];
};

export const getUploadPostageBatchId = async (
  argsPostageBatchId: string | undefined,
  bee: Bee,
  config: SwarmConfig,
): Promise<string> => {
    let postageBatchId = argsPostageBatchId;
    const autoAssignStamp = config.autoAssignStamp ?? true;
    let maxRemainingSize = 0;

    if (!postageBatchId && !autoAssignStamp) {
      throw new Error('No postageBatchId was provided. Please repeat the prompt and also specify the usable postage batch id.');
    } else if (!postageBatchId) {
      try {
        const rawPostageBatches = await bee.getPostageBatches();

        rawPostageBatches.forEach((batch) => {
          if (!batch.usable) {
            return;
          }

          const remainingSize = batch.remainingSize.toBytes();

          if (remainingSize > maxRemainingSize) {
            maxRemainingSize = remainingSize;
            postageBatchId = batch.batchID.toHex();
          }
        });
      } catch (error) {
        if (errorHasStatus(error, NOT_FOUND_STATUS)) {
          postageBatchId = DEFAULT_GATEWAY_BATCH_ID;
        } else {
          throw new Error('Retrieval of postage batches failed.');
        }
      }
    }

    if (!postageBatchId) {
      throw new Error('There is no usable postage batch with capacity.');
    }

    return postageBatchId!;
};

const dateUnits: Record<string, number | undefined> = {
  ms: 1,
  milli: 1,
  millis: 1,
  millisecond: 1,
  milliseconds: 1,
  s: 1000,
  sec: 1000,
  second: 1000,
  seconds: 1000,
  m: 60_000,
  min: 60_000,
  minute: 60_000,
  minutes: 60_000,
  h: 3_600_000,
  hour: 3_600_000,
  hours: 3_600_000,
  d: 86_400_000,
  day: 86_400_000,
  days: 86_400_000,
  w: 604_800_000,
  week: 604_800_000,
  weeks: 604_800_000,
  month: 2_592_000_000,
  months: 2_592_000_000,
  y: 31_536_000_000,
  year: 31_536_000_000,
  years: 31_536_000_000,
};

// From bee.js
export function makeDate(numberWithUnit: string): number {
  const number = parseFloat(numberWithUnit);
  if (isNaN(number)) {
    throw Error("makeDate got NaN for input");
  }
  const unit = numberWithUnit
    .replace(/^-?[0-9.]+/, "")
    .trim()
    .toLowerCase();
  const multiplier = dateUnits[unit];
  if (!multiplier) {
    throw Error(`Unknown unit: "${unit}"`);
  }
  return number * multiplier;
}
