import { Router } from 'express';
import pick from 'lodash/pick';
import { Op, type WhereOptions } from 'sequelize';

import Joi from 'joi';
import { merge } from 'lodash';
import { PaymentCurrency } from '../store/models/payment-currency';
import { PaymentMethod } from '../store/models/payment-method';
import { authenticate } from '../libs/security';
import { Setting } from '../store/models';
import logger from '../libs/logger';
import { createListParamSchema, getOrder, getWhereFromKvQuery } from '../libs/api';

const router = Router();
const authAdmin = authenticate<Setting>({ component: true, roles: ['owner', 'admin'] });
router.get('/', async (req, res) => {
  const attributes = ['id', 'name', 'symbol', 'decimal', 'logo', 'payment_method_id', 'maximum_precision', 'type'];
  const where: WhereOptions<PaymentMethod> = { livemode: req.livemode, active: true };

  const methods = await PaymentMethod.findAll({
    where,
    order: [['created_at', 'ASC']],
    include: [
      { model: PaymentCurrency, as: 'payment_currencies', where: { active: true }, order: [['created_at', 'ASC']] },
    ],
  });

  methods.forEach((method) => {
    // @ts-ignore
    method.payment_currencies = method.payment_currencies?.map((x) => pick(x, attributes));
  });

  res.json({
    paymentMethods: methods.map((x) => ({
      ...pick(x, ['id', 'name', 'type', 'logo', 'payment_currencies', 'default_currency_id', 'maximum_precision']),
      api_host: x.settings?.arcblock?.api_host,
    })),
    baseCurrency: await PaymentCurrency.findOne({
      where: { is_base_currency: true, livemode: req.livemode },
      attributes,
    }),
  });
});

const donateListSchema = createListParamSchema<{
  active?: boolean;
  mountLocation?: string;
  description?: string;
  componentDid?: string;
}>({
  active: Joi.boolean().empty(''),
  mountLocation: Joi.string().empty(''),
  description: Joi.string().empty(''),
  componentDid: Joi.string().empty(''),
});

