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

import { createIdGenerator, formatMetadata } from '../libs/util';

import { authenticate } from '../libs/security';
import { PromotionCode, Coupon, PaymentCurrency } from '../store/models';
import { getRedemptionData } from '../libs/discount/redemption';
import { createListParamSchema, MetadataSchema } from '../libs/api';
import logger from '../libs/logger';

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

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

  if (promotionCode) {
    return promotionCode.toJSON();
  }

  return null;
}

// Create promotion code with full validation
export async function createPromotionCode(payload: any) {
  // 1. Prepare promotion code data
  const promotionCodeData = pick(payload, [
    'id',
    'coupon_id',
    'code',
    'description',
    'active',
    'max_redemptions',
    'expires_at',
    'verification_type',
    'nft_config',
    'vc_config',
    'customer_dids',
    'restrictions',
    'metadata',
  ]);

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

  // 2. Validate that coupon exists
  const coupon = await Coupon.findOne({
    where: { id: promotionCodeData.coupon_id, livemode: !!(promotionCodeData as any).livemode },
  });
  if (!coupon) {
    throw new Error(`Coupon ${promotionCodeData.coupon_id} not found`);
  }

  // 3. Generate code if not provided
  if (!promotionCodeData.code) {
    promotionCodeData.code = createIdGenerator('', 8)();
  }

  // 4. Ensure code is unique
  const existingCode = await PromotionCode.findOne({
    where: { code: promotionCodeData.code, livemode: !!(promotionCodeData as any).livemode },
  });
  if (existingCode) {
    throw new Error('Promotion code already exists');
  }

  // 4.5. Format restrictions currency_options if provided
  if ((promotionCodeData as any).restrictions) {
    const allCurrencies = await PaymentCurrency.findAll();
    (promotionCodeData as any).restrictions = PromotionCode.formatRestrictionsCurrencyOptions(
      (promotionCodeData as any).restrictions,
      allCurrencies
    );
  }

  // 5. Create promotion code
  const promotionCode = await PromotionCode.insert(promotionCodeData);

  return promotionCode;
}

/**
 * POST /api/promotion-codes
 * Create a new promotion code
 */
router.post('/', authAdmin, async (req, res) => {
  try {
    const schema = Joi.object({
      coupon_id: Joi.string().required(),
      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(),
    });

    const { error, value } = schema.validate(req.body);
    if (error) {
      return res.status(400).json({
        error: 'Invalid request',
        details: error.details?.[0]?.message || 'Validation error',
      });
    }

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

    logger.info('Promotion code created', {
      promotionCodeId: promotionCode.id,
      code: promotionCode.code,
      requestedBy: req.user?.did,
    });

    const doc = await getExpandedPromotionCode(promotionCode.id as string);
    return res.json(doc);
  } catch (error) {
    logger.error('Error creating promotion code', {
      error: error.message,
      body: req.body,
    });
    return res.status(400).json({ error: error.message });
  }
});

/**
 * GET /api/promotion-codes
 * List all promotion codes with pagination
 */
router.get('/', authAdmin, async (req, res) => {
  const schema = Joi.object({
    page: Joi.number().integer().positive().default(1),
    pageSize: Joi.number().integer().positive().max(100).default(20),
    coupon_id: Joi.string().optional(),
    active: Joi.boolean().optional(),
  });

  const { error, value } = schema.validate(req.query, {
    stripUnknown: false,
    allowUnknown: true,
  });
  if (error) {
    return res.status(400).json({
      error: 'Invalid request',
      details: error.details?.[0]?.message || 'Validation error',
    });
  }

  const { page, pageSize, coupon_id, active } = value;
  const where: any = { livemode: req.livemode };

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

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

  const { count, rows } = await PromotionCode.findAndCountAll({
    where,
    include: [{ model: Coupon, as: 'coupon' }],
    limit: pageSize,
    offset: (page - 1) * pageSize,
    order: [['created_at', 'DESC']],
  });

  return res.json({
    count,
    list: rows.map((promotionCode) => promotionCode.toJSON()),
    paging: { page, pageSize },
  });
});

/**
 * GET /api/promotion-codes/:id
 * Get promotion code details by ID (admin only)
 */
router.get('/:id', authAdmin, async (req, res) => {
  const promotionCode = await getExpandedPromotionCode(req.params.id as string);

  if (!promotionCode) {
    return res.status(404).json({ error: 'Promotion code not found' });
  }

  return res.json(promotionCode);
});

/**
 * PUT /api/promotion-codes/:id
 * Update a promotion code (limited fields can be updated)
 */
