import {FutureFlagOptions} from '../../future/flags';
import {ConfigInterface} from '../base-types';
import {graphqlClientClass, GraphqlClient} from '../clients/admin';
import {BillingError} from '../error';

import {
  AppSubscription,
  BillingCheck,
  BillingCheckParams,
  BillingCheckResponse,
  BillingCheckResponseObject,
  CurrentAppInstallation,
  CurrentAppInstallations,
  OneTimePurchase,
  APP_SUBSCRIPTION_FRAGMENT,
} from './types';
import {convertLineItems} from './utils';

interface SubscriptionMeetsCriteriaParams {
  subscription: AppSubscription;
  isTest?: boolean;
  plans?: string | string[];
}

interface PurchaseMeetsCriteriaParams {
  purchase: OneTimePurchase;
  isTest?: boolean;
  plans?: string | string[];
}

interface InternalParams {
  client: GraphqlClient;
  isTest?: boolean;
  plans?: string | string[];
}

export function check<
  Config extends ConfigInterface,
  Future extends FutureFlagOptions = Config['future'],
>(config: Config): BillingCheck<Future> {
  return async function check<Params extends BillingCheckParams<Future>>(
    params: Params,
  ): Promise<BillingCheckResponse<Params, Future>> {
    if (!config.future?.unstable_managedPricingSupport && !config.billing) {
      throw new BillingError({
        message: 'Attempted to look for purchases without billing configs',
        errorData: [],
      });
    }

    const {session, isTest = true, plans} = params;
    const returnObject =
      (params as BillingCheckParams<{unstable_managedPricingSupport: false}>)
        .returnObject ?? false;

    const GraphqlClient = graphqlClientClass({config});
    const client = new GraphqlClient({session});

    const payments = await assessPayments({client, isTest, plans});

    if (config.future?.unstable_managedPricingSupport || returnObject) {
      return payments as BillingCheckResponse<Params, Future>;
    } else {
      return payments.hasActivePayment as BillingCheckResponse<Params, Future>;
    }
  };
}

export async function assessPayments({
  client,
  isTest,
  plans,
}: InternalParams): Promise<BillingCheckResponseObject> {
  const returnValue: BillingCheckResponseObject = {
    hasActivePayment: false,
    oneTimePurchases: [],
    appSubscriptions: [],
  };

  let installation: CurrentAppInstallation;
  let endCursor: string | null = null;
  do {
    const currentInstallations = await client.request<CurrentAppInstallations>(
      HAS_PAYMENTS_QUERY,
      {variables: {endCursor}},
    );

    installation = currentInstallations.data?.currentAppInstallation!;
    installation.activeSubscriptions.forEach((subscription) => {
      if (subscriptionMeetsCriteria({subscription, isTest, plans})) {
        returnValue.hasActivePayment = true;
        if (subscription.lineItems) {
          subscription.lineItems = convertLineItems(subscription.lineItems);
        }
        returnValue.appSubscriptions.push(subscription);
      }
    });
    installation.oneTimePurchases.edges.forEach(({node: purchase}) => {
      if (purchaseMeetsCriteria({purchase, isTest, plans})) {
        returnValue.hasActivePayment = true;
        returnValue.oneTimePurchases.push(purchase);
      }
    });

    endCursor = installation.oneTimePurchases.pageInfo.endCursor;
  } while (installation?.oneTimePurchases.pageInfo.hasNextPage);

  return returnValue;
}

function subscriptionMeetsCriteria({
  subscription,
  isTest,
  plans,
}: SubscriptionMeetsCriteriaParams): boolean {
  return (
    (typeof plans === 'undefined' || plans.includes(subscription.name)) &&
    (isTest || !subscription.test)
  );
}

function purchaseMeetsCriteria({
  purchase,
  isTest,
  plans,
}: PurchaseMeetsCriteriaParams): boolean {
  return (
    (typeof plans === 'undefined' || plans.includes(purchase.name)) &&
    (isTest || !purchase.test) &&
    purchase.status === 'ACTIVE'
  );
}

const HAS_PAYMENTS_QUERY = `
  ${APP_SUBSCRIPTION_FRAGMENT}
  query appSubscription($endCursor: String) {
    currentAppInstallation {
      activeSubscriptions {
        ...AppSubscriptionFragment
      }
      oneTimePurchases(first: 250, sortKey: CREATED_AT, after: $endCursor) {
        edges {
          node {
            id
            name
            test
            status
          }
        }
        pageInfo {
          hasNextPage
          endCursor
        }
      }
    }
  }
`;
