import notification from 'utils/notification'
import {
  CheckoutInvoice,
  CustomerWithDetails,
  Interval,
  Product,
  ProductKey,
  SalesSetupConfirmationFailure,
  SalesSoftFail,
  SalesSuccess,
  Subscription,
  SubscriptionDetails,
  TaxAmount,
  Price
} from 'types/sales'
import { Maybe } from 'types/utilities'
import { CancelFeedback } from '@komi-app/components'
import Sales, {
  Subscription as SubscriptionFromSDK,
  CustomerIntent,
  TaxAmount as TaxAmountFromSDK,
  SubscriptionDetails as SubscriptionDetailsFromSDK,
  ProductKey as ProductKeyFromSDK,
  Interval as IntervalFromSDK,
  Price as PriceFromSDK,
  ConfirmStripePaymentMethodIntentError,
  ChargeLastInvoiceError,
  DataStripeAccountStatus,
  DataConnectedAccount,
  PaymentProvider,
  DataStripeAccountLink,
  StripeAccountLinkBody
} from '@komi-app/sales-sdk'
import { getSDKConfig } from 'config'
import { SalesServiceInterface } from './SalesService'

export default class SalesServiceV1 implements SalesServiceInterface {
  private sales: Sales
  constructor() {
    this.sales = new Sales(getSDKConfig('sales'))
  }

  init(profileId: string) {
    this.sales.init(profileId)
  }

  getCustomerInfo = async (): Promise<Maybe<CustomerWithDetails>> => {
    try {
      const {
        firstname,
        lastname,
        email,
        userId,
        provider,
        pricing,
        paymentMethod,
        subscriptions,
        coupon
      } = await this.sales.getMyCustomerDetails()

      const customer: CustomerWithDetails = {
        firstname,
        lastname,
        email,
        user_id: userId,
        provider,
        ...(pricing && {
          pricing: {
            country: pricing?.country,
            region: pricing?.region
          }
        }),
        ...(paymentMethod && {
          paymentMethod: {
            card: {
              brand: paymentMethod.card?.brand,
              country: paymentMethod.card?.country,
              exp_month: paymentMethod.card?.expMonth,
              exp_year: paymentMethod.card?.expYear,
              last4: paymentMethod.card?.last4
            }
          }
        }),
        ...(subscriptions && {
          subscriptions: this.convertSubscriptionDetails(subscriptions, userId)
        }),
        ...(coupon && {
          coupon: {
            id: coupon.id,
            duration: coupon.duration,
            duration_in_months: coupon.durationInMonths,
            amount_off: coupon.amountOff,
            product_key: coupon.productKey,
            message: coupon.message,
            label: coupon.label
          }
        })
      }

      return customer
    } catch (err) {
      return void notification.error({
        message: 'An error occurred while retrieving customer details.'
      })
    }
  }

  createSetup = async (): Promise<Maybe<string>> => {
    try {
      const { clientSecret }: CustomerIntent =
        await this.sales.setupMyStripePaymentMethodIntent()

      const client_secret = clientSecret || ''

      return client_secret
    } catch (err) {
      return void notification.error({
        message: 'An error occurred while setting up payment method.'
      })
    }
  }

  confirmSetup = async (
    setup_id: string
  ): Promise<
    Maybe<SalesSuccess | SalesSetupConfirmationFailure | SalesSoftFail>
  > => {
    try {
      await this.sales.confirmMyStripePaymentMethodIntent(setup_id)

      return { success: true }
    } catch (err) {
      const error = err as ConfirmStripePaymentMethodIntentError
      const statusCode = error.response.status
      if (statusCode === 409) {
        return {
          success: null,
          status: statusCode
        } as SalesSoftFail
      }
      notification.error({
        message: 'An error occurred while confirming payment method.'
      })
      return {
        success: false,
        status: statusCode,
        message: error.message
      }
    }
  }

  chargePayment = async (
    productKey: ProductKey
  ): Promise<Maybe<SalesSuccess | SalesSetupConfirmationFailure>> => {
    try {
      await this.sales.chargeLastInvoice(productKey)

      return { success: true }
    } catch (err) {
      const error = err as ChargeLastInvoiceError
      const statusCode = error.response.status
      return {
        success: false,
        status: statusCode,
        message: error.message
      }
    }
  }

  getProduct = async (product_key: ProductKey): Promise<Maybe<Product>> => {
    try {
      const { key, name, priceKey, prices } =
        await this.sales.getProduct(product_key)

      const product = {
        key,
        name,
        price_key: priceKey,
        prices: this.convertPrices(prices)
      }

      return product
    } catch (err) {
      return void notification.error({
        message: 'An error occurred while retrieving product'
      })
    }
  }

