import { Op } from 'sequelize';
import { BN } from '@ocap/util';
import {
  Discount,
  Customer,
  PromotionCode,
  Coupon,
  Subscription,
  Invoice,
  PaymentCurrency,
  SubscriptionItem,
  Product,
  Price,
  PaymentMethod,
} from '../../store/models';
import { expandLineItems } from '../session';
import { formatCurrencyInfo } from '../util';

interface RedemptionFilters {
  coupon_id?: string;
  promotion_code_id?: string;
  start?: { [Op.lte]: number };
}

interface RedemptionOptions {
  page: number;
  pageSize: number;
  type?: 'customer' | 'subscription';
}

interface SavingsByCurrency {
  amount: string;
  currency: PaymentCurrency;
  formattedAmount: string;
}

interface DiscountRecordData {
  id: string;
  coupon_id?: string;
  promotion_code_id?: string;
  checkout_session_id?: string;
  subscription_id?: string;
  start: number;
  end?: number;
  created_at: Date | string;
}

interface CustomerUsageStats {
  customer: any;
  total_discount_records: number;
  unique_checkout_sessions: Set<string>;
  unique_subscriptions: Set<string>;
  promotion_codes_used: Map<string, any>;
  first_used: Date;
  last_used: Date;
  discount_records: any[];
}

async function getCustomerRedemptions(filters: RedemptionFilters) {
  const customerDiscounts = await Discount.findAll({
    where: filters as any,
    include: [
      {
        model: Customer,
        as: 'customer',
        required: true,
      },
      {
        model: PromotionCode,
        as: 'promotionCode',
        required: false,
      },
      {
        model: Coupon,
        as: 'coupon',
        required: false,
      },
    ],
    order: [['created_at', 'DESC']],
  });

  return buildCustomerUsageMap(customerDiscounts);
}

function buildCustomerUsageMap(customerDiscounts: any[]) {
  const customerUsageMap = new Map<string, CustomerUsageStats>();

  for (const discount of customerDiscounts) {
    const customerId = discount.customer_id;
    const discountWithIncludes = discount as any;

    if (!customerUsageMap.has(customerId)) {
      customerUsageMap.set(customerId, {
        customer: discountWithIncludes.customer,
        total_discount_records: 0,
        unique_checkout_sessions: new Set(),
        unique_subscriptions: new Set(),
        promotion_codes_used: new Map(),
        first_used: discount.created_at,
        last_used: discount.created_at,
        discount_records: [],
      });
    }

    const usage = customerUsageMap.get(customerId)!;
    usage.total_discount_records++;

    const promotionCodeInfo = discountWithIncludes.promotionCode || null;

    usage.discount_records.push({
      id: discount.id,
      checkout_session_id: discount.checkout_session_id,
      subscription_id: discount.subscription_id,
      discount_start: discount.start,
      discount_end: discount.end,
      created_at: discount.created_at,
      promotion_code: promotionCodeInfo,
    });

    // Track unique sessions and subscriptions
    if (discount.checkout_session_id) {
      usage.unique_checkout_sessions.add(discount.checkout_session_id);
    }
    if (discount.subscription_id) {
      usage.unique_subscriptions.add(discount.subscription_id);
    }

    // Track promotion codes used
    if (discountWithIncludes.promotionCode) {
      const promoKey = `${discountWithIncludes.promotionCode.id}|${discountWithIncludes.promotionCode.code}`;
      usage.promotion_codes_used.set(promoKey, promotionCodeInfo);
    }

    // Track usage time range
    if (discount.created_at < usage.first_used) {
      usage.first_used = discount.created_at;
    }
    if (discount.created_at > usage.last_used) {
      usage.last_used = discount.created_at;
    }
  }

  return customerUsageMap;
}

