/* eslint-disable no-bitwise */
/* eslint-disable no-nested-ternary */
/* eslint-disable @typescript-eslint/indent */
import { formatCheckoutHeadlines, formatPrice, getPrefix, getPriceCurrencyOptions } from '@blocklet/payment-react';
import type {
  LineItem,
  PriceRecurring,
  TInvoiceExpanded,
  TLineItemExpanded,
  TPaymentCurrency,
  TPaymentLinkExpanded,
  TPaymentMethodExpanded,
  TPrice,
  TProductExpanded,
  TSubscriptionExpanded,
} from '@blocklet/payment-types';
import { Hasher } from '@ocap/mcrypto';
import { hexToNumber } from '@ocap/util';
import { isEmpty, isObject } from 'lodash';
import cloneDeep from 'lodash/cloneDeep';
import isEqual from 'lodash/isEqual';
import { joinURL } from 'ufo';

import { t } from '../locales/index';

export const MAX_SAFE_AMOUNT = 1000000000;

export const formatProductPrice = (
  { prices, unit_label }: { prices: TPrice[]; unit_label: string },
  currency: TPaymentCurrency,
  locale: string = 'en'
): string => {
  if (prices.length > 1) {
    return t('admin.price.count', locale, { count: prices.length });
  }

  if (prices.length === 1) {
    return formatPrice(prices[0] as TPrice, currency, unit_label, 1, true, locale);
  }

  return t('admin.price.empty', locale);
};

export function getPricingModel(price: TPrice) {
  if (price.billing_scheme === 'tiered') {
    return price.tiers_mode;
  }

  if (price.transform_quantity) {
    return 'package';
  }

  if (price.type === 'recurring' && price.recurring?.usage_type === 'metered' && price.recurring?.meter_id) {
    return 'credit_metered';
  }

  return 'standard';
}

export function getProductByPriceId(products: TProductExpanded[], priceId: string) {
  const product = cloneDeep(products.find((x) => x.prices.some((p) => p.id === priceId)));
  if (product) {
    product.prices = product.prices.filter((x) => x.id === priceId);
  }
  return product;
}

export function getPriceFromProducts(products: TProductExpanded[], priceId: string) {
  const product = products.find((x) => x.prices.some((p) => p.id === priceId));
  return product?.prices.find((x) => x.id === priceId);
}

export function formatPaymentLinkPricing(
  link: TPaymentLinkExpanded,
  currency: TPaymentCurrency,
  locale: string = 'en'
) {
  return formatCheckoutHeadlines(
    link.line_items,
    currency,
    {
      trialInDays: link.subscription_data?.trial_period_days || 0,
      trialEnd: link.subscription_data?.trial_end || 0,
    },
    locale
  );
}

export function getWebhookStatusColor(status: string) {
  switch (status) {
    case 'enabled':
      return 'success';
    case 'disabled':
    default:
      return 'default';
  }
}

export function getDefaultPriceCurrencyIdsFromProduct(product: TProductExpanded): string[] {
  return (
    product.prices
      .find((price) => price.id === product.default_price_id)
      ?.currency_options?.map((x) => x.currency_id) || []
  );
}

// Checks if the currencies of the products in the list match up to a certain index
export function isProductCurrenciesMatched(productA: TProductExpanded, productB: TProductExpanded): boolean {
  const currenciesA = getDefaultPriceCurrencyIdsFromProduct(productA);

  const currenciesB = getDefaultPriceCurrencyIdsFromProduct(productB);

  return (
    currenciesA.every((currencyId) => currenciesB.includes(currencyId)) &&
    currenciesB.every((currencyId) => currenciesA.includes(currencyId))
  );
}

