/* eslint-disable no-console */
/* eslint-disable consistent-return */
import OcapClient from '@ocap/client';
import { BN, fromTokenToUnit } from '@ocap/util';
import { ethers } from 'ethers';
import { Router } from 'express';
import Joi from 'joi';
import pick from 'lodash/pick';

import { getWallet } from '@blocklet/sdk/lib/wallet';
import { Op } from 'sequelize';
import { sendErc20ToUser } from '../integrations/ethereum/token';
import { BNPositiveValidator, createListParamSchema, getOrder, getWhereFromKvQuery, MetadataSchema } from '../libs/api';
import { wallet } from '../libs/auth';
import { EVM_CHAIN_TYPES } from '../libs/constants';
import { authenticate } from '../libs/security';
import { formatMetadata } from '../libs/util';
import {
  Customer,
  Invoice,
  PaymentCurrency,
  PaymentIntent,
  PaymentMethod,
  Refund,
  Subscription,
} from '../store/models';
import logger from '../libs/logger';
import { getGasPayerExtra } from '../libs/payment';
import { getRefundAmountSetup } from '../libs/refund';

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

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

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

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

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

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

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

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

  if (status) {
    where.status = status
      .split(',')
      .map((x) => x.trim())
      .filter(Boolean);
  }

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

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

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

const searchSchema = createListParamSchema<{}>({});
const refundRequestSchema = Joi.object({
  amount: BNPositiveValidator.required(),
  currency_id: Joi.string().required(),
  customer_id: Joi.string().required(),
  payment_method_id: Joi.string().required(),
  payment_intent_id: Joi.string().required(),
  reason: Joi.string()
    .valid('duplicate', 'requested_by_customer', 'requested_by_admin', 'fraudulent', 'expired_uncaptured_charge')
    .required(),
  description: Joi.string().max(200).required(),
  metadata: MetadataSchema,
  invoice_id: Joi.string().optional(),
  subscription_id: Joi.string().optional(),
});
router.post('/', authAdmin, async (req, res) => {
  const { error } = refundRequestSchema.validate(req.body);
  if (error) {
    return res.status(400).json({ error: `Refund request invalid: ${error.message}` });
  }
  try {
    const paymentCurrency = await PaymentCurrency.findByPk(req.body.currency_id);
    if (!paymentCurrency) {
      throw new Error('payment currency not found');
    }
    const amount = fromTokenToUnit(req.body.amount, paymentCurrency?.decimal).toString();
    const amountBN = new BN(amount);
    const result = await getRefundAmountSetup({
      paymentIntentId: req.body.payment_intent_id,
    });
    if (amountBN.gt(new BN(result.amount))) {
      throw new Error('refund amount exceeds the available amount');
    }

    const paymentMethod = await PaymentMethod.findByPk(req.body.payment_method_id);
    if (!paymentMethod) {
      throw new Error(`payment method not found: ${req.body.payment_method_id}`);
    }
    const item = await Refund.create({
      ...req.body,
      type: 'refund',
      livemode: !!req.livemode,
      amount,
      metadata: formatMetadata(req.body.metadata),
      attempt_count: 0,
      attempted: false,
      next_attempt: 0,
      last_attempt_error: null,
      starting_balance: '0',
      ending_balance: '0',
      status: 'pending',
      starting_token_balance: {},
      ending_token_balance: {},
    });
    logger.info('refund created', {
      ...req.params,
      ...req.body,
      result: item.toJSON(),
      requestedBy: req.user?.did,
    });
    res.json(item);
  } catch (err) {
    logger.error('Create refund failed', {
      error: err.message,
      stack: err.stack,
      requestBody: req.body,
      requestedBy: req.user?.did,
    });
    res.status(400).json({ error: err.message });
  }
});