router.get('/donate', async (req, res) => {
  try {
    const { page, pageSize, mountLocation, description, componentDid, ...query } = await donateListSchema.validateAsync(
      req.query,
      { stripUnknown: false, allowUnknown: true }
    );

    const where = {
      type: 'donate',
      ...getWhereFromKvQuery(query.q),
    };

    if (typeof query.active === 'boolean') {
      where.active = query.active;
    }
    if (mountLocation) {
      where.mount_location = mountLocation;
    }
    if (description) {
      where.description = description;
    }
    if (componentDid) {
      where.component_did = componentDid;
    }

    const { rows: list, count } = await Setting.findAndCountAll({
      where,
      order: getOrder(req.query, [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']]),
      offset: (page - 1) * pageSize,
      limit: pageSize,
      distinct: true,
    });

    return res.json({ count, list, paging: { page, pageSize } });
  } catch (err) {
    logger.error(err);
    return res.status(400).json({ error: err.message });
  }
});

router.get('/donate/:mountLocationOrId', async (req, res) => {
  try {
    if (!req.params.mountLocationOrId) {
      return res.status(400).json({ error: 'mountLocation is required' });
    }
    const settings = await Setting.findOne({
      where: {
        type: 'donate',
        [Op.or]: [{ mount_location: req.params.mountLocationOrId }, { id: req.params.mountLocationOrId }],
      },
    });
    return res.json(settings);
  } catch (err) {
    logger.error(err);
    return res.status(400).json({ error: err.message });
  }
});

const SettingSchema = Joi.object({
  type: Joi.string().required(),
  mountLocation: Joi.string().required(),
  settings: Joi.object().optional(),
  active: Joi.boolean().optional(),
  description: Joi.string().required(),
  livemode: Joi.boolean().optional(),
  componentDid: Joi.string().optional(),
}).unknown(true);
router.post('/', async (req, res) => {
  try {
    const { error } = SettingSchema.validate(req.body);
    if (error) {
      return res.status(400).json({ error: error.message });
    }
    const { type, mountLocation, settings = {}, active = true, description, livemode = true, componentDid } = req.body;
    const raw = {
      type,
      mount_location: mountLocation,
      description,
      active,
      livemode,
      settings: settings || {},
      component_did: componentDid,
    };
    if (type === 'donate') {
      const defaultSetting = {
        amount: {
          presets: ['1', '5', '10'],
          preset: '5',
          minimum: '0.01',
          maximum: '100',
          custom: true,
        },
        btnText: 'Donate',
        historyType: 'avatar',
      };

      if (settings?.amount) {
        const amountSchema = Joi.object({
          presets: Joi.array()
            .items(Joi.string())
            .when('custom', { is: false, then: Joi.required(), otherwise: Joi.optional() }),
          preset: Joi.string().empty('').optional(),
          minimum: Joi.string().when('custom', { is: true, then: Joi.required() }),
          maximum: Joi.string().when('custom', { is: true, then: Joi.required() }),
          custom: Joi.boolean().required(),
        });

        const { error: amountError } = amountSchema.validate(settings.amount);
        if (amountError) {
          return res.status(400).json({ error: amountError.message });
        }
      }

      raw.settings = merge({}, defaultSetting, settings);
      if (settings.amount) {
        raw.settings.amount = settings.amount;
      }
    }
    if (type === 'notification') {
      const notificationSchema = Joi.object({
        self_handle: Joi.boolean().required().default(false),
        include_events: Joi.array().items(Joi.string()).optional(),
        exclude_events: Joi.array().items(Joi.string()).optional(),
      });
      const { error: notificationError, value: notificationSettings } = notificationSchema.validate(settings);
      if (notificationError) {
        return res.status(400).json({ error: notificationError.message });
      }
      raw.settings = notificationSettings;
    }
    const exist = await Setting.findOne({
      where: {
        type,
        mount_location: mountLocation,
      },
    });
    if (exist) {
      if (exist.component_did !== componentDid) {
        await exist.update({
          component_did: componentDid,
        });
      }
      return res.json(exist);
    }
    const doc = await Setting.create(raw);
    return res.json(doc);
  } catch (err) {
    logger.error(err);
    return res.status(400).json({ error: err.message });
  }
});

const UpdateSettingSchema = Joi.object({
  settings: Joi.object().optional(),
  active: Joi.boolean().optional(),
  description: Joi.string().optional(),
}).unknown(true);
router.put('/:mountLocationOrId', authAdmin, async (req, res) => {
  try {
    const { error } = UpdateSettingSchema.validate(req.body);
    if (error) {
      return res.status(400).json({ error: error.message });
    }
    const { settings = {}, active = true, description } = req.body;
    const raw: {
      active: boolean;
      settings: Record<string, any>;
      description?: string;
    } = {
      active,
      settings: settings || {},
    };
    if (description) {
      raw.description = description;
    }
    const setting = await Setting.findOne({
      where: {
        [Op.or]: [
          {
            id: req.params.mountLocationOrId,
          },
          {
            mount_location: req.params.mountLocationOrId,
          },
        ],
      },
    });
    if (!setting) {
      return res.status(404).json({ error: 'Setting not found' });
    }
    raw.settings = merge({}, setting.settings, settings);
    if (setting.type === 'donate') {
      if (settings?.amount) {
        const amountSchema = Joi.object({
          presets: Joi.array()
            .items(Joi.string())
            .when('custom', { is: false, then: Joi.required(), otherwise: Joi.optional() }),
          preset: Joi.string().empty('').optional(),
          minimum: Joi.string().when('custom', { is: true, then: Joi.required() }),
          maximum: Joi.string().when('custom', { is: true, then: Joi.required() }),
          custom: Joi.boolean().required(),
        });

        const { error: amountError } = amountSchema.validate(settings.amount);
        if (amountError) {
          return res.status(400).json({ error: amountError.message });
        }
        raw.settings.amount = settings.amount;
      }
    }
    if (setting.type === 'notification') {
      const notificationSchema = Joi.object({
        self_handle: Joi.boolean().required().default(false),
        include_events: Joi.array().items(Joi.string()).optional(),
        exclude_events: Joi.array().items(Joi.string()).optional(),
      });
      const { error: notificationError, value: notificationSettings } = notificationSchema.validate(settings);
      if (notificationError) {
        return res.status(400).json({ error: notificationError.message });
      }
      raw.settings = notificationSettings;
    }
    const doc = await setting.update(raw);
    return res.json(doc);
  } catch (err) {
    logger.error(err);
    return res.status(400).json({ error: err.message });
  }
});

router.delete('/:mountLocationOrId', authAdmin, async (req, res) => {
  try {
    const setting = await Setting.findOne({
      where: {
        [Op.or]: [
          {
            id: req.params.mountLocationOrId,
          },
          {
            mount_location: req.params.mountLocationOrId,
          },
        ],
      },
    });
    if (!setting) {
      return res.status(404).json({ error: `Setting ${req.params.mountLocationOrId} not found` });
    }
    await setting.destroy();
    return res.json({ message: `Setting ${req.params.mountLocationOrId} deleted` });
  } catch (err) {
    logger.error(err);
    return res.status(400).json({ error: err.message });
  }
});

router.get('/:mountLocationOrId', authAdmin, async (req, res) => {
  try {
    const setting = await Setting.findOne({
      where: {
        [Op.or]: [
          {
            id: req.params.mountLocationOrId,
          },
          {
            mount_location: req.params.mountLocationOrId,
          },
        ],
      },
    });
    return res.json(setting);
  } catch (err) {
    logger.error(err);
    return res.status(400).json({ error: err.message });
  }
});

export default router;