  cancelSubscription = async (
    product_key: ProductKey
  ): Promise<Maybe<boolean>> => {
    try {
      await this.sales.cancelStripeSubscription(product_key)

      return true
    } catch (err) {
      return void notification.error({
        message: 'An error occurred while canceling subscription'
      })
    }
  }

  checkoutSubscription = async (
    product_key: ProductKey,
    interval: Interval
  ): Promise<Maybe<CheckoutInvoice>> => {
    try {
      const {
        amountDue,
        currency,
        subtotal,
        total,
        totalDiscountAmounts,
        totalTaxAmounts
      } = await this.sales.checkoutStripeSubscription(product_key, interval)

      const convertedTaxAmounts: TaxAmount[] =
        this.convertTaxAmounts(totalTaxAmounts)

      const checkout = {
        amount_due: amountDue,
        currency,
        subtotal,
        total,
        total_discount_amounts: totalDiscountAmounts,
        total_tax_amounts: convertedTaxAmounts
      }

      return checkout
    } catch (err) {
      return void notification.error({
        message: 'An error occurred during checkout'
      })
    }
  }

  cancelSubscriptionFeedback = async (
    product_key: ProductKey,
    feedback: CancelFeedback
  ): Promise<Maybe<boolean>> => {
    try {
      await this.sales.cancelStripeSubscription(product_key, {
        reason: feedback.reason || '',
        description: feedback.description || ''
      })

      return true
    } catch (err) {
      return void notification.error({
        message: 'An error occurred while submitting feedback'
      })
    }
  }

  createSubscription = async (
    product_key: ProductKey,
    intervalParam: Interval
  ): Promise<Maybe<Subscription>> => {
    try {
      const {
        profileId,
        productKey,
        userId,
        priceKey,
        interval,
        countryCode,
        regionCode,
        status,
        startDate,
        endDate,
        cancelDate,
        trialStart,
        trialEnd,
        currentPeriodStart,
        currentPeriodEnd
      } = await this.sales.createStripeSubscription(product_key, intervalParam)

      const subscription = {
        talent_profile_id: profileId,
        product_key: productKey,
        user_id: userId,
        price_key: priceKey,
        interval: interval,
        country_code: countryCode,
        region_code: regionCode,
        status: status,
        start_date: startDate,
        end_date: endDate,
        cancel_date: cancelDate,
        trial_start: trialStart,
        trial_end: trialEnd,
        current_period_start: currentPeriodStart,
        current_period_end: currentPeriodEnd
      }

      return subscription
    } catch (err) {
      return void notification.error({
        message: 'An error occurred while subscribing'
      })
    }
  }

  updateSubscription = async (
    product_key: ProductKey,
    intervalParam: Interval
  ): Promise<Maybe<Subscription>> => {
    try {
      const {
        profileId,
        productKey,
        userId,
        priceKey,
        interval,
        countryCode,
        regionCode,
        status,
        startDate,
        endDate,
        cancelDate,
        trialStart,
        trialEnd,
        currentPeriodStart,
        currentPeriodEnd
      }: SubscriptionFromSDK = await this.sales.updateStripeSubscription(
        product_key,
        intervalParam
      )

      const updatedSubscription = {
        talent_profile_id: profileId,
        product_key: productKey,
        user_id: userId,
        price_key: priceKey,
        interval: interval,
        country_code: countryCode,
        region_code: regionCode,
        status: status,
        start_date: startDate,
        end_date: endDate,
        cancel_date: cancelDate,
        trial_start: trialStart,
        trial_end: trialEnd,
        current_period_start: currentPeriodStart,
        current_period_end: currentPeriodEnd
      }
      return updatedSubscription
    } catch (err) {
      return void notification.error({
        message: 'An error occurred while updating subscription'
      })
    }
  }

  deletePaymentMethod = async (): Promise<any> => {
    try {
      await this.sales.removeMyStripePaymentMethod()
      return true
    } catch (err) {
      return void notification.error({
        message: 'An error occurred while deleting payment'
      })
    }
  }

