import { isValid } from '@arcblock/did';
import { Router } from 'express';
import Joi from 'joi';
import pick from 'lodash/pick';

import { sessionMiddleware } from '@blocklet/sdk/lib/middlewares/session';
import type { WhereOptions } from 'sequelize';
import { createListParamSchema, getOrder, getWhereFromKvQuery, MetadataSchema } from '../libs/api';
import logger from '../libs/logger';
import { authenticate } from '../libs/security';
import { formatMetadata } from '../libs/util';
import { PaymentLink, TPaymentIntentExpanded } from '../store/models';
import { CheckoutSession } from '../store/models/checkout-session';
import { Customer } from '../store/models/customer';
import { PaymentCurrency } from '../store/models/payment-currency';
import { PaymentIntent } from '../store/models/payment-intent';
import { PaymentMethod } from '../store/models/payment-method';
import { Payout } from '../store/models/payout';

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

// list payment intents
const paginationSchema = createListParamSchema<{
  status?: string;
  payment_intent_id?: string;
  customer_id?: string;
  customer_did?: string;
  currency_id?: string;
}>({
  status: Joi.string().empty(''),
  payment_intent_id: Joi.string().empty(''),
  customer_id: Joi.string().empty(''),
  customer_did: Joi.string().empty(''),
  currency_id: Joi.string().empty(''),
});
router.get('/', authMine, async (req, res) => {
  const { page, pageSize, status, livemode, ...query } = await paginationSchema.validateAsync(req.query, {
    stripUnknown: false,
    allowUnknown: true,
  });
  const where = getWhereFromKvQuery(query.q);

  if (status) {
    where.status = status
      .split(',')
      .map((x) => x.trim())
      .filter(Boolean);
  }
  if (query.customer_id) {
    where.customer_id = query.customer_id;
  }
  if (query.customer_did && isValid(query.customer_did)) {
    const customer = await Customer.findOne({ where: { did: query.customer_did } });
    if (customer) {
      where.customer_id = customer.id;
    } else {
      res.json({ count: 0, list: [] });
      return;
    }
  }
  if (query.payment_intent_id) {
    where.payment_intent_id = query.payment_intent_id;
  }

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

  if (typeof livemode === 'boolean') {
    where.livemode = livemode;
  }

  Object.keys(query)
    .filter((x) => x.startsWith('metadata.'))
    .forEach((key: string) => {
      // @ts-ignore
      where[key] = query[key];
    });

  try {
    const { rows: list, count } = await Payout.findAndCountAll({
      where,
      order: getOrder(req.query, [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']]),
      offset: (page - 1) * pageSize,
      limit: pageSize,
      include: [
        { model: PaymentCurrency, as: 'paymentCurrency' },
        { model: PaymentMethod, as: 'paymentMethod' },
        { model: PaymentIntent, as: 'paymentIntent' },
        { model: Customer, as: 'customer' },
      ],
    });

    res.json({ count, list, paging: { page, pageSize } });
  } catch (err) {
    logger.error(err);
    res.json({ count: 0, list: [], paging: { page, pageSize } });
  }
});

const mineRecordPaginationSchema = createListParamSchema<{
  currency_id?: string;
  status?: string;
  customer_id?: string;
}>({
  currency_id: Joi.string().empty(''),
  status: Joi.string().empty(''),
});
router.get('/mine', sessionMiddleware({ accessKey: true }), async (req, res) => {
  try {
    const {
      page,
      pageSize,
      currency_id: currencyId,
      customer_id: customerId,
      status,
    } = await mineRecordPaginationSchema.validateAsync(req.query, {
      stripUnknown: true,
      allowUnknown: true,
    });
    if (!req.user) {
      return res.status(403).json({ code: 'REQUIRE_LOGIN', error: 'Please login to continue' });
    }
    const customer = await Customer.findOne({ where: { did: customerId || req.user?.did } });
    if (!customer) {
      throw new Error(`Customer not found: ${req.user?.did}`);
    }

    const where: WhereOptions = { customer_id: customer.id };
    if (currencyId) {
      where.currency_id = currencyId;
    }
    if (status) {
      where.status = status;
    }

    const { rows: list, count } = await Payout.findAndCountAll({
      where,
      order: getOrder(req.query, [['created_at', 'DESC']]),
      offset: (page - 1) * pageSize,
      limit: pageSize,
      include: [
        { model: PaymentCurrency, as: 'paymentCurrency' },
        { model: PaymentMethod, as: 'paymentMethod' },
        { model: Customer, as: 'customer' },
      ],
    });
    const json = list.map((x) => x.toJSON());
    const result = await Promise.all(
      json.map(async (x) => {
        const paymentIntent = await PaymentIntent.findByPk(x.payment_intent_id, {
          include: [{ model: Customer, as: 'customer' }],
        });
        if (paymentIntent) {
          // @ts-ignore
          x.paymentIntent = paymentIntent.toJSON();
        }
        return x;
      })
    );
    return res.json({ count, list: result, paging: { page, pageSize } });
  } catch (err) {
    logger.error(err);
    return res.status(400).json({ error: err.message });
  }
});

router.get('/:id', authPortal, async (req, res) => {
  try {
    const doc = (await Payout.findOne({
      where: { id: req.params.id },
      include: [
        { model: PaymentCurrency, as: 'paymentCurrency' },
        { model: PaymentMethod, as: 'paymentMethod' },
        { model: PaymentIntent, as: 'paymentIntent' },
        { model: Customer, as: 'customer' },
      ],
    })) as Payout & { paymentIntent: TPaymentIntentExpanded & { customer: Customer } };
    if (!doc) {
      return res.status(404).json(null);
    }
    let checkoutSession = null;
    let paymentLink = null;
    if (doc.payment_intent_id) {
      try {
        checkoutSession = (await CheckoutSession.findOne({
          where: { payment_intent_id: doc.payment_intent_id },
        })) as CheckoutSession & { paymentLink: PaymentLink };
        if (checkoutSession && checkoutSession.payment_link_id) {
          paymentLink = await PaymentLink.findByPk(checkoutSession.payment_link_id);
        }
      } catch (err) {
        logger.error(err);
      }
    }
    const payout = doc.toJSON();
    if (doc.paymentIntent && doc.paymentIntent.customer_id) {
      const paymentCustomer = await Customer.findOne({ where: { id: doc.paymentIntent.customer_id } });
      if (paymentCustomer) {
        // @ts-ignore
        payout.paymentIntent.customer = paymentCustomer.toJSON();
        return res.json({
          ...payout,
          checkoutSession,
          paymentLink,
        });
      }
    }
    return res.json(payout);
  } catch (err) {
    logger.error(err);
    return res.status(500).json({ error: `Failed to get payout: ${err.message}` });
  }
});

// eslint-disable-next-line consistent-return
router.put('/:id', authAdmin, async (req, res) => {
  try {
    const doc = await Payout.findByPk(req.params.id as string);
    if (!doc) {
      return res.status(404).json({ error: 'Payout not found' });
    }

    const raw = pick(req.body, ['metadata']);
    if (raw.metadata) {
      const { error: metadataError } = MetadataSchema.validate(raw.metadata);
      if (metadataError) {
        return res.status(400).json({ error: `metadata invalid: ${metadataError.message}` });
      }
      raw.metadata = formatMetadata(raw.metadata);
    }

    await doc.update(raw);
    res.json(doc);
  } catch (err) {
    logger.error(err);
    res.json(null);
  }
});

export default router;