async function getSubscriptionRedemptions(filters: RedemptionFilters) {
  const subscriptionDiscounts = await Discount.findAll({
    where: {
      ...filters,
      subscription_id: { [Op.ne]: null as any },
    },
    include: [
      {
        model: PromotionCode,
        as: 'promotionCode',
        required: false,
      },
      {
        model: Coupon,
        as: 'coupon',
        required: false,
      },
    ],
    order: [['created_at', 'DESC']],
  });

  const subscriptionIds = [...new Set(subscriptionDiscounts.map((d) => d.subscription_id).filter(Boolean))] as string[];

  if (subscriptionIds.length === 0) {
    return [];
  }

  // Query subscriptions with all related data, similar to subscriptions.ts get('/')
  const subscriptions = await Subscription.findAll({
    where: { id: { [Op.in]: subscriptionIds } },
    include: [
      { model: PaymentCurrency, as: 'paymentCurrency' },
      { model: PaymentMethod, as: 'paymentMethod' },
      { model: SubscriptionItem, as: 'items' },
      { model: Customer, as: 'customer' },
    ],
  });

  // Get products and prices for expanding line items, similar to subscriptions.ts
  const products = (await Product.findAll()).map((x) => x.toJSON());
  const prices = (await Price.findAll()).map((x) => x.toJSON());

  // Convert to JSON and expand line items
  const subscriptionDocs = subscriptions.map((x) => x.toJSON());
  subscriptionDocs.forEach((doc) => {
    // @ts-ignore
    expandLineItems(doc.items, products, prices);
  });

  const subscriptionMap = new Map(subscriptionDocs.map((s) => [s.id, s]));

  // Group discounts by subscription for efficient processing
  const discountsBySubscription = new Map<string, any[]>();
  subscriptionDiscounts.forEach((discount) => {
    const subId = discount.subscription_id!;
    if (!discountsBySubscription.has(subId)) {
      discountsBySubscription.set(subId, []);
    }
    discountsBySubscription.get(subId)!.push(discount);
  });

  // Build results with savings calculation
  const results = await Promise.all(
    Array.from(discountsBySubscription.entries()).map(async ([subscriptionId, discounts]) => {
      const subscription = subscriptionMap.get(subscriptionId);
      if (!subscription) return [];

      // Get discount IDs for this subscription
      const discountIds = discounts.map((d) => d.id);

      // Calculate total savings for this subscription
      const totalSavingsByCurrency = await calculateSubscriptionTotalSavingsFromInvoices(subscriptionId, discountIds);

      // Return enhanced subscription data with discount info and savings for each discount
      return discounts.map((discount) =>
        buildSubscriptionResultWithSavings(discount, subscription, totalSavingsByCurrency)
      );
    })
  );

  return results.flat();
}

function buildSubscriptionResultWithSavings(
  discount: Discount & { promotionCode?: PromotionCode | null; coupon?: Coupon | null },
  subscription: Record<string, any>,
  totalSavingsByCurrency: Record<string, SavingsByCurrency>
) {
  const promotionCodeInfo = discount.promotionCode || null;
  const couponInfo = discount.coupon || null;

  return {
    ...subscription, // subscription is already a JSON object with expanded items
    discount_info: {
      discount_id: discount.id,
      checkout_session_id: discount.checkout_session_id,
      discount_start: discount.start,
      discount_end: discount.end,
      discount_created_at: discount.created_at,
      promotion_code: promotionCodeInfo,
      coupon_info: couponInfo,
      total_savings: totalSavingsByCurrency,
    },
  };
}

function paginateResults<T>(results: T[], page: number, pageSize: number) {
  const total = results.length;
  const paginatedResults = results.slice((page - 1) * pageSize, page * pageSize);
  return { results: paginatedResults, total };
}

/**
 * Generic function to calculate total savings by currency from Invoice.total_discount_amounts
 * @param whereCondition Where condition for Invoice query
 * @param discountIds Array of discount IDs to match
 * @returns Object with currency_id as key and savings data
 */
