import {
  format,
  isAfter,
  isBefore,
  differenceInMinutes,
  add,
  getTime,
  getMonth,
  getYear,
  isToday,
  isFuture,
  addDays,
} from "date-fns";
import {
  SSXSubscriptionStage,
  DEFAULT_TRIAL_LENGTH_DAYS,
} from "constants/trial";
import {
  PaymentCard,
  OverviewStatus,
  Product,
  Subscription,
  PaymentMethodAction,
  SessionStorageKeys,
  Language,
  FLOW_UPDATE,
  SubscriptionStatus,
  SubscriptionDetails as SubscriptionDetailsType,
} from "types/sales";
import { salesService } from "services";
import { FLAGS, isOn, getFeatureValue, isOff } from "@komi-app/flags-sdk";
import { Maybe } from "@komi-app/sales-sdk";

export interface SubscriptionDetails {
  stage: SSXSubscriptionStage;
  trialRemainingDaysFormatted?: string;
  subscriptionTrialEndAtFormatted?: string;
}

export const getPriceForSubscription = (
  subscription?: Subscription,
  product?: Product,
) => {
  if (!subscription || !product) return;

  const [price] = product.prices[subscription.interval];

  if (!price) {
    return;
  }

  return price;
};

/**
 * Returns the default trial length if the experiment is off, otherwise returns the experiment value.
 * Uses `growthBook` directly as it's also used outside of React tree.
 * @param defaultTrialLength The default trial length to use if the experiment is off
 */
export const getTrialDuration = (defaultTrialLength: number): number => {
  const isTrialLengthExperimentOn = isOn(FLAGS.FEAT_TRIAL_LENGTH_EXPERIMENT);

  if (isTrialLengthExperimentOn) {
    return getFeatureValue(FLAGS.TRIAL_DURATION, defaultTrialLength);
  }

  return defaultTrialLength;
};