export function isPriceCurrencyAligned(list: LineItem[], products: TProductExpanded[], index: number) {
  const prices = list.map((x) => {
    const product = getProductByPriceId(products, x.price_id);
    const price = product?.prices.find((p) => p.id === x.price_id);
    return price;
  });

  const current = getPriceCurrencyOptions(prices[index] as TPrice)
    .map((x) => x.currency_id)
    .sort();

  for (let i = 0; i < index; i++) {
    const previous = getPriceCurrencyOptions(prices[i] as TPrice)
      .map((x) => x.currency_id)
      .sort();
    if (isEqual(current, previous) === false) {
      return false;
    }
  }

  return true;
}

export function isPriceRecurringAligned(list: LineItem[], products: TProductExpanded[], index: number) {
  const prices = list.map((x) => {
    const product = getProductByPriceId(products, x.price_id);
    const price = product?.prices.find((p) => p.id === x.price_id);
    return price;
  });

  if (prices[index]?.type !== 'recurring') {
    return true;
  }

  const recurring = prices.slice(0, index).find((x) => x?.type === 'recurring')?.recurring;
  if (!recurring) {
    return true;
  }

  return prices.slice(0, index + 1).every((x: any) => {
    if (x.type !== 'recurring') {
      return true;
    }

    // If the interval and interval_count are different, the recurring is not aligned
    if (
      String(recurring?.interval) !== String(x.recurring?.interval) ||
      Number(recurring?.interval_count) !== Number(x.recurring?.interval_count)
    ) {
      return false;
    }

    return true;
  });
}

export function isPriceAligned(list: LineItem[], products: TProductExpanded[], index: number) {
  const currency = isPriceCurrencyAligned(list, products, index);
  const recurring = isPriceRecurringAligned(list, products, index);
  return {
    currency,
    recurring,
    aligned: currency && recurring,
  };
}

export function groupPricingTableItems(items: any[]) {
  const grouped: { [key: string]: any[] } = {};
  items.forEach((x: any, index) => {
    x.index = index;
    if (!grouped[x.product_id]) {
      grouped[x.product_id] = [];
    }
    grouped[x.product_id]?.push(x);
  });

  return Object.entries(grouped);
}

export function isSuccessAttempt(code: number) {
  return code >= 200 && code < 300;
}

export function filterCurrencies(method: TPaymentMethodExpanded, hasSelected: (currency: TPaymentCurrency) => boolean) {
  method.payment_currencies = method.payment_currencies.filter((x) => hasSelected(x));
  return method;
}

export function getSupportedPaymentMethods(
  methods: TPaymentMethodExpanded[],
  hasSelected: (currency: TPaymentCurrency) => boolean
) {
  const filtered = cloneDeep(methods).map((x) => filterCurrencies(x, hasSelected));
  return filtered.filter((x) => x.payment_currencies.length);
}

export function getSupportedPaymentCurrencies(items: TLineItemExpanded[]) {
  const currencies = items.reduce((acc: string[], x: any) => {
    return acc.concat(getPriceCurrencyOptions(x.price).map((c: any) => c.currency_id));
  }, []);
  return Array.from(new Set(currencies));
}

export const debounce = (fun: Function, wait: number) => {
  let timeout: any = null;
  return function inner() {
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(() => {
      fun();
    }, wait);
  };
};

export function canChangePaymentMethod(subscription: TSubscriptionExpanded) {
  return (
    ['active', 'trialing', 'past_due'].includes(subscription.status) &&
    subscription.items.every((x) => getPriceCurrencyOptions(x.price).length > 1)
  );
}

// Steal from https://d3js.org/d3-scale-chromatic/categorical
export function getCategoricalColors(specifier: string = '4e79a7f28e2ce1575976b7b259a14fedc949af7aa1ff9da79c755f') {
  const n = (specifier.length / 6) | 0;
  const colors = new Array(n);
  let i = 0;
  while (i < n) colors[i] = `#${specifier.slice(i * 6, ++i * 6)}`;
  return colors;
}