async function calculateTotalSavingsFromInvoices(
  whereCondition: Record<string, any>,
  discountIds: string[]
): Promise<Record<string, SavingsByCurrency>> {
  if (discountIds.length === 0) {
    return {};
  }

  // Query invoices with minimal attributes to reduce data transfer
  const invoices = await Invoice.findAll({
    where: whereCondition,
    attributes: ['id', 'currency_id', 'total_discount_amounts'],
  });

  // Query currencies - simple and straightforward since there won't be many
  const currencies = await PaymentCurrency.findAll({
    attributes: ['id', 'symbol', 'decimal', 'name'],
  });
  const currencyMap = new Map(currencies.map((c) => [c.id, c]));

  // Single pass calculation using BN for precise arithmetic
  const savingsByCurrency: Record<string, BN> = {};

  invoices.forEach((invoice) => {
    if (invoice.total_discount_amounts && Array.isArray(invoice.total_discount_amounts)) {
      const currencyId = invoice.currency_id;

      // Process all discount amounts in one pass
      invoice.total_discount_amounts.forEach((discountAmount: Record<string, any>) => {
        if (discountAmount.discount && discountIds.includes(discountAmount.discount)) {
          const amount = discountAmount.amount || '0';
          const bnAmount = new BN(amount);

          if (savingsByCurrency[currencyId]) {
            savingsByCurrency[currencyId] = savingsByCurrency[currencyId].add(bnAmount);
          } else {
            savingsByCurrency[currencyId] = bnAmount;
          }
        }
      });
    }
  });

  // Convert BN back to string and add formatted amount
  const result: Record<string, SavingsByCurrency> = {};
  Object.entries(savingsByCurrency).forEach(([currencyId, bnAmount]) => {
    const currency = currencyMap.get(currencyId);
    if (!currency) {
      console.warn(`Currency not found for ID: ${currencyId}`);
      return;
    }

    const rawAmount = bnAmount.toString();
    const formattedAmount = formatCurrencyInfo(rawAmount, currency, null, false);

    result[currencyId] = {
      amount: rawAmount,
      currency,
      formattedAmount,
    };
  });

  return result;
}

/**
 * Calculate total savings by currency from Invoice.total_discount_amounts for a customer
 * @param customerId Customer ID
 * @param discountIds Array of discount IDs to match
 * @returns Object with currency_id as key and total amount as value
 */
function calculateCustomerTotalSavingsFromInvoices(
  customerId: string,
  discountIds: string[]
): Promise<Record<string, SavingsByCurrency>> {
  return calculateTotalSavingsFromInvoices({ customer_id: customerId }, discountIds);
}

/**
 * Calculate total savings by currency from Invoice.total_discount_amounts for a subscription
 * @param subscriptionId Subscription ID
 * @param discountIds Array of discount IDs to match
 * @returns Object with currency_id as key and savings data
 */
function calculateSubscriptionTotalSavingsFromInvoices(
  subscriptionId: string,
  discountIds: string[]
): Promise<Record<string, SavingsByCurrency>> {
  return calculateTotalSavingsFromInvoices({ subscription_id: subscriptionId }, discountIds);
}

/**
 * Process discounts and extract statistics
 * @param discounts Array of discount records
 * @returns Processing results with unique tracking
 */
