import { Router } from 'express';
import Joi from 'joi';

import { Op } from 'sequelize';
import { createListParamSchema, getOrder, getWhereFromKvQuery } from '../libs/api';
import { mergePaginate, type DataSource } from '../libs/pagination';
import logger from '../libs/logger';
import { authenticate } from '../libs/security';
import {
  CreditTransaction,
  Customer,
  CreditGrant,
  Meter,
  MeterEvent,
  Subscription,
  PaymentCurrency,
  PaymentMethod,
  Invoice,
  TCreditTransactionExpanded,
} from '../store/models';

const router = Router();
const authMine = authenticate<CreditTransaction>({ component: true, roles: ['owner', 'admin'], mine: true });
const authPortal = authenticate<CreditTransaction>({
  component: true,
  roles: ['owner', 'admin'],
  record: {
    // @ts-ignore
    model: CreditTransaction,
    field: 'customer_id',
  },
});

const listSchema = createListParamSchema<{
  customer_id?: string;
  subscription_id?: string;
  credit_grant_id?: string;
  meter_id?: string;
  start?: number;
  end?: number;
  source?: string;
  include_grants?: boolean;
}>({
  customer_id: Joi.string().empty(''),
  subscription_id: Joi.string().empty(''),
  credit_grant_id: Joi.string().empty(''),
  meter_id: Joi.string().empty(''),
  start: Joi.number().integer().optional(),
  end: Joi.number().integer().optional(),
  source: Joi.string().empty(''),
  include_grants: Joi.boolean().optional(),
});

