/* eslint-disable @typescript-eslint/require-await */
import { Router } from 'express';
import Joi from 'joi';
import pick from 'lodash/pick';

import { CustomError, formatError } from '@blocklet/error';
import { fromTokenToUnit } from '@ocap/util';
import { authenticate } from '../libs/security';
import { createListParamSchema, getOrder, getWhereFromKvQuery, MetadataSchema } from '../libs/api';
import { createIdGenerator, formatMetadata } from '../libs/util';
import { trimDecimals } from '../libs/math-utils';
import { Coupon, PaymentCurrency, PromotionCode } from '../store/models';
import { getRedemptionData } from '../libs/discount/redemption';
import logger from '../libs/logger';

const router = Router();
const auth = authenticate({ component: true, roles: ['owner', 'admin'] });

const PromotionCodeSchema = Joi.object({
  code: Joi.string().optional().max(16),
  description: Joi.string().empty('').max(250).optional(),
  active: Joi.boolean().default(true),
  max_redemptions: Joi.number().integer().positive().optional(),
  expires_at: Joi.number().integer().min(0).optional(),
  verification_type: Joi.string().valid('code', 'nft', 'vc', 'user_restricted').default('code'),
  nft_config: Joi.object({
    addresses: Joi.array().items(Joi.string()).optional(),
    tags: Joi.array().items(Joi.string()).optional(),
    trusted_issuers: Joi.array().items(Joi.string()).optional(),
    trusted_parents: Joi.array().items(Joi.string()).optional(),
    min_balance: Joi.number().integer().positive().default(1),
  }).optional(),
  vc_config: Joi.object({
    roles: Joi.array().items(Joi.string()).optional(),
    trusted_issuers: Joi.array().items(Joi.string()).optional(),
  }).optional(),
  customer_dids: Joi.array().items(Joi.string()).optional(),
  restrictions: Joi.object({
    currency_options: Joi.object()
      .pattern(
        Joi.string(),
        Joi.object({
          minimum_amount: Joi.number().min(0).required(),
        })
      )
      .optional(),
    first_time_transaction: Joi.boolean().optional(),
    minimum_amount: Joi.number().positive().optional(),
    minimum_amount_currency: Joi.string().optional(),
    require_minimum_amount: Joi.boolean().optional(),
  })
    .optional()
    .unknown(true),
  metadata: Joi.object().optional(),
}).unknown(true);

const createCouponSchema = Joi.object({
  id: Joi.string().empty('').optional(),
  name: Joi.string().required().max(64),
  description: Joi.string().empty('').max(250).optional(),
  amount_off: Joi.string().empty('').optional(),
  percent_off: Joi.number().min(0).max(100).optional(),
  currency_id: Joi.string().empty('').optional(),
  duration: Joi.string().valid('once', 'forever', 'repeating').required(),
  duration_in_months: Joi.number().min(1).when('duration', {
    is: 'repeating',
    then: Joi.required(),
    otherwise: Joi.optional(),
  }),
  max_redemptions: Joi.number().min(1).empty('').optional(),
  redeem_by: Joi.number().integer().min(0).empty('').optional(),
  applies_to: Joi.object({
    products: Joi.array().items(Joi.string()),
  }).optional(),
  currency_options: Joi.object()
    .pattern(
      Joi.string(),
      Joi.object({
        amount_off: Joi.number().min(0).required(),
      })
    )
    .optional(),
  metadata: MetadataSchema,
  promotion_codes: Joi.array().items(PromotionCodeSchema).optional(),
})
  .unknown(true)
  .custom((value, helpers) => {
    const { amount_off: amountOff, percent_off: percentOff, currency_id: currencyId } = value;
    const hasAmountOff = amountOff && amountOff.trim() !== '';
    const hasPercentOff = percentOff !== undefined && percentOff !== null;

    if (!hasAmountOff && !hasPercentOff) {
      return helpers.error('any.required', { message: 'Either amount_off or percent_off must be provided' });
    }
    if (hasAmountOff && hasPercentOff) {
      return helpers.error('any.custom', { message: 'Cannot provide both amount_off and percent_off' });
    }

    // If amount_off is provided, currency_id is required
    if (hasAmountOff && (!currencyId || currencyId.trim() === '')) {
      return helpers.error('any.required', { message: 'currency_id is required when amount_off is provided' });
    }

    return value;
  });