router.get('/search', auth, async (req, res) => {
  const { page, pageSize, livemode, q, o } = await searchSchema.validateAsync(req.query, {
    stripUnknown: false,
    allowUnknown: true,
  });

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

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

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

// Helper function: Query payment method and currency by contract and chain type
async function queryPaymentMethodAndCurrency(
  chainType: string,
  livemode: boolean,
  contract: string
): Promise<{
  paymentCurrency: PaymentCurrency | null;
  paymentMethodId: string;
  currencyId: string;
}> {
  let paymentMethod: PaymentMethod | null = null;
  let paymentCurrency: PaymentCurrency | null = null;
  let paymentMethodId = '';
  let currencyId = '';

  paymentMethod = await PaymentMethod.findOne({
    where: { type: chainType, livemode },
  });

  if (paymentMethod) {
    paymentMethodId = paymentMethod.id;
    paymentCurrency = await PaymentCurrency.findOne({
      where: { contract, payment_method_id: paymentMethod.id },
    });
    if (paymentCurrency) {
      currencyId = paymentCurrency.id;
      logger.info('PaymentMethod and Currency ID queried by contract and chain_type', {
        paymentMethodId,
        currencyId,
        contract,
        chainType,
      });
    } else {
      logger.warn('PaymentCurrency not found by contract and chain_type', {
        contract,
        chainType,
        paymentMethodId: paymentMethod.id,
      });
    }
  } else {
    logger.warn('PaymentMethod not found by chain_type', { chainType, livemode });
  }

  return { paymentCurrency, paymentMethodId, currencyId };
}

// Helper function: Resolve or create payment intent
async function resolveOrCreatePaymentIntent(
  customerId: string,
  finalPaymentMethodId: string,
  finalCurrencyId: string,
  livemode: boolean,
  amount: string,
  chainType: string,
  destination: string,
  description: string | undefined,
  metadata: Record<string, any> | undefined
): Promise<string> {
  if (!finalPaymentMethodId || !finalCurrencyId) {
    logger.warn('Cannot create payment intent: missing payment_method_id or currency_id', {
      hasPaymentMethod: !!finalPaymentMethodId,
      hasCurrency: !!finalCurrencyId,
    });
    return '';
  }

  // Validate and normalize customer_id - must be non-empty string if provided
  let finalCustomerId: string | undefined;
  if (customerId && customerId.trim().length > 0) {
    finalCustomerId = customerId.trim();
  } else if (customerId && customerId.trim().length === 0) {
    logger.warn('Invalid customer_id: empty string, will not set customer_id', {
      customerId,
    });
  }

  const createData: any = {
    livemode,
    amount,
    amount_received: amount,
    amount_capturable: '0',
    currency_id: finalCurrencyId,
    payment_method_id: finalPaymentMethodId,
    description: description || `Refund to ${destination}`,
    status: 'succeeded',
    capture_method: 'automatic',
    confirmation_method: 'automatic',
    payment_method_types: [chainType],
    statement_descriptor: '',
    statement_descriptor_suffix: '',
    setup_future_usage: 'on_session',
    metadata: formatMetadata(metadata || {}),
  };

  // Only set customer_id if it's a valid non-empty string
  if (finalCustomerId) {
    createData.customer_id = finalCustomerId;
  }

  try {
    const newPaymentIntent = await PaymentIntent.create(createData);

    logger.info('Created new payment intent for refund', {
      paymentIntentId: newPaymentIntent.id,
      refundAmount: amount,
    });

    return newPaymentIntent.id;
  } catch (error: any) {
    logger.error('Failed to create payment intent for refund', {
      error,
      createData: {
        ...createData,
        customer_id: createData.customer_id || 'null',
        customer_id_length: createData.customer_id?.length || 0,
      },
    });
    throw error;
  }
}

// Returns transaction hash and payment details
async function processDirectTransfer({
  destination,
  amount,
  recordId,
  contract,
  chainType,
  chainApiHost,
  description,
}: {
  destination: string;
  amount: string;
  recordId: string;
  contract: string;
  chainType: string;
  chainApiHost: string;
  description: string;
}): Promise<{ txHash: string; paymentDetails: any }> {
  if (chainType === 'arcblock') {
    const client = new OcapClient(chainApiHost);
    const signed = await client.signTransferV2Tx({
      tx: {
        itx: {
          to: destination,
          value: '0',
          assets: [],
          tokens: [{ address: contract, value: amount }],
          data: {
            typeUrl: 'json',
            // @ts-ignore
            value: {
              appId: wallet.address,
              reason: description || `Refund to ${destination}`,
              refundId: recordId,
            },
          },
        },
      },
      wallet,
    });

    // @ts-ignore
    const { buffer } = await client.encodeTransferV2Tx({ tx: signed });
    // @ts-ignore
    const txHash = await client.sendTransferV2Tx({ tx: signed, wallet }, await getGasPayerExtra(buffer));

    return {
      txHash,
      paymentDetails: {
        arcblock: {
          tx_hash: txHash,
          payer: wallet.address,
          type: 'transfer',
        },
      },
    };
  }
  if (EVM_CHAIN_TYPES.includes(chainType)) {
    const provider = new ethers.JsonRpcProvider(chainApiHost);
    const receipt = await sendErc20ToUser(provider, contract, destination, amount);
    const tx = await provider.getTransaction(receipt.hash);
    const payerAddress = tx?.from || '';

    const paymentDetails: any = {
      [chainType]: {
        tx_hash: receipt.hash,
        payer: payerAddress,
        type: 'transfer',
      },
    };

    if (receipt) {
      paymentDetails[chainType].block_height = receipt.blockNumber.toString();
      paymentDetails[chainType].gas_used = receipt.gasUsed.toString();
      paymentDetails[chainType].gas_price = receipt.gasPrice?.toString() || '0';
    }

    return {
      txHash: receipt.hash,
      paymentDetails,
    };
  }
  throw new Error(`Unsupported chain type: ${chainType}`);
}

const syncRefundRequestSchema = Joi.object({
  // Refund creation required fields
  livemode: Joi.number().required().description('Testnet (0) or mainnet (1)'),
  amount: Joi.string().required().description('Transfer amount in unit format'),
  destination: Joi.string().required().description('Output account address (to address)'),

  // Transfer execution required fields
  contract: Joi.string().required().description('Token contract address for transfer'),
  chainType: Joi.string().required().description('Chain type, e.g. "arcblock", "ethereum", "base", "bitcoin"'),
  chainApiHost: Joi.string().required().description('RPC API endpoint for the chain'),

  // Optional fields
  description: Joi.string().max(512).optional().description('Refund description (optional)'),
  orderId: Joi.string().optional().description('Order ID for duplicate check (optional)'),
  customerName: Joi.string().optional().description('Customer name for mock user (default: "Broker")'),
  metadata: MetadataSchema.optional(),
});

router.post('/sync', authAdmin, async (req: any, res: any) => {
  const { error } = syncRefundRequestSchema.validate(req.body);
  if (error) {
    return res.status(400).json({ error: `Refund request invalid: ${error.message}` });
  }

  // Extract and validate request data
  const { livemode, amount, destination, contract, chainType, chainApiHost } = req.body;
  const { description, metadata, orderId, customerName } = req.body;

  try {
    // Convert and validate input types
    const livemodeBoolean = !!livemode;
    const amountString = String(amount);
    const amountBN = new BN(amountString);
    if (amountBN.lte(new BN('0'))) {
      throw new Error('Refund amount must be greater than 0');
    }

    // Query payment method and currency first (needed for duplicate check)
    const { paymentMethodId: finalPaymentMethodId, currencyId: finalCurrencyId } = await queryPaymentMethodAndCurrency(
      chainType,
      livemodeBoolean,
      contract
    );

    // Check for existing refund with same orderId in metadata if provided
    if (orderId && orderId.trim()) {
      const existingRefund = await Refund.findOne({
        where: {
          metadata: { orderId: orderId.trim() },
          status: { [Op.in]: ['succeeded', 'pending'] },
        },
      });

      if (existingRefund) {
        logger.info('Order refund already completed or in-progress. Current request interrupted/terminated', {
          orderId,
          refundId: existingRefund.id,
          status: existingRefund.status,
        });

        return res.json(existingRefund);
      }
    }

    // Get system DID as default payer (发款方)
    const systemDid = getWallet().address;

    // Create mock customer if customerName is provided, otherwise use "Broker" as default
    const finalCustomerName = customerName || 'Broker';
    let finalCustomer = await Customer.findOne({
      where: { did: systemDid },
    });

    if (!finalCustomer) {
      finalCustomer = await Customer.create({
        livemode: livemodeBoolean,
        did: systemDid,
        name: finalCustomerName,
        email: '',
        phone: '',
        address: Customer.formatAddressFromUser(null),
        description: 'Broker customer for refund',
        metadata: { isBroker: true },
        balance: '0',
        next_invoice_sequence: 1,
        delinquent: false,
        invoice_prefix: Customer.getInvoicePrefix(),
      });
      logger.info('Created mock customer for refund', {
        customerId: finalCustomer.id,
        customerName: finalCustomerName,
        did: systemDid,
      });
    }

    if (finalCustomerName !== finalCustomer?.name) {
      finalCustomer = await finalCustomer.update({ name: finalCustomerName, metadata: { isBroker: true } });
    }

    // Resolve or create payment intent
    const finalPaymentIntentId = await resolveOrCreatePaymentIntent(
      finalCustomer.id,
      finalPaymentMethodId,
      finalCurrencyId,
      livemodeBoolean,
      amountString,
      chainType,
      destination,
      description,
      { ...metadata, payerDid: systemDid }
    );

    // Build metadata with orderId and destination (Refund model doesn't have destination field)
    const refundMetadata = {
      ...formatMetadata(metadata || {}),
      ...(orderId && orderId.trim() ? { orderId: orderId.trim() } : {}),
      payerDid: systemDid,
    };

    // Create refund record
    const refund = await Refund.create({
      livemode: livemodeBoolean,
      description: description || `Refund to ${destination}`,
      amount: amountString,
      payment_intent_id: finalPaymentIntentId || '',
      customer_id: finalCustomer.id,
      currency_id: finalCurrencyId || '',
      payment_method_id: finalPaymentMethodId || '',
      status: 'pending',
      attempt_count: 0,
      attempted: false,
      next_attempt: 0,
      last_attempt_error: null,
      starting_balance: '0',
      ending_balance: '0',
      starting_token_balance: {},
      ending_token_balance: {},
      type: 'refund',
      metadata: refundMetadata,
    });

    logger.info('Synchronous refund created, processing transfer directly', {
      refundId: refund.id,
      destination,
      amount,
      chainType,
      requestedBy: req.user?.did,
    });

    // Process transfer
    try {
      const { txHash, paymentDetails } = await processDirectTransfer({
        destination,
        amount: amountString,
        recordId: refund.id,
        contract,
        chainType,
        chainApiHost,
        description: description || `Refund to ${destination}`,
      });

      await refund.update({
        status: 'succeeded',
        last_attempt_error: null,
        attempt_count: refund.attempt_count + 1,
        attempted: true,
        payment_details: paymentDetails,
      });

      logger.info('Synchronous refund transfer completed', {
        refundId: refund.id,
        txHash,
        requestedBy: req.user?.did,
      });

      // Reload and enrich refund data
      const updatedRefund = await Refund.findByPk(refund.id, {
        include: [
          { model: PaymentCurrency, as: 'paymentCurrency', required: false },
          { model: PaymentIntent, as: 'paymentIntent', required: false },
          { model: PaymentMethod, as: 'paymentMethod', required: false },
        ],
      });

      if (!updatedRefund) {
        throw new Error('Refund not found after processing');
      }

      return res.json(updatedRefund.toJSON());
    } catch (processError: any) {
      logger.error('Synchronous refund transfer failed', {
        error: processError,
        requestedBy: req.user?.did,
      });
      // Handle transfer failure
      await refund.update({
        status: 'failed',
        last_attempt_error: {
          type: 'api_error',
          code: processError.code || 'api_error',
          message: processError.message || 'Transfer failed',
        },
        attempt_count: refund.attempt_count + 1,
        attempted: true,
        failure_reason: 'unknown',
      });

      logger.error('Synchronous refund transfer failed', {
        refundId: refund.id,
        error: processError.message || processError.error?.message,
        requestedBy: req.user?.did,
      });

      // Reload and enrich failed refund data
      const failedRefund = await Refund.findByPk(refund.id, {
        include: [
          { model: PaymentCurrency, as: 'paymentCurrency', required: false },
          { model: PaymentIntent, as: 'paymentIntent', required: false },
          { model: PaymentMethod, as: 'paymentMethod', required: false },
        ],
      });

      if (failedRefund) {
        return res.status(400).json({
          ...failedRefund.toJSON(),
          error: processError.message || processError.error?.message || 'Refund transfer failed',
        });
      }
      throw processError;
    }
  } catch (err: any) {
    logger.error('Create synchronous refund failed', {
      error: err,
      requestedBy: req.user?.did,
    });
    return res.status(400).json({ error: err.message });
  }
});

router.get('/:id', auth, async (req, res) => {
  const doc = await Refund.findByPk(req.params.id as string, {
    include: [
      { model: Customer, as: 'customer' },
      { model: PaymentCurrency, as: 'paymentCurrency' },
      { model: PaymentIntent, as: 'paymentIntent' },
      { model: PaymentMethod, as: 'paymentMethod' },
      { model: Invoice, as: 'invoice' },
      { model: Subscription, as: 'subscription' },
    ],
  });

  if (doc) {
    // @ts-ignore
    const paymentMethod = await PaymentMethod.findByPk(doc.paymentCurrency.payment_method_id);
    return res.json({ ...doc.toJSON(), paymentMethod: paymentMethod?.toJSON() });
  }

  return res.status(404).json(null);
});

// eslint-disable-next-line consistent-return
router.put('/:id', authAdmin, async (req, res) => {
  try {
    const doc = await Refund.findByPk(req.params.id as string);
    if (!doc) {
      return res.status(404).json({ error: 'Refund 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);
    logger.info('Refund updated', {
      refundId: doc.id,
      updatedFields: Object.keys(raw),
      requestedBy: req.user?.did,
    });
    res.json(doc);
  } catch (err) {
    logger.error('Update refund failed', {
      refundId: req.params.id,
      error: err.message,
      stack: err.stack,
      requestBody: req.body,
      requestedBy: req.user?.did,
    });
    return res.status(400).json({ error: err.message });
  }
});

router.post('/:id/cancel', authAdmin, async (req, res) => {
  const doc = await Refund.findByPk(req.params.id as string);
  if (!doc) {
    return res.status(404).json({ error: 'Refund not found' });
  }
  if (doc.status === 'succeeded') {
    return res.status(400).json({ error: 'Refund is already succeeded' });
  }
  try {
    await doc.update({ status: 'canceled' });
    return res.json(doc);
  } catch (err) {
    logger.error('Cancel refund failed', {
      refundId: req.params.id,
      error: err.message,
      requestedBy: req.user?.did,
    });
    return res.status(400).json({ error: err.message });
  }
});

export default router;