router.get('/', authMine, async (req, res) => {
  try {
    const { page, pageSize, ...query } = await listSchema.validateAsync(req.query, { stripUnknown: true });
    const includeGrants = !!query.include_grants;
    const where = getWhereFromKvQuery(query.q);

    if (query.meter_id) {
      where.meter_id = query.meter_id;
    }
    if (query.customer_id) {
      where.customer_id = query.customer_id;
    }

    if (query.subscription_id) {
      where.subscription_id = query.subscription_id;
    }

    if (query.credit_grant_id) {
      where.credit_grant_id = query.credit_grant_id;
    }

    if (query.source) {
      where.source = query.source;
    }

    if (query.start) {
      where.created_at = {
        [Op.gte]: new Date(query.start * 1000),
      };
    }

    if (query.end) {
      where.created_at = {
        [Op.lte]: new Date(query.end * 1000),
      };
    }

    if (query.start && query.end) {
      where.created_at = {
        [Op.between]: [new Date(query.start * 1000), new Date(query.end * 1000)],
      };
    }

    if (includeGrants) {
      if (!query.customer_id) {
        return res.status(400).json({
          error: 'customer_id is required when include_grants=true',
        });
      }

      const orderDirection = query.o === 'asc' ? 'ASC' : 'DESC';

      const transactionSource: DataSource<any> = {
        async count() {
          const count = await CreditTransaction.count({ where });
          return count;
        },
        async fetch(limit, offset) {
          const rows = await CreditTransaction.findAll({
            where,
            limit,
            offset,
            order: [['created_at', orderDirection]],
            include: [
              { model: Customer, as: 'customer', attributes: ['id', 'name', 'email', 'did'] },
              { model: Meter, as: 'meter' },
              { model: Subscription, as: 'subscription', attributes: ['id', 'description', 'status'], required: false },
              { model: CreditGrant, as: 'creditGrant', attributes: ['id', 'name', 'currency_id'] },
              {
                model: MeterEvent,
                as: 'meterEvent',
                attributes: ['id', 'source_data', 'timestamp', 'processed_at', 'created_at'],
                required: false,
              },
            ],
          });
          // Transform transactions
          return rows.map((item: any) => ({
            ...item.toJSON(),
            activity_type: 'transaction',
          }));
        },
        meta: { type: 'database' },
      };

      // Grant where conditions
      const grantWhere: any = {
        customer_id: query.customer_id,
        status: ['granted', 'depleted', 'expired'],
      };
      if (query.subscription_id) {
        grantWhere['metadata.subscription_id'] = query.subscription_id;
      }
      if (query.start) {
        grantWhere.created_at = {
          [Op.gte]: new Date(query.start * 1000),
        };
      }
      if (typeof query.livemode === 'boolean') grantWhere.livemode = query.livemode;

      const grantSource: DataSource<any> = {
        async count() {
          const { count } = await CreditGrant.findAndCountAll({ where: grantWhere });
          return count;
        },
        async fetch(limit, offset) {
          const { rows } = await CreditGrant.findAndCountAll({
            where: grantWhere,
            limit,
            offset,
            order: [['created_at', orderDirection]],
            include: [
              { model: Customer, as: 'customer', attributes: ['id', 'name', 'email', 'did'] },
              {
                model: PaymentCurrency,
                as: 'paymentCurrency',
                attributes: ['id', 'symbol', 'decimal', 'maximum_precision'],
              },
            ],
          });

          // Fetch invoice info for paid grants
          const invoiceIds = rows
            .map((item: any) => item.metadata?.invoice_id)
            .filter((id: string | undefined): id is string => !!id);

          // For first scheduled grants (schedule_seq === 1) without invoice_id, find invoice by subscription_id
          const subscriptionIdsForFirstGrant = rows
            .filter(
              (item: any) =>
                item.metadata?.schedule_seq === 1 && !item.metadata?.invoice_id && item.metadata?.subscription_id
            )
            .map((item: any) => item.metadata.subscription_id);

          const [invoicesByIds, invoicesBySubscription] = await Promise.all([
            invoiceIds.length > 0
              ? Invoice.findAll({
                  where: { id: { [Op.in]: invoiceIds } },
                  attributes: ['id', 'total', 'amount_paid', 'currency_id', 'billing_reason', 'subscription_id'],
                  include: [
                    {
                      model: PaymentCurrency,
                      as: 'paymentCurrency',
                      attributes: ['id', 'symbol', 'decimal', 'type'],
                    },
                  ],
                })
              : [],
            subscriptionIdsForFirstGrant.length > 0
              ? Invoice.findAll({
                  where: {
                    subscription_id: { [Op.in]: subscriptionIdsForFirstGrant },
                    status: 'paid',
                  },
                  attributes: ['id', 'total', 'amount_paid', 'currency_id', 'billing_reason', 'subscription_id'],
                  include: [
                    {
                      model: PaymentCurrency,
                      as: 'paymentCurrency',
                      attributes: ['id', 'symbol', 'decimal', 'type'],
                    },
                  ],
                  order: [['created_at', 'ASC']],
                })
              : [],
          ]);

          const invoiceMap = new Map(invoicesByIds.map((inv: any) => [inv.id, inv]));
          for (const inv of invoicesBySubscription) {
            const subId = inv.subscription_id as string;
            if (subId && !invoiceMap.has(subId)) {
              invoiceMap.set(subId, inv);
            }
          }

          // Transform grants with invoice info
          return rows.map((item: any) => {
            const grantData = item.toJSON();
            const invoice = invoiceMap.get(grantData.metadata.invoice_id);
            return {
              ...grantData,
              activity_type: 'grant',
              invoice: invoice
                ? {
                    total: invoice.total,
                    amount_paid: invoice.amount_paid,
                    currency_id: invoice.currency_id,
                    billing_reason: invoice.billing_reason,
                    subscription_id: invoice.subscription_id,
                    paymentCurrency: invoice.paymentCurrency,
                  }
                : null,
            };
          });
        },
        meta: { type: 'database' },
      };

      // Define sort function
      const sortFn = (a: any, b: any) => {
        const aDate = new Date(a.created_at).getTime();
        const bDate = new Date(b.created_at).getTime();
        return query.o === 'asc' ? aDate - bDate : bDate - aDate;
      };

      // Use mergePaginate
      const result = await mergePaginate([transactionSource, grantSource], { page, pageSize }, sortFn);

      // Load payment currencies for final result
      const paymentCurrencies = await PaymentCurrency.findAll({
        attributes: ['id', 'symbol', 'decimal', 'maximum_precision'],
        where: { type: 'credit' },
      });

      const enhancedData = result.data.map((item) => ({
        ...item,
        paymentCurrency: paymentCurrencies.find(
          (x) => x.id === (item.activity_type === 'grant' ? item.currency_id : item.creditGrant?.currency_id)
        ),
      }));

      return res.json({
        count: result.total,
        list: enhancedData,
        paging: result.paging,
        meta: {
          unified_cash_flow: true,
          includes: ['transaction', 'grant'],
        },
      });
    }

    const { rows: list, count } = await CreditTransaction.findAndCountAll({
      where,
      order: getOrder(query, [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']]),
      offset: (page - 1) * pageSize,
      limit: pageSize,
      include: [
        {
          model: Customer,
          as: 'customer',
          attributes: ['id', 'name', 'email', 'did'],
        },
        {
          model: Meter,
          as: 'meter',
        },
        {
          model: Subscription,
          as: 'subscription',
          attributes: ['id', 'description', 'status'],
          required: false,
        },
        {
          model: CreditGrant,
          as: 'creditGrant',
          attributes: ['id', 'name', 'currency_id'],
        },
        {
          model: MeterEvent,
          as: 'meterEvent',
          attributes: ['id', 'source_data'], // Get source_data from related MeterEvent
          required: false,
        },
      ],
    });

    const paymentCurrencies = await PaymentCurrency.findAll({
      attributes: ['id', 'symbol', 'decimal', 'maximum_precision'],
      where: { type: 'credit' },
    });

    const result = list.map((item) => ({
      ...item.toJSON(),
      activity_type: 'transaction',
      paymentCurrency: paymentCurrencies.find((x) => x.id === (item as any).creditGrant?.currency_id),
    }));

    return res.json({
      count,
      list: result,
      paging: { page, pageSize },
      meta: {
        unified_cash_flow: false,
        includes: ['transaction'],
      },
    });
  } catch (err) {
    logger.error('Error listing credit transactions', err);
    return res.status(400).json({ error: err.message });
  }
});

const summarySchema = Joi.object({
  customer_id: Joi.string().optional(),
  subscription_id: Joi.string().optional(),
  currency_id: Joi.string().optional(),
  meter_id: Joi.string().optional(),
  start: Joi.number().integer().optional(),
  end: Joi.number().integer().optional(),
}).unknown(true);

// get credit transaction summary for customer or subscription
router.get('/summary', authMine, async (req, res) => {
  try {
    const {
      customer_id: customerId,
      subscription_id: subscriptionId,
      currency_id: currencyId,
      meter_id: meterId,
      start,
      end,
    } = await summarySchema.validateAsync(req.query, { stripUnknown: true });

    const result = await CreditTransaction.getUsageSummary({
      customerId,
      subscriptionId,
      currencyId,
      meterId,
      startTime: start ? new Date(start * 1000) : undefined,
      endTime: end ? new Date(end * 1000) : undefined,
    });

    return res.json(result);
  } catch (err) {
    logger.error('get credit transaction summary failed', err);
    return res.status(400).json({ error: err.message });
  }
});

router.get('/:id', authPortal, async (req, res) => {
  try {
    const transaction = (await CreditTransaction.findByPk(req.params.id, {
      include: [
        {
          model: Customer,
          as: 'customer',
          attributes: ['id', 'name', 'email', 'did', 'phone', 'description'],
        },
        {
          model: CreditGrant,
          as: 'creditGrant',
          include: [
            {
              model: PaymentCurrency,
              as: 'paymentCurrency',
            },
          ],
        },
        {
          model: Meter,
          as: 'meter',
          attributes: ['id', 'name', 'event_name', 'unit', 'status', 'description'],
        },
        {
          model: Subscription,
          as: 'subscription',
          attributes: [
            'id',
            'description',
            'status',
            'current_period_start',
            'current_period_end',
            'cancel_at_period_end',
            'canceled_at',
          ],
          required: false,
        },
        {
          model: MeterEvent,
          as: 'meterEvent',
          attributes: ['id', 'source_data', 'timestamp', 'processed_at', 'created_at'],
          required: false,
        },
      ],
    })) as CreditTransaction &
      TCreditTransactionExpanded & { creditGrant?: CreditGrant & { paymentCurrency?: PaymentCurrency } };

    if (!transaction) {
      return res.status(404).json({ error: 'Credit transaction not found' });
    }

    let paymentMethod = null;
    if (transaction.creditGrant?.paymentCurrency?.payment_method_id) {
      paymentMethod = await PaymentMethod.findByPk(transaction.creditGrant.paymentCurrency.payment_method_id);
    }

    return res.json({
      ...transaction.toJSON(),
      paymentMethod,
    });
  } catch (err) {
    logger.error('get credit transaction failed', err);
    return res.status(400).json({ error: err.message });
  }
});

export default router;