export const getSubscriptionOverviewData = (
  subscription: Subscription | null | undefined,
  now: Date = new Date(),
): {
  description?: string;
  status?: OverviewStatus;
  trialRemainingDays?: number;
} => {
  const isFailedPaymentFlowOn = isOn(FLAGS.FEAT_FAILED_PAYMENT_FLOW);
  const isReSubscriptionFlowOff = isOff(FLAGS.FEAT_SB_18_RE_SUBSCRIPTION_FLOW);

  if (!subscription) {
    return {};
  }

  if (isFailedPaymentFlowOn) {
    //subscription cancelled scenarios
    if (subscription.cancel_date) {
      // trial is ongoing and trial end date is in future
      if (
        subscription.trial_end &&
        isAfter(new Date(subscription.trial_end), now)
      ) {
        const remainingDays = getRemainingTrialDays(
          new Date(subscription.trial_end),
          now,
        );

        return {
          description: `Your free trial expires in ${remainingDays} days on ${formatStripeDate(
            subscription.trial_end,
          )}`,
          status: OverviewStatus.FREE_TRIAL,
          trialRemainingDays: remainingDays,
        };
      }

      /*
        Note: commented the below code as we are immediately cancelling the subscription on cancel, 
        i.e it is no longer active till the current subscription cycle.

        Can be uncommented when our cancel behaviour is modified to cancel at the end of current billing cycle.
      */
      // There is active subscription and subscription end date is in the future
      if (
        subscription.current_period_end &&
        isAfter(new Date(subscription.current_period_end), now) &&
        isReSubscriptionFlowOff
      ) {
        return {
          description: `Your subscription was canceled on ${formatStripeDate(
            subscription.cancel_date,
          )}. Your profile URL will remain active until it expires on ${formatStripeDate(
            subscription.current_period_end,
          )}.`,
          status: OverviewStatus.CANCELLED,
        };
      }

      // In all the other scenarios of canceled subscription
      return {
        description: `Your subscription was canceled on ${formatStripeDate(
          subscription.cancel_date,
        )}`,
        status: OverviewStatus.CANCELLED,
      };
    }

    //During the trial
    // Trial is ongoing and trial end is in the future
    if (
      subscription.trial_end &&
      isAfter(new Date(subscription.trial_end), now)
    ) {
      const remainingDays = getRemainingTrialDays(
        new Date(subscription.trial_end),
        now,
      );

      return {
        description: `Your free trial expires in ${remainingDays} ${
          remainingDays === 1 ? "day" : "days"
        } on ${formatStripeDate(subscription.trial_end)}`,
        status: OverviewStatus.FREE_TRIAL,
        trialRemainingDays: remainingDays,
      };
    }

    //Trial ended and subscription is active
    if (
      subscription.trial_end &&
      isBefore(new Date(subscription.trial_end), now) &&
      subscription.status === SubscriptionStatus.ACTIVE
    ) {
      // subscription next billing date is in the future
      if (
        subscription.current_period_end &&
        isAfter(new Date(subscription.current_period_end), now)
      ) {
        return {
          description: `Your next billing date is ${formatStripeDate(
            subscription.current_period_end,
          )}.`,
          status: OverviewStatus.ACTIVE,
        };
      }
      //TODO: modify the description after investigating SB-283
      //Condition when the subscription is active and current_period_end is in past
      if (
        subscription.current_period_end &&
        isBefore(new Date(subscription.current_period_end), now)
      ) {
        //TODO: adding this temporary description for now. Need to modify this later
        return {
          description: `Your subscription is active`,
          status: OverviewStatus.ACTIVE,
        };
      }
    }

    //Trial ended and there is no subscription
    if (
      subscription.trial_end &&
      isBefore(new Date(subscription.trial_end), now) &&
      !subscription.current_period_start
    ) {
      return {
        description: `Your free trial expired on ${formatStripeDate(
          subscription.trial_end,
        )}`,
        status: OverviewStatus.FREE_TRIAL_EXPIRED,
      };
    }

    //Trial ended and subscription is not active
    if (
      subscription.trial_end &&
      isBefore(new Date(subscription.trial_end), now) &&
      subscription.current_period_start &&
      subscription.status !== SubscriptionStatus.ACTIVE
    ) {
      // Incomplete status indicates that there has been a failed payment and subscription has not started
      if (subscription.status === SubscriptionStatus.INCOMPLETE) {
        return {
          description: `You do not have an active subscription due to a failed payment`,
          status: OverviewStatus.INACTIVE,
        };
      } else if (
        subscription.status === SubscriptionStatus.INCOMPLETE_EXPIRED
      ) {
        return {
          description: `Your subscription is expired.`,
          status: OverviewStatus.CANCELLED,
        };
      }
      // Indicates that a subscription has been taken, but the recurring payment failed
      else if (
        subscription.status === SubscriptionStatus.PAST_DUE ||
        subscription.status === SubscriptionStatus.UNPAID
      ) {
        return {
          description: `Your subscription is overdue`,
          status: OverviewStatus.OVERDUE,
        };
      }
      // Indicates that subscription is present on profile but the status is trailing
      /*
        Note: This condition is unreachable once the DB inconsistences are fixed.
              Keeping the status active for now
      */
      else if (subscription.status === SubscriptionStatus.TRIALING) {
        return {
          description: `Your KOMI subscription`,
          status: OverviewStatus.ACTIVE,
        };
      }
    }
  }
  // legacy logic for when FF(FEAT_FAILED_PAYMENT_FLOW)is OFF
  else {
    if (subscription.cancel_date) {
      if (
        subscription.trial_end &&
        isAfter(new Date(subscription.trial_end), now)
      ) {
        const remainingDays = getRemainingTrialDays(
          new Date(subscription.trial_end),
          now,
        );

        return {
          description: `Your free trial expires in ${remainingDays} days on ${formatStripeDate(
            subscription.trial_end,
          )}`,
          status: OverviewStatus.FREE_TRIAL,
          trialRemainingDays: remainingDays,
        };
      }

      if (
        subscription.current_period_end &&
        isAfter(new Date(subscription.current_period_end), now)
      ) {
        return {
          description: `Your subscription was canceled on ${formatStripeDate(
            subscription.cancel_date,
          )}. Your profile URL will remain active until it expires on ${formatStripeDate(
            subscription.current_period_end,
          )}.`,
          status: OverviewStatus.CANCELLED,
        };
      }

      return {
        description: `Your subscription was canceled on ${formatStripeDate(
          subscription.cancel_date,
        )}`,
        status: OverviewStatus.CANCELLED,
      };
    }

    if (
      subscription.current_period_end &&
      isAfter(new Date(subscription.current_period_end), now)
    ) {
      return {
        description: `Your next billing date is ${formatStripeDate(
          subscription.current_period_end,
        )}.`,
        status: OverviewStatus.ACTIVE,
      };
    }

    if (
      subscription.trial_end &&
      isAfter(new Date(subscription.trial_end), now)
    ) {
      const remainingDays = getRemainingTrialDays(
        new Date(subscription.trial_end),
        now,
      );

      return {
        description: `Your free trial expires in ${remainingDays} ${
          remainingDays === 1 ? "day" : "days"
        } on ${formatStripeDate(subscription.trial_end)}`,
        status: OverviewStatus.FREE_TRIAL,
        trialRemainingDays: remainingDays,
      };
    }

    // in the context of profile_builder, trial_end will always be set
    if (
      subscription.trial_end &&
      isBefore(new Date(subscription.trial_end), now)
    ) {
      return {
        description: `Your free trial expired on ${formatStripeDate(
          subscription.trial_end,
        )}`,
        status: OverviewStatus.FREE_TRIAL_EXPIRED,
      };
    }
  }

  return {};
};