function processDiscountRecords(
  discounts: (Discount & { promotionCode?: PromotionCode | null; coupon?: Coupon | null })[]
) {
  const couponsUsed = new Map<string, string>();
  const promotionCodesUsed = new Map<string, PromotionCode>();
  const uniqueCheckoutSessions = new Set<string>();
  const uniqueSubscriptions = new Set<string>();

  let firstUsed: Date | null = null;
  let lastUsed: Date | null = null;

  const discountRecords = discounts.map((discount) => {
    try {
      const discountData = discount.toJSON() as unknown as DiscountRecordData & { subscription_id?: string };
      const { promotionCode, coupon } = discount;

      // Track unique sessions and subscriptions
      if (discountData.checkout_session_id) {
        uniqueCheckoutSessions.add(discountData.checkout_session_id);
      }
      if (discountData.subscription_id) {
        uniqueSubscriptions.add(discountData.subscription_id);
      }

      // Track unique coupons
      if (discountData.coupon_id) {
        couponsUsed.set(discountData.coupon_id, discountData.coupon_id);
      }

      // Track unique promotion codes
      if (promotionCode) {
        const promoKey = `${promotionCode.id}|${promotionCode.code}`;
        promotionCodesUsed.set(promoKey, promotionCode);
      }

      // Track usage time range
      const createdAtDate = new Date(discountData.created_at);
      if (!firstUsed || createdAtDate < firstUsed) {
        firstUsed = createdAtDate;
      }
      if (!lastUsed || createdAtDate > lastUsed) {
        lastUsed = createdAtDate;
      }

      return {
        id: discountData.id,
        coupon_id: discountData.coupon_id,
        promotion_code_id: discountData.promotion_code_id,
        checkout_session_id: discountData.checkout_session_id,
        subscription_id: discountData.subscription_id,
        discount_start: discountData.start,
        discount_end: discountData.end,
        created_at: discountData.created_at,
        promotion_code: promotionCode,
        coupon,
      };
    } catch (error) {
      console.error('Error processing discount record:', error);
      // Return a fallback record to avoid breaking the entire operation
      return {
        id: discount.id,
        coupon_id: undefined,
        promotion_code_id: undefined,
        checkout_session_id: undefined,
        subscription_id: undefined,
        discount_start: 0,
        discount_end: undefined,
        created_at: discount.created_at,
        promotion_code: null,
        coupon: null,
      };
    }
  });

  return {
    discountRecords,
    couponsUsed: Array.from(couponsUsed.keys()),
    promotionCodesUsed: Array.from(promotionCodesUsed.values()),
    uniqueCheckoutSessions: uniqueCheckoutSessions.size,
    uniqueSubscriptions: uniqueSubscriptions.size,
    firstUsed,
    lastUsed,
  };
}

/**
 * Get comprehensive discount statistics for a subscription
 * @param subscriptionId Subscription ID
 * @returns Subscription discount statistics including total savings, coupons, and promotion codes used
 */
export async function getSubscriptionDiscountStats(subscriptionId: string) {
  const discounts = await Discount.findAll({
    where: { subscription_id: subscriptionId },
    include: [
      {
        model: PromotionCode,
        as: 'promotionCode',
        required: false,
      },
      {
        model: Coupon,
        as: 'coupon',
        required: false,
      },
    ],
    order: [['created_at', 'DESC']],
  });

  if (discounts.length === 0) {
    return {
      subscription_id: subscriptionId,
      total_discount_records: 0,
      total_savings: {},
      coupons_used: [],
      promotion_codes_used: [],
      discount_records: [],
    };
  }

  const discountIds = discounts.map((d) => d.id);

  // Calculate total savings from invoices
  const totalSavings = await calculateSubscriptionTotalSavingsFromInvoices(subscriptionId, discountIds);

  // Process discount records and extract statistics
  const processedData = processDiscountRecords(
    discounts as (Discount & { promotionCode?: PromotionCode | null; coupon?: Coupon | null })[]
  );

  return {
    subscription_id: subscriptionId,
    total_discount_records: discounts.length,
    total_savings: totalSavings,
    coupons_used: processedData.couponsUsed,
    promotion_codes_used: processedData.promotionCodesUsed,
    discount_records: processedData.discountRecords,
  };
}

/**
 * Get comprehensive discount statistics for a customer
 * @param customerId Customer ID
 * @returns Customer discount statistics including total savings, coupons, and promotion codes used
 */