const updateCouponSchema = Joi.object({
  name: Joi.string().empty('').max(64).optional(),
  description: Joi.string().empty('').max(250).optional(),
  max_redemptions: Joi.number().min(1).empty('').optional(),
  redeem_by: Joi.number().integer().min(0).empty('').optional(),
  valid: Joi.boolean().empty('').optional(),
  currency_options: Joi.object()
    .pattern(
      Joi.string(),
      Joi.object({
        amount_off: Joi.number().min(0).required(),
      })
    )
    .optional(),
  metadata: MetadataSchema,
}).unknown(true);

// Get expanded coupon with all related information
export async function getExpandedCoupon(id: string) {
  const coupon = await Coupon.findByPk(id, {
    include: [{ model: PaymentCurrency, as: 'currency' }],
  });

  if (coupon) {
    const couponData = coupon.toJSON();

    // Get related promotion codes
    const promotionCodes = await PromotionCode.findAll({
      where: { coupon_id: id },
      order: [['created_at', 'DESC']],
    });

    const appliedProducts = await coupon.getAppliedProducts();

    return {
      ...couponData,
      promotion_codes: promotionCodes,
      applied_products: appliedProducts,
    };
  }

  return null;
}

// Helper function to create coupon with promotion codes
export async function createCouponAndPromotionCodes(payload: any) {
  // 1. Prepare coupon data
  const couponData = pick(payload, [
    'id',
    'name',
    'description',
    'amount_off',
    'percent_off',
    'currency_id',
    'currency_options',
    'duration',
    'max_redemptions',
    'redeem_by',
    'applies_to',
    'metadata',
  ]);

  if (payload.duration === 'repeating' && payload.duration_in_months) {
    (couponData as any).duration_in_months = payload.duration_in_months;
  }

  (couponData as any).livemode = !!payload.livemode;
  (couponData as any).created_via = payload.created_via || 'api';
  couponData.metadata = formatMetadata(couponData.metadata);

  // 2. Validate currency if amount_off is used
  if (couponData.amount_off && !couponData.currency_id) {
    throw new CustomError(400, 'currency_id is required when amount_off is provided');
  }
  // Get all payment currencies for formatting
  const allCurrencies = await PaymentCurrency.findAll();
  if (couponData.currency_id) {
    const currency = await PaymentCurrency.findByPk(couponData.currency_id);
    if (!currency) {
      throw new Error(`Currency ${couponData.currency_id} not found`);
    }
    if (couponData.amount_off) {
      couponData.amount_off = fromTokenToUnit(
        trimDecimals(couponData.amount_off || '0', currency.decimal),
        currency.decimal
      ).toString();
    }
  }

  // Format currency_options if provided
  if ((couponData as any).currency_options) {
    (couponData as any).currency_options = Coupon.formatCurrencyOptions(
      (couponData as any).currency_options,
      allCurrencies
    );
    if (couponData.currency_id && couponData.amount_off && !couponData.currency_options[couponData.currency_id]) {
      couponData.currency_options[couponData.currency_id] = {
        amount_off: couponData.amount_off,
      };
    }
  }

  // 3. Create coupon
  const coupon = await Coupon.insert(couponData);

  // 4. Create promotion codes if provided
  if (Array.isArray(payload.promotion_codes) && payload.promotion_codes.length > 0) {
    const promotionCodes = await Promise.all(
      payload.promotion_codes.map(async (promoCodeData: any) => {
        // Generate code if not provided or ensure it's unique
        if (!promoCodeData.code || !promoCodeData.code.trim()) {
          promoCodeData.code = await createIdGenerator('', 8)();
        } else {
          // Check if provided code is unique
          const existingCode = await PromotionCode.findOne({
            where: { code: promoCodeData.code, livemode: coupon.livemode },
          });
          if (existingCode) {
            throw new Error(`Promotion code '${promoCodeData.code}' already exists`);
          }
        }

        // Format restrictions currency_options if provided
        const formattedRestrictions = PromotionCode.formatRestrictionsCurrencyOptions(
          promoCodeData.restrictions,
          allCurrencies
        );

        return PromotionCode.insert({
          ...promoCodeData,
          restrictions: formattedRestrictions,
          active: true,
          coupon_id: coupon.id,
          livemode: coupon.livemode,
          created_via: coupon.created_via,
          metadata: promoCodeData.metadata,
        });
      })
    );
    return { coupon, promotion_codes: promotionCodes };
  }

  return { coupon, promotion_codes: [] };
}