  /**
   * Converts prices from the new format to the old format.
   *
   * @param {Record<IntervalFromSDK, PriceFromSDK[]>} prices - Prices in the new format.
   * @returns {Record<Interval, Price[]>} - Prices in the old format.
   */
  convertPrices = (
    prices: Record<IntervalFromSDK, PriceFromSDK[]>
  ): Record<Interval, Price[]> => {
    const convertedPrices: Record<Interval, Price[]> = {} as Record<
      Interval,
      Price[]
    >

    // Iterate through each interval in the new prices object
    Object.keys(prices).forEach(intervalKey => {
      const interval = intervalKey as Interval
      const newPrices = prices[intervalKey as IntervalFromSDK]

      // Convert each price in the new prices array to the old format
      const convertedIntervalPrices = newPrices.map(newPrice => {
        return {
          product_key: newPrice.productKey,
          price_key: newPrice.priceKey,
          interval: newPrice.interval,
          amount: newPrice.amount,
          currency: newPrice.currency,
          tax_included: newPrice.taxIncluded
        }
      })

      // Assign the converted prices to the corresponding interval in the old format
      convertedPrices[interval] = convertedIntervalPrices
    })

    return convertedPrices
  }

  /**
   * Converts an array of tax amounts from the new SDK format to the legacy format.
   *
   * @param {TaxAmountFromSDK[]} taxAmounts - The array of tax amounts in the new SDK format.
   * @returns {TaxAmount[]} - An array of tax amounts in the legacy format.
   */
  convertTaxAmounts = (taxAmounts: TaxAmountFromSDK[]): TaxAmount[] => {
    return taxAmounts.map(taxAmount => ({
      amount: taxAmount.amount,
      inclusive: taxAmount.inclusive,
      tax_rate: {
        active: taxAmount.taxRate.active,
        description: taxAmount.taxRate.description,
        display_name: taxAmount.taxRate.displayName,
        inclusive: taxAmount.taxRate.inclusive,
        percentage: taxAmount.taxRate.percentage
      }
    }))
  }

  /**
   * Converts subscription details from new format(from SDK) to legacy format.
   *
   * @param {SubscriptionDetailsFromSDK} newTypedSubscriptions - The subscription details in the new format.
   * @param {number} userId - The user ID.
   * @returns {SubscriptionDetails} - The converted subscription details in the legacy format.
   */
  convertSubscriptionDetails = (
    newTypedSubscriptions: SubscriptionDetailsFromSDK,
    userId: number
  ) => {
    const legacyTypedSubscription: SubscriptionDetails = {}

    // Iterate through each key(profileId) in the new subscription details object
    for (const key in newTypedSubscriptions) {
      if (Object.prototype.hasOwnProperty.call(newTypedSubscriptions, key)) {
        const productTypes = newTypedSubscriptions[key]

        legacyTypedSubscription[key] = {}

        // Iterate through each product key in the product types object
        for (const productKey in productTypes) {
          if (Object.prototype.hasOwnProperty.call(productTypes, productKey)) {
            const subscription = productTypes[
              productKey as ProductKeyFromSDK
            ] as SubscriptionFromSDK

            // Convert the subscription object to the old format and assign it to the legacy object
            legacyTypedSubscription[key][productKey as ProductKey] = {
              talent_profile_id: subscription.profileId,
              product_key: subscription.productKey,
              user_id: userId,
              price_key: subscription.priceKey,
              interval: subscription.interval,
              country_code: subscription.countryCode,
              region_code: subscription.regionCode,
              status: subscription.status,
              start_date: subscription.startDate,
              end_date: subscription.endDate,
              cancel_date: subscription.cancelDate,
              trial_start: subscription.trialStart,
              trial_end: subscription.trialEnd,
              current_period_start: subscription.currentPeriodStart,
              current_period_end: subscription.currentPeriodEnd
            }
          }
        }
      }
    }

    return legacyTypedSubscription
  }

  /**
   * Placeholder method from the Legacy sales service.
   * Can be deleted when we completely move to new sales integration
   */
  canDeletePaymentMethod = async (): Promise<Maybe<boolean>> => {
    console.log('this: ', this)
    throw Error('Will not be implemented')
  }

  async getQuota(
    ...params: Parameters<(typeof Sales)['prototype']['getQuota']>
  ) {
    return await this.sales.getQuota(...params)
  }

  getStripeAccountStatus = async (): Promise<DataStripeAccountStatus> => {
    return await this.sales.getStripeAccountStatus()
  }

  getOrCreateConnectedAccount = async (
    email: string | undefined,
    provider: PaymentProvider = PaymentProvider.STRIPE
  ): Promise<DataConnectedAccount> => {
    return await this.sales.getOrCreateConnectedAccount(provider, email)
  }

  generateStripeAccountLink = async (
    payload: StripeAccountLinkBody
  ): Promise<DataStripeAccountLink> => {
    return await this.sales.generateStripeAccountLink(payload)
  }
}