export const cardBrandNameMap: { [x: string]: string } = {
  amex: "American Express",
  diners: "Diners Club International",
  discover: "Discover Card",
  jcb: "JCB Co., Ltd.",
  mastercard: "Mastercard",
  unionpay: "UnionPay",
  visa: "Visa",
  unknown: "Unknown",
};

export const isPaymentMethodExpired = (
  paymentCard: PaymentCard,
  now: Date = new Date(),
): boolean => {
  return isBefore(
    new Date(Number(paymentCard.exp_year), Number(paymentCard.exp_month), 0), // month is zero-index however card expiry dates include the month of expiry
    new Date(getYear(now), getMonth(now)), // year/month/1
  );
};

export const formatStripeDate = (dateMs: number): string =>
  format(new Date(dateMs), "MMM dd, yyyy");

export const getRemainingTrialDays = (endDate: Date, now: Date): number => {
  const clamp = (num: number, min: number, max: number): number =>
    Math.min(Math.max(num, min), max);

  const remainingMinutes = differenceInMinutes(endDate, now);
  const remainingDays = remainingMinutes / (24 * 60);

  // Trials could be any length up to 30 days. Clamp to max possible trial length
  return clamp(Math.ceil(remainingDays), 0, DEFAULT_TRIAL_LENGTH_DAYS);
};

export const subscriptionInTrial = (
  subscription?: Subscription,
  now: Date = new Date(),
): boolean => {
  const { stage } = subscriptionTrialForUser(subscription, now);

  return (
    stage === SSXSubscriptionStage.Early || stage === SSXSubscriptionStage.Late
  );
};

export const subscriptionTrialForUser = (
  subscription?: Subscription,
  now: Date = new Date(),
): SubscriptionDetails => {
  if (!subscription) {
    return {
      stage: SSXSubscriptionStage.None,
    };
  }

  if (!subscription.trial_end) {
    return {
      stage: SSXSubscriptionStage.None,
    };
  }

  // subscription has been taken out, do not display trial banner
  if (
    subscription.current_period_end &&
    subscription.status !== SubscriptionStatus.CANCELED
  ) {
    return {
      stage: SSXSubscriptionStage.None,
    };
  }

  if (isBefore(new Date(subscription.trial_end), now)) {
    return {
      stage: SSXSubscriptionStage.Inactive,
    };
  }

  const trialRemainingDays = getRemainingTrialDays(
    new Date(subscription.trial_end),
    now,
  );

  return {
    stage: ((trialRemainingDays: number): SSXSubscriptionStage => {
      const trialDuration = getTrialDuration(DEFAULT_TRIAL_LENGTH_DAYS);

      if (trialRemainingDays > 0) {
        // Don't bother with the late status if the trial duration is less than 2 weeks
        if (trialDuration >= 14 && trialRemainingDays <= 7) {
          return SSXSubscriptionStage.Late;
        }

        return SSXSubscriptionStage.Early;
      }

      return SSXSubscriptionStage.Expired;
    })(trialRemainingDays),
    trialRemainingDaysFormatted: `in ${trialRemainingDays} ${
      trialRemainingDays === 1 ? "day" : "days"
    }`,
    subscriptionTrialEndAtFormatted: formatStripeDate(subscription.trial_end),
  };
};

export enum NextBillingDateOptions {
  REVIEW = "Review",
}

export const getNextBillingDate = (
  subscription?: Subscription,
  now: Date = new Date(),
  options?: NextBillingDateOptions,
): string | null => {
  if (!subscription) {
    const trialDuration = getTrialDuration(DEFAULT_TRIAL_LENGTH_DAYS);
    return formatStripeDate(getTime(add(now, { days: trialDuration })));
  }

  if (subscription.cancel_date && options !== NextBillingDateOptions.REVIEW) {
    return null;
  }

  if (subscription.current_period_end) {
    return formatStripeDate(subscription.current_period_end);
  }

  // in the context of profile_builder, trial_end will always be set
  if (subscription.trial_end) {
    return formatStripeDate(subscription.trial_end);
  }

  return null;
};

export const getCurrentBillingDate = (
  subscription: Subscription,
): Maybe<string> => {
  const now: number = Date.now();
  if (
    subscription.status === SubscriptionStatus.TRIALING &&
    subscription.trial_end
  ) {
    if (isFuture(subscription.trial_end) || isToday(subscription.trial_end)) {
      // If subscription is trailing and the trial_end is in future/now, the subscription is charged on the day of trial_end
      return formatStripeDate(subscription.trial_end);
    } else {
      // If the trial has expired charge immediately
      return formatStripeDate(now);
    }
  } else if (
    subscription.status === SubscriptionStatus.INCOMPLETE ||
    subscription.status === SubscriptionStatus.INCOMPLETE_EXPIRED ||
    subscription.status === SubscriptionStatus.CANCELED ||
    subscription.status === SubscriptionStatus.PAST_DUE ||
    subscription.status === SubscriptionStatus.UNPAID
  ) {
    // The subscription is in above states means it's not paid for so the charge is triggered immediately
    return formatStripeDate(now);
  } else if (
    subscription.status === SubscriptionStatus.ACTIVE &&
    subscription.current_period_end
  ) {
    /* If the current subscription is already paid and when the interval is modified 
     stripe is prorating and issuing the invoice immediately. So due date would be the current day */
    return formatStripeDate(now);
  }

  return void 0;
};