export async function getCustomerDiscountStats(customerId: string) {
  // Get all discounts for this customer
  const discounts = await Discount.findAll({
    where: { customer_id: customerId },
    include: [
      {
        model: PromotionCode,
        as: 'promotionCode',
        required: false,
      },
    ],
    order: [['created_at', 'DESC']],
  });

  if (discounts.length === 0) {
    return {
      customer_id: customerId,
      total_discount_records: 0,
      unique_checkout_sessions: 0,
      unique_subscriptions: 0,
      total_savings: {},
      coupons_used: [],
      promotion_codes_used: [],
      first_used: null,
      last_used: null,
      discount_records: [],
    };
  }

  const discountIds = discounts.map((d) => d.id);

  // Calculate total savings from invoices
  const totalSavings = await calculateCustomerTotalSavingsFromInvoices(customerId, discountIds);

  // Process discount records and extract statistics
  const processedData = processDiscountRecords(
    discounts as (Discount & { promotionCode?: PromotionCode | null; coupon?: Coupon | null })[]
  );

  return {
    customer_id: customerId,
    total_discount_records: discounts.length,
    unique_checkout_sessions: processedData.uniqueCheckoutSessions,
    unique_subscriptions: processedData.uniqueSubscriptions,
    total_savings: totalSavings,
    coupons_used: processedData.couponsUsed,
    promotion_codes_used: processedData.promotionCodesUsed,
    first_used: processedData.firstUsed,
    last_used: processedData.lastUsed,
    discount_records: processedData.discountRecords,
  };
}

export async function getRedemptionData(
  filters: RedemptionFilters,
  options: RedemptionOptions,
  entity: any,
  entityType: 'coupon' | 'promotion_code'
) {
  const { page, pageSize, type } = options;
  const now = Math.floor(Date.now() / 1000);

  // Add time filter
  const queryFilters = {
    ...filters,
    start: { [Op.lte]: now },
  };

  const entityInfo = entity.toJSON();

  if (!type || type === 'customer') {
    const customerUsageMap = await getCustomerRedemptions(queryFilters);

    // Enhance customer data with accurate savings calculation
    const customerUsages = await Promise.all(
      Array.from(customerUsageMap.values()).map(async (usage: any) => {
        // Get discount IDs from this customer's discount records
        const discountIds = usage.discount_records.map((record: any) => record.id).filter(Boolean);

        // Calculate total savings by currency from invoices
        const totalSavingsByCurrency = await calculateCustomerTotalSavingsFromInvoices(usage.customer.id, discountIds);

        return {
          ...usage.customer.toJSON(),
          coupon_usage_stats: {
            total_discount_records: usage.total_discount_records,
            unique_checkout_sessions: usage.unique_checkout_sessions.size,
            unique_subscriptions: usage.unique_subscriptions.size,
            promotion_codes_used: Array.from(usage.promotion_codes_used.values()),
            first_used: usage.first_used,
            last_used: usage.last_used,
            total_savings: totalSavingsByCurrency,
          },
          discount_records: usage.discount_records,
        };
      })
    );

    const { results: paginatedCustomers, total } = paginateResults(customerUsages, page, pageSize);

    return {
      count: total,
      customers: paginatedCustomers,
      paging: { page, pageSize },
      [`${entityType}_info`]: entityInfo,
    };
  }

  if (type === 'subscription') {
    const subscriptionResults = await getSubscriptionRedemptions(queryFilters);
    const { results: paginatedSubscriptions, total } = paginateResults(subscriptionResults, page, pageSize);

    // Add entity info to each subscription result
    const subscriptionsWithEntityInfo = paginatedSubscriptions.map((sub) => ({
      ...sub,
      discount_info: {
        ...sub.discount_info,
        [`${entityType}_info`]: entity.toJSON(),
      },
    }));

    return {
      count: total,
      subscriptions: subscriptionsWithEntityInfo,
      paging: { page, pageSize },
      [`${entityType}_info`]: entityInfo,
    };
  }

  throw new Error('Type parameter must be either "customer" or "subscription"');
}

export type { RedemptionFilters, RedemptionOptions };