/**
 * POST /api/coupons
 * Create a new coupon with optional promotion codes
 */
router.post('/', auth, async (req, res) => {
  try {
    logger.info('Creating coupon with body:', req.body);
    const { error, value } = createCouponSchema.validate(req.body);
    if (error) {
      return res.status(400).json({
        error: error.details?.[0]?.message || 'Validation error',
      });
    }

    const result = await createCouponAndPromotionCodes({
      ...value,
      livemode: req.livemode,
      created_via: req.user?.via || 'api',
    });

    logger.info('Coupon and promotion codes created', {
      couponId: result.coupon.id,
      promotionCodesCount: result.promotion_codes.length,
      requestedBy: req.user?.did,
    });

    return res.json(result);
  } catch (error) {
    logger.error('Error creating coupon', {
      error,
      body: req.body,
    });
    return res.status(500).json(formatError(error));
  }
});

// Create pagination schema
const paginationSchema = createListParamSchema<{
  valid?: boolean;
  name?: string;
}>({
  valid: Joi.boolean().empty('').optional(),
  name: Joi.string().empty('').optional(),
});

/**
 * GET /api/coupons
 * List all coupons with pagination and filtering
 */
router.get('/', auth, async (req, res) => {
  const { page, pageSize, valid, name, ...query } = await paginationSchema.validateAsync(req.query, {
    stripUnknown: false,
    allowUnknown: true,
  });

  const where = query.q ? getWhereFromKvQuery(query.q as any) : {};

  if (valid !== undefined) {
    where.valid = !!valid;
  }

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

  if (name) {
    where.name = name;
  }

  const { count, rows } = await Coupon.findAndCountAll({
    where,
    include: [
      {
        model: PaymentCurrency,
        as: 'currency',
        required: false,
      },
    ],
    limit: pageSize,
    offset: (page - 1) * pageSize,
    order: getOrder(req.query, [['created_at', 'DESC']]),
  });

  return res.json({
    count,
    list: rows,
    paging: { page, pageSize },
  });
});

/**
 * GET /api/coupons/:id
 * Retrieve a specific coupon
 */
router.get('/:id', auth, async (req, res) => {
  const coupon = await getExpandedCoupon(req.params.id as string);
  if (!coupon) {
    return res.status(404).json({ error: 'Coupon not found' });
  }
  return res.json(coupon);
});

/**
 * PUT /api/coupons/:id
 * Update a coupon (limited fields can be updated)
 */