export const getSubscriptionFlowLanguage = (): Language => {
  if (sessionStorage.getItem(SessionStorageKeys.FLOW) === FLOW_UPDATE) {
    return {
      Global: {
        Title: "Update Your Komi Subscription",
      },
      SubscriptionSelection: {
        CTAButtonLabel: "Confirm Selection",
      },
      SubscriptionConfirmation: {
        Title: "Your Subscription Has Been Updated",
      },
    };
  }

  return {
    Global: {
      Title: "Set Up Your Komi Subscription",
    },
    SubscriptionSelection: {
      CTAButtonLabel: "Review Subscription",
    },
    SubscriptionConfirmation: {
      Title: "Thank you for subscribing!",
    },
  };
};

export const getSubscriptionConfirmationMessage = (
  subscription: Subscription | null | undefined,
  now: Date = new Date(),
): string | null => {
  if (!subscription) {
    return null;
  }

  const nextBillingDate = getNextBillingDate(subscription, now);

  if (!nextBillingDate) {
    return null;
  }

  if (subscription.cancel_date) {
    if (
      subscription.trial_end &&
      isAfter(new Date(subscription.trial_end), now)
    ) {
      return `You have successfully activated your Komi subscription. A receipt will be sent to your email. You will not be charged until your free trial expires on ${nextBillingDate}. If you change your mind, you can cancel your subscription any time in Settings.`;
    }

    if (
      subscription.current_period_end &&
      isAfter(new Date(subscription.current_period_end), now)
    ) {
      return `You have successfully activated your Komi subscription. A receipt will be sent to your email. You will not be charged until your existing subscription ends on ${nextBillingDate}. If you change your mind, you can cancel your subscription any time in Settings.`;
    }

    return `You have successfully activated your Komi subscription. A receipt will be sent to your email. If you change your mind, you can cancel your subscription any time in Settings.`;
  }

  if (
    subscription.current_period_end &&
    isAfter(new Date(subscription.current_period_end), now)
  ) {
    // still in trial
    if (
      subscription.trial_end &&
      isAfter(new Date(subscription.trial_end), now)
    ) {
      return `You have successfully updated your Komi subscription. A receipt will be sent to your email. You will not be charged until your free trial ends on ${nextBillingDate}. If you change your mind, you can cancel your subscription any time in Settings.`;
    }

    // out of trial
    return `You have successfully updated your Komi subscription. A receipt will be sent to your email. You will not be charged until your current plan ends on ${nextBillingDate}. If you change your mind, you can cancel your subscription any time in Settings.`;
  }

  if (
    subscription.trial_end &&
    isAfter(new Date(subscription.trial_end), now)
  ) {
    return `You have successfully activated your Komi subscription. A receipt will be sent to your email. You will not be charged until your free trial expires on ${nextBillingDate}. If you change your mind, you can cancel your subscription any time in Settings.`;
  }

  // in the context of profile_builder, trial_end will always be set
  if (
    subscription.trial_end &&
    isBefore(new Date(subscription.trial_end), now)
  ) {
    return `You have successfully activated your Komi subscription. A receipt will be sent to your email. If you change your mind, you can cancel your subscription any time in Settings.`;
  }

  return null;
};

/* Function to check if payment method can be deleted or updated
  * replaces the can-delete API call
  @params 
    * talentProfileId - profile id of current profile being viewed
    * subscriptions - subscription details
*/
export function checkCanDeletePayment(
  talentProfileId: string,
  subscriptions?: SubscriptionDetailsType,
): boolean {
  if (subscriptions) {
    // Find the subscription of the current profile
    const subscriptionKey = Object.keys(subscriptions).find(
      (key) =>
        subscriptions[key]?.profile_builder?.talent_profile_id ===
        talentProfileId,
    );

    // Get the subscription data
    const profileBuilderSubscription =
      subscriptionKey && subscriptions[subscriptionKey]?.profile_builder;

    if (profileBuilderSubscription) {
      const { status, start_date } = profileBuilderSubscription;

      // Check if status is not CANCELED and start_date is existing
      if (status !== SubscriptionStatus.CANCELED && start_date) {
        return false;
      }
    }
  }

  return true;
}
