import { Notification } from '@blocklet/sdk';
import { Op } from 'sequelize';
import { env } from '@blocklet/sdk/lib/config';
import dayjs from '../libs/dayjs';
import logger from '../libs/logger';
import { Event, Invoice, InvoiceItem, Price, Subscription } from '../store/models';
import { getAdminBillingSubscriptionsUrl, getOwnerDid } from '../libs/util';
import { translate } from '../locales';
import type { BaseEmailTemplate, BaseEmailTemplateType } from '../libs/notification/template/base';
import { getUserLocale } from '../integrations/blocklet/notification';
import { formatTime } from '../libs/time';

function getAppName() {
  return env.appName;
}
interface SubscriptionSummary {
  totalCount: number;
  unreportedCount: number;
  discrepantCount: number;
  abnormalSubscriptions: Array<Subscription>;
  normalCount: number;
  timeRange: {
    start: number;
    end: number;
  };
}

interface MeteringSubscriptionDetectionContext {
  locale: string;
  userDid: string;
  summary: SubscriptionSummary;
  startTimeStr: string;
  endTimeStr: string;
  viewAppUrl: string;
}

export class MeteringSubscriptionDetectionTemplate implements BaseEmailTemplate<MeteringSubscriptionDetectionContext> {
  private timeRange: { start: number; end: number };
  options: {
    timeRange: {
      start: number;
      end: number;
    };
  };

  constructor() {
    const end = dayjs();
    const start = end.subtract(24, 'hours');
    this.timeRange = {
      start: start.unix(),
      end: end.unix(),
    };
    this.options = {
      timeRange: this.timeRange,
    };
  }

  private async getAbnormalSubscriptions(meteringInvoiceIds: string[], meteringSubscriptionIds: string[]) {
    const [unreportedEvents, discrepantEvents] = await Promise.all([
      Event.findAll({
        where: {
          type: 'usage.report.empty',
          created_at: {
            [Op.between]: [this.timeRange.start * 1000, this.timeRange.end * 1000],
          },
          object_id: {
            [Op.in]: meteringSubscriptionIds,
          },
        },
      }),
      Event.findAll({
        where: {
          type: 'billing.discrepancy',
          created_at: {
            [Op.between]: [this.timeRange.start * 1000, this.timeRange.end * 1000],
          },
          object_id: {
            [Op.in]: meteringInvoiceIds,
          },
        },
      }),
    ]);

    const abnormalSubscriptionIds = new Set([
      ...unreportedEvents.map((e) => e.object_id),
      ...discrepantEvents.map((e) => e.data?.object?.subscription_id),
    ]);

    const unreportedSubscriptionIds = Array.from(new Set(unreportedEvents.map((e) => e.object_id)));
    const discrepantSubscriptionIds = Array.from(new Set(discrepantEvents.map((e) => e.data?.object?.subscription_id)));
    const subscriptions = await Subscription.findAll({
      where: {
        id: Array.from(abnormalSubscriptionIds),
      },
    });

    return {
      unreported: unreportedSubscriptionIds,
      discrepant: discrepantSubscriptionIds,
      subscriptions,
    };
  }

  async getContext(): Promise<MeteringSubscriptionDetectionContext> {
    const { start, end } = this.timeRange;
    const invoices = await Invoice.findAll({
      where: {
        created_at: {
          [Op.between]: [start * 1000, end * 1000],
        },
        billing_reason: {
          [Op.notIn]: ['stake', 'slash_stake', 'recharge'],
        },
      },
      include: [
        { model: Subscription, as: 'subscription' },
        { model: InvoiceItem, as: 'lines' },
      ],
    });

    const meteringInvoices: any[] = [];
    await Promise.all(
      invoices.map(async (invoice) => {
        const invoiceJson = invoice.toJSON();
        const prices = (await Price.findAll()).map((x) => x.toJSON());
        // @ts-ignore
        (invoiceJson.lines || []).forEach((item) => {
          item.price = prices.find((x) => x.id === item.price_id);
        });
        // @ts-ignore
        if ((invoiceJson.lines || []).some((line) => line.price?.recurring?.usage_type === 'metered')) {
          meteringInvoices.push(invoice);
        }
      })
    );
    const meteringInvoiceIds = Array.from(new Set(meteringInvoices.map((invoice) => invoice.id)));
    const meteringSubscriptionIds = Array.from(
      new Set(meteringInvoices.map((invoice) => invoice?.subscription_id).filter(Boolean) as string[])
    );

    const abnormalSubscriptions = await this.getAbnormalSubscriptions(meteringInvoiceIds, meteringSubscriptionIds);
    const userDid = await getOwnerDid();
    if (!userDid) {
      throw new Error('get owner did failed');
    }
    const locale = await getUserLocale(userDid);

    const viewAppUrl = getAdminBillingSubscriptionsUrl({ locale, userDid });
    return {
      locale,
      userDid,
      summary: {
        totalCount: meteringSubscriptionIds.length,
        normalCount: Math.max(0, meteringSubscriptionIds.length - abnormalSubscriptions.subscriptions.length),
        unreportedCount: abnormalSubscriptions.unreported.length,
        discrepantCount: abnormalSubscriptions.discrepant.length,
        abnormalSubscriptions: abnormalSubscriptions.subscriptions,
        timeRange: this.timeRange,
      },
      startTimeStr: formatTime(this.timeRange.start * 1000),
      endTimeStr: formatTime(this.timeRange.end * 1000),
      viewAppUrl,
    };
  }

  async getTemplate(): Promise<BaseEmailTemplateType> {
    const context = await this.getContext();
    // @ts-ignore
    const { locale, summary, startTimeStr, endTimeStr, viewAppUrl } = context;
    const body =
      summary.abnormalSubscriptions.length === 0
        ? translate('notification.meteringSubscriptionDetection.healthyBody', locale, {
            totalCount: summary.totalCount,
            startTimeStr,
            endTimeStr,
          })
        : `${translate('notification.meteringSubscriptionDetection.body', locale, {
            totalCount: summary.totalCount,
            startTimeStr,
            endTimeStr,
            normalCount: summary.normalCount,
            abnormalCount: summary.abnormalSubscriptions.length,
            unreportedCount: summary.unreportedCount,
            discrepantCount: summary.discrepantCount,
          })}\n${summary.abnormalSubscriptions.map((sub) => `${sub.description}（${sub.id}）`).join('\n')}`;
    return {
      title: translate('notification.meteringSubscriptionDetection.title', locale, {
        appName: getAppName(),
      }),
      body,
      // @ts-ignore
      actions: [
        viewAppUrl && {
          name: translate('notification.meteringSubscriptionDetection.view', locale),
          title: translate('notification.meteringSubscriptionDetection.view', locale),
          link: viewAppUrl,
        },
      ].filter(Boolean),
    };
  }
}

export async function createMeteringSubscriptionDetection() {
  logger.info('Start metering subscription detection');

  try {
    const template = new MeteringSubscriptionDetectionTemplate();
    const context = await template.getContext();
    const notification = await template.getTemplate();

    await Notification.sendToUser(context.userDid, notification as any, { allowUnsubscribe: false });
    logger.info('Metering subscription detection created', context);
  } catch (error) {
    logger.error('Failed to create metering subscription detection', error);
  }
}