router.put('/:id', auth, async (req, res) => {
  const { error, value } = updateCouponSchema.validate(req.body);
  if (error) {
    return res.status(400).json({
      error: error.details?.[0]?.message || 'Validation error',
    });
  }

  if (!req.params.id) {
    return res.status(400).json({ error: 'Coupon ID is required' });
  }

  const coupon = await Coupon.findByPk(req.params.id);

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

  if (req.body.metadata) {
    const { error: metadataError } = MetadataSchema.validate(req.body.metadata);
    if (metadataError) {
      return res.status(400).json({ error: `metadata invalid: ${metadataError.message}` });
    }
  }

  if (coupon.locked) {
    const allowedUpdates = pick(value, ['name', 'metadata', 'description']);
    if (Object.keys(allowedUpdates).length === 0) {
      return res.status(403).json({ error: 'Coupon is locked and cannot be modified' });
    }
    await coupon.update(Coupon.formatBeforeSave(allowedUpdates));
  }

  // Format currency_options if provided
  if (value.currency_options) {
    const allCurrencies = await PaymentCurrency.findAll();
    value.currency_options = Coupon.formatCurrencyOptions(value.currency_options, allCurrencies);
  }

  const isUsed = await coupon.isUsed();

  // Check if coupon is being used
  if (isUsed && !coupon.locked) {
    // Lock the coupon and only allow limited updates
    await coupon.update({ locked: true });
    const allowedUpdates = pick(value, ['name', 'metadata', 'description']);
    if (Object.keys(allowedUpdates).length === 0) {
      return res.status(403).json({
        error: 'Coupon is being used. Only name and metadata can be updated.',
      });
    }
    await coupon.update(Coupon.formatBeforeSave(allowedUpdates));
  } else if (!coupon.locked && !isUsed) {
    // Full update allowed
    await coupon.update(Coupon.formatBeforeSave(value));
  }

  logger.info('Coupon updated', {
    couponId: req.params.id,
    updatedFields: Object.keys(value),
    requestedBy: req.user?.did,
  });

  const doc = await getExpandedCoupon(req.params.id as string);

  return res.json(doc);
});

/**
 * DELETE /api/coupons/:id
 * Delete a coupon (mark as invalid if used, otherwise hard delete)
 */
router.delete('/:id', auth, async (req, res) => {
  try {
    const coupon = await Coupon.findOne({
      where: {
        id: req.params.id,
        livemode: req.livemode,
      },
    });

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

    if (coupon.locked) {
      return res.status(403).json({ error: 'Coupon is locked and cannot be deleted' });
    }

    const isUsed = await coupon.isUsed();

    // Check if coupon is being used
    if (isUsed) {
      // Mark as invalid instead of deleting
      await coupon.update({ locked: true });
      return res.status(403).json({ error: 'Coupon is being used and cannot be deleted' });
    }

    // Hard delete if not used
    await coupon.destroy();

    logger.info('Coupon deleted', {
      couponId: req.params.id,
    });

    return res.json(coupon);
  } catch (error) {
    logger.error('Error deleting coupon', {
      error: error.message,
      couponId: req.params.id,
    });
    return res.status(400).json(formatError(error));
  }
});

router.get('/:id/used', auth, async (req, res) => {
  const coupon = await Coupon.findByPk(req.params.id);
  if (!coupon) {
    return res.status(404).json({ error: 'Coupon not found' });
  }
  const used = await coupon.isUsed();
  if (used && !coupon.locked) {
    await coupon.update({ locked: true });
  }
  return res.json({ used });
});

// Create redemptions pagination schema
const redemptionsSchema = createListParamSchema<{
  type?: string;
}>({
  type: Joi.string().valid('customer', 'subscription').optional(),
});

// Get active redemptions for a coupon with detailed admin analytics
router.get('/:id/redemptions', auth, async (req, res) => {
  try {
    const couponId = req.params.id as string;

    const { page, pageSize, type } = await redemptionsSchema.validateAsync(req.query, {
      stripUnknown: false,
      allowUnknown: true,
    });

    const coupon = await Coupon.findByPk(couponId);
    if (!coupon) {
      return res.status(404).json({ error: 'Coupon not found' });
    }

    const result = await getRedemptionData(
      { coupon_id: couponId },
      { page, pageSize, type: type as 'customer' | 'subscription' | undefined },
      coupon,
      'coupon'
    );

    return res.json(result);
  } catch (error: any) {
    logger.error('Error getting coupon redemptions', {
      error: error.message,
      stack: error.stack,
      couponId: req.params.id,
    });
    return res.status(500).json(formatError(error));
  }
});

export default router;