router.put('/:id', authAdmin, async (req, res) => {
  try {
    const schema = Joi.object({
      description: Joi.string().empty('').max(250).optional(),
      active: Joi.boolean().optional(),
      max_redemptions: Joi.number().integer().positive().optional(),
      expires_at: Joi.number().integer().min(0).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(),
      }).optional(),
      metadata: MetadataSchema,
    });

    const { error, value } = schema.validate(req.body, {
      stripUnknown: true,
    });
    if (error) {
      return res.status(400).json({
        error: 'Invalid request',
        details: error.details?.[0]?.message || 'Validation error',
      });
    }

    const promotionCode = await PromotionCode.findOne({
      where: { id: req.params.id },
    });

    if (!promotionCode) {
      return res.status(404).json({ error: 'Promotion code not found' });
    }

    if (promotionCode.locked) {
      return res.status(403).json({ error: 'Promotion code is locked and cannot be modified' });
    }

    if (!promotionCode.active) {
      return res.status(403).json({ error: 'Promotion code is invalid and cannot be modified' });
    }

    // Format restrictions currency_options if provided
    if (value.restrictions) {
      const allCurrencies = await PaymentCurrency.findAll();
      value.restrictions = PromotionCode.formatRestrictionsCurrencyOptions(value.restrictions, allCurrencies);
    }

    // Check if promotion code is being used
    if (await promotionCode.isUsed()) {
      // Lock the promotion code and only allow limited updates
      await promotionCode.update({ locked: true });
      const allowedUpdates = pick(value, ['metadata']);
      if (Object.keys(allowedUpdates).length === 0) {
        return res.status(403).json({
          error: 'Promotion code is being used. Only metadata can be updated.',
        });
      }
      await promotionCode.update(PromotionCode.formatBeforeSave(allowedUpdates));
    } else {
      await promotionCode.update(PromotionCode.formatBeforeSave(value));
    }

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

    const doc = await getExpandedPromotionCode(req.params.id as string);
    return res.json(doc);
  } catch (error) {
    logger.error('Error updating promotion code', {
      error: error.message,
      id: req.params.id,
      body: req.body,
    });
    return res.status(400).json({ error: error.message });
  }
});

/**
 * PUT /api/promotion-codes/:id/archive
 * Archive a promotion code (set active to false, making it unusable)
 */
router.put('/:id/archive', authAdmin, async (req, res) => {
  try {
    const promotionCode = await PromotionCode.findOne({
      where: { id: req.params.id, livemode: req.livemode },
    });

    if (!promotionCode) {
      return res.status(404).json({ error: 'Promotion code not found' });
    }

    if (!promotionCode.active) {
      return res.status(400).json({ error: 'Promotion code is already archived' });
    }

    // Archive the promotion code by setting active to false
    await promotionCode.update({ active: false });

    logger.info('Promotion code archived', {
      promotionCodeId: req.params.id,
      requestedBy: req.user?.did,
    });
    return res.json(promotionCode);
  } catch (error) {
    logger.error('Error archiving promotion code', {
      error: error.message,
      id: req.params.id,
    });
    return res.status(400).json({ error: error.message });
  }
});

/**
 * DELETE /api/promotion-codes/:id
 * Delete a promotion code (mark as inactive if used, otherwise hard delete)
 */
router.delete('/:id', authAdmin, async (req, res) => {
  try {
    const promotionCode = await PromotionCode.findOne({
      where: { id: req.params.id, livemode: req.livemode },
    });

    if (!promotionCode) {
      return res.status(404).json({ error: 'Promotion code not found' });
    }

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

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

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

    logger.info('Promotion code deleted', {
      promotionCodeId: req.params.id,
      requestedBy: req.user?.did,
    });

    return res.json(promotionCode);
  } catch (error) {
    logger.error('Error deleting promotion code', {
      error: error.message,
      id: req.params.id,
    });
    return res.status(400).json({ error: error.message });
  }
});

/**
 * GET /api/promotion-codes/:id/used
 * Check if a promotion code is being used
 */
router.get('/:id/used', authAdmin, async (req, res) => {
  const promotionCode = await PromotionCode.findOne({
    where: { id: req.params.id, livemode: req.livemode },
  });

  if (!promotionCode) {
    return res.status(404).json({ error: 'Promotion code not found' });
  }

  const used = await promotionCode.isUsed();
  if (!used) {
    await promotionCode.update({ locked: false });
  }

  return res.json({ used });
});

/**
 * GET /api/promotion-codes/by-code/:code
 * Get promotion code details by code (admin only)
 */
router.get('/by-code/:code', authAdmin, async (req, res) => {
  const { code } = req.params;
  if (!code) {
    return res.status(400).json({ error: 'Code parameter is required' });
  }

  const promotionCode = await PromotionCode.findByCode(code.toLowerCase());

  if (!promotionCode) {
    return res.status(404).json({ error: 'Promotion code not found' });
  }
  const doc = await getExpandedPromotionCode(promotionCode.id as string);

  return res.json(doc);
});

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

/**
 * GET /api/promotion-codes/:id/redemptions
 * Get active redemptions for a promotion code with detailed admin analytics
 */
router.get('/:id/redemptions', authAdmin, async (req, res) => {
  try {
    const promotionCodeId = req.params.id as string;

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

    const promotionCode = await PromotionCode.findByPk(promotionCodeId);
    if (!promotionCode) {
      return res.status(404).json({ error: 'Promotion code not found' });
    }

    const result = await getRedemptionData(
      { promotion_code_id: promotionCodeId },
      { page, pageSize, type: type as 'customer' | 'subscription' | undefined },
      promotionCode,
      'promotion_code'
    );

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

export default router;