export function stringToColor(str: string) {
  const colors = getCategoricalColors();

  // @ts-ignore
  const hash = Hasher.SHA3.hash256(str).slice(-12);
  // @ts-ignore
  const index = Math.abs(hexToNumber(hash)) % colors.length;
  return colors[index];
}

export function goBackOrFallback(fallback: string) {
  const prevPage = window.location.href;
  window.history.go(-1);
  setTimeout(() => {
    if (window.location.href === prevPage) {
      window.location.href = joinURL(getPrefix(), fallback);
    }
  }, 500);
}

export function isObjectContent(value: string) {
  try {
    return isObject(JSON.parse(value));
  } catch (err) {
    return false;
  }
}

export function isEmptyExceptNumber(value: any): boolean {
  if (typeof value === 'number') {
    return false;
  }
  return isEmpty(value);
}

export function getRecurringPeriod(recurring: PriceRecurring) {
  const { interval } = recurring;
  const count = +recurring.interval_count || 1;
  const dayInMs = 24 * 60 * 60 * 1000;

  switch (interval) {
    case 'hour':
      return 60 * 60 * 1000;
    case 'day':
      return count * dayInMs;
    case 'week':
      return count * 7 * dayInMs;
    case 'month':
      return count * 30 * dayInMs;
    case 'year':
      return count * 365 * dayInMs;
    default:
      return 0;
  }
}

export function getInvoiceUsageReportStartEnd(invoice: TInvoiceExpanded, showPreviousPeriod: boolean = false) {
  const { subscription, paymentMethod } = invoice;
  const usageReportRange = {
    start: invoice.metadata?.usage_start || invoice.period_start,
    end: invoice.metadata?.usage_end || invoice.period_end,
  };
  if (!subscription || !showPreviousPeriod) {
    return usageReportRange;
  }
  if (invoice?.billing_reason === 'subscription_cancel') {
    return usageReportRange;
  }
  const cycle = getRecurringPeriod(subscription.pending_invoice_item_interval);
  let offset = 0;
  if (['arcblock', 'ethereum', 'base'].includes(paymentMethod.type)) {
    switch (invoice?.billing_reason) {
      case 'subscription_cycle':
        offset = cycle / 1000;
        break;
      default:
        break;
    }
  }
  usageReportRange.start = invoice.period_start - offset;
  usageReportRange.end = invoice.period_end - offset;
  return usageReportRange;
}

export function getAppInfo(address: string): { name: string; avatar: string; type: string; url: string } | null {
  const blockletJson = window.blocklet;
  if (blockletJson) {
    if (blockletJson?.appId === address) {
      return {
        name: blockletJson?.appName,
        avatar: blockletJson?.appLogo,
        type: 'dapp',
        url: blockletJson?.appUrl,
      };
    }
    const appInfo = blockletJson?.componentMountPoints?.find((x: any) => x.appId === address);
    if (appInfo) {
      return {
        name: appInfo.name || '',
        avatar: joinURL(getPrefix(), `.well-known/service/blocklet/logo-bundle/${appInfo.did}`),
        type: 'dapp',
        url: joinURL(getPrefix(), appInfo.mountPoint),
      };
    }
  }
  return null;
}

export function isActive(subscription: TSubscriptionExpanded) {
  return ['active', 'trialing'].includes(subscription.status);
}
export function isWillCanceled(subscription: TSubscriptionExpanded) {
  const now = Date.now() / 1000;
  if (
    ['active', 'trialing'].includes(subscription.status) &&
    subscription.cancel_at_period_end &&
    subscription.current_period_end > now
  ) {
    return true;
  }
  if (subscription.cancel_at && subscription.cancel_at > now) {
    return true;
  }
  return false;
}

export function formatProxyUrl(url: string) {
  if (url.startsWith('/')) {
    return joinURL(window.location.origin, url);
  }
  const proxyUrl = new URL(url);
  if (proxyUrl.host === window.location.host) {
    return url;
  }
  return `${window.location.origin}/.well-known/service/proxy?url=${url}`;
}
