import {
  BadRequestException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { randomBytes, randomUUID, createCipheriv, createDecipheriv } from 'node:crypto';
import { Brackets, Repository } from 'typeorm';
import { ConfigService } from '@nestjs/config';
import { assertAdmin } from '../admin/admin-access';
import { maskSensitivePayload } from '../third-party/third-party-security';
import { ThirdPartyClientService } from '../third-party/third-party-client.service';
import { Course, OrderFormField } from '../third-party/entities/course.entity';
import { User } from '../users/entities/user.entity';
import type {
  CreateOrderDto,
  LocalOrderAction,
  LocalOrderActionDto,
  OrderListQuery,
  SelectedCourseDto,
  ThirdPartyOrderAction,
  ThirdPartyOrderActionDto,
  ThirdPartyOrderListSyncDto,
  ThirdPartyCourseQueryDto,
} from './dto/orders.dto';
import { OrderAction } from './entities/order-action.entity';
import { OrderFulfillment } from './entities/order-fulfillment.entity';
import { OrderItem } from './entities/order-item.entity';
import {
  FulfillmentStatus,
  Order,
  PaymentStatus,
} from './entities/order.entity';
import {
  ThirdPartyOrder,
  THIRD_PARTY_ORDER_PROVIDER,
} from './entities/third-party-order.entity';

type UnknownRecord = Record<string, unknown>;

const SOURCE_ALL = 'all';
const THIRD_PARTY_SOURCE = 'third_party';
const SELF_OWNED_SOURCE = 'self_owned';
const PAYMENT_STATUSES = new Set<PaymentStatus>([
  'unpaid',
  'paying',
  'paid',
  'pay_failed',
  'closed',
  'refunding',
  'refunded',
  'partial_refunded',
]);
const FULFILLMENT_STATUSES = new Set<FulfillmentStatus>([
  'pending',
  'submitting',
  'submitted',
  'processing',
  'completed',
  'failed',
  'canceled',
]);
const THIRD_PARTY_ACTIONS: Record<ThirdPartyOrderAction, string> = {
  budan: 'budan',
  gaimi: 'gaimi',
  stop: 'stop',
  priority: 'priority',
  convert: 'convert',
  'update-time': 'update_time',
  'update-cycle': 'update_cycle',
};
const LOCAL_ORDER_ACTIONS = new Set<LocalOrderAction>([
  'process',
  'complete',
  'cancel',
  'close',
  'remark',
]);
const SENSITIVE_ORDER_KEYS = new Set([
  'password',
  'pass',
  'studentPassword',
  'accountPassword',
]);

@Injectable()
export class OrdersService {
  constructor(
    private readonly configService: ConfigService,
    private readonly thirdPartyClientService: ThirdPartyClientService,
    @InjectRepository(Course)
    private readonly productsRepository: Repository<Course>,
    @InjectRepository(Order)
    private readonly ordersRepository: Repository<Order>,
    @InjectRepository(OrderItem)
    private readonly orderItemsRepository: Repository<OrderItem>,
    @InjectRepository(OrderFulfillment)
    private readonly fulfillmentsRepository: Repository<OrderFulfillment>,
    @InjectRepository(OrderAction)
    private readonly orderActionsRepository: Repository<OrderAction>,
    @InjectRepository(ThirdPartyOrder)
    private readonly thirdPartyOrdersRepository: Repository<ThirdPartyOrder>,
  ) {}

  async queryThirdPartyCourses(actor: User, dto: ThirdPartyCourseQueryDto) {
    assertAdmin(actor);
    const product = await this.getProduct(dto.productId);
    this.assertPurchasable(product);
    if (product.sourceType !== THIRD_PARTY_SOURCE) {
      throw new BadRequestException('只有第三方商品需要查课');
    }

    const school = this.normalizeRequiredString(dto.school, '学校不能为空');
    const account = this.normalizeRequiredString(dto.account, '账号不能为空');
    const password = this.normalizeRequiredString(dto.password, '密码不能为空');
    const platform = this.getThirdPartyPlatform(product);
    const payload: UnknownRecord = {
      platform,
      school,
      user: account,
      pass: password,
    };
    const expand = this.normalizeRecord(dto.expand);
    if (expand) {
      payload.expand = expand;
    }

    const response = await this.thirdPartyClientService.call('get', payload);
    const courses = this.extractCourseRecords(response);
    return {
      product: this.toProductSummary(product),
      query: {
        platform,
        school,
        account: this.maskText(account),
      },
      courses,
      raw: maskSensitivePayload(response),
    };
  }

  async createOrder(actor: User, dto: CreateOrderDto) {
    assertAdmin(actor);
    const product = await this.getProduct(dto.productId);
    this.assertPurchasable(product);
    const quantity = this.normalizeQuantity(dto.quantity);
    const orderPayload = this.normalizeRecord(dto.orderPayload) ?? {};
    this.validateOrderPayload(product.orderFormSchema ?? [], orderPayload);
    const selectedCourse = this.normalizeSelectedCourse(dto.selectedCourse);
    if (product.sourceType === THIRD_PARTY_SOURCE && !selectedCourse?.courseName) {
      throw new BadRequestException('第三方商品需要先选择查课结果');
    }

    const unitPrice = Number(product.price || 0);
    const totalAmount = (unitPrice * quantity).toFixed(2);
    const orderNo = await this.generateOrderNo();
    const { publicPayload, encryptedPayload } =
      this.splitSensitivePayload(orderPayload);
    const now = new Date();
    const order = this.ordersRepository.create({
      id: randomUUID(),
      orderNo,
      userId: actor.id,
      sourceType: product.sourceType,
      provider: product.provider,
      totalAmount,
      payableAmount: totalAmount,
      paidAmount: '0.00',
      paymentStatus: 'unpaid',
      fulfillmentStatus: 'pending',
      remark: this.normalizeOptionalString(dto.remark),
      paidAt: null,
      closedAt: null,
    });

    await this.ordersRepository.save(order);

    const itemPayload = {
      ...publicPayload,
      selectedCourse: selectedCourse
        ? maskSensitivePayload(selectedCourse)
        : null,
      expand: this.normalizeRecord(dto.expand),
    };
    const orderItem = this.orderItemsRepository.create({
      id: randomUUID(),
      orderId: order.id,
      productId: product.id,
      productName: product.name,
      sourceType: product.sourceType,
      externalProductId: product.externalId ?? product.remoteCourseId,
      provider: product.provider,
      unitPrice: Number(product.price || 0).toFixed(2),
      quantity,
      totalAmount,
      payload: itemPayload,
      encryptedPayload: Object.keys(encryptedPayload).length
        ? encryptedPayload
        : null,
      createdAt: now,
    });
    await this.orderItemsRepository.save(orderItem);

    return this.getOrderDetail(actor, order.id);
  }

  async listOrders(actor: User, query: OrderListQuery) {
    assertAdmin(actor);
    const { page, pageSize } = this.getPagination(query);
    const keyword = String(query.keyword || '').trim();
    const sourceType = this.normalizeSourceType(query.sourceType);
    const paymentStatus = this.normalizePaymentStatus(query.paymentStatus);
    const fulfillmentStatus = this.normalizeFulfillmentStatus(
      query.fulfillmentStatus,
    );

    const builder = this.ordersRepository
      .createQueryBuilder('order')
      .orderBy('order.createdAt', 'DESC')
      .skip((page - 1) * pageSize)
      .take(pageSize);

    if (sourceType) {
      builder.andWhere('order.sourceType = :sourceType', { sourceType });
    }
    if (paymentStatus) {
      builder.andWhere('order.paymentStatus = :paymentStatus', {
        paymentStatus,
      });
    }
    if (fulfillmentStatus) {
      builder.andWhere('order.fulfillmentStatus = :fulfillmentStatus', {
        fulfillmentStatus,
      });
    }
    if (keyword) {
      builder.andWhere(
        new Brackets((qb) => {
          qb.where('order.orderNo LIKE :keyword', { keyword: `%${keyword}%` })
            .orWhere('order.remark LIKE :keyword', {
              keyword: `%${keyword}%`,
            });
        }),
      );
    }

    const [orders, total] = await builder.getManyAndCount();
    const items = await this.getItemsByOrderIds(orders.map((order) => order.id));
    return {
      list: orders.map((order) => ({
        ...order,
        items: items.get(order.id) ?? [],
      })),
      total,
      page,
      pageSize,
    };
  }

  async getOrderDetail(actor: User, idOrNo: string) {
    assertAdmin(actor);
    const order = await this.ordersRepository.findOne({
      where: [{ id: idOrNo }, { orderNo: idOrNo }],
    });
    if (!order) {
      throw new NotFoundException('订单不存在');
    }

    const [items, fulfillments, actions] = await Promise.all([
      this.orderItemsRepository.find({
        where: { orderId: order.id },
        order: { createdAt: 'ASC' },
      }),
      this.fulfillmentsRepository.find({
        where: { orderId: order.id },
        order: { createdAt: 'ASC' },
      }),
      this.orderActionsRepository.find({
        where: { orderId: order.id },
        order: { createdAt: 'DESC' },
      }),
    ]);

    return { ...order, items, fulfillments, actions };
  }

  async markOrderPaid(actor: User, idOrNo: string) {
    assertAdmin(actor);
    const detail = await this.getOrderDetail(actor, idOrNo);
    const order = await this.ordersRepository.findOneByOrFail({
      id: detail.id,
    });

    if (order.paymentStatus === 'paid') {
      return this.getOrderDetail(actor, order.id);
    }
    if (order.paymentStatus !== 'unpaid' && order.paymentStatus !== 'paying') {
      throw new BadRequestException('当前订单状态不能标记已支付');
    }

    order.paymentStatus = 'paid';
    order.paidAmount = order.payableAmount;
    order.paidAt = new Date();
    await this.ordersRepository.save(order);
    await this.fulfillPaidOrder(order.id);
    return this.getOrderDetail(actor, order.id);
  }

  async refreshThirdPartyOrder(
    actor: User,
    idOrNo: string,
    dto: ThirdPartyOrderActionDto,
  ) {
    assertAdmin(actor);
    const detail = await this.getOrderDetail(actor, idOrNo);
    this.assertThirdPartyOrder(detail);
    const context = await this.getOrderOperationContext(detail.id);
    const requestPayload = this.buildThirdPartyOrderIdentityPayload(
      context,
      dto,
      true,
    );
    return this.callThirdPartyOrderAction({
      actor,
      order: context.order,
      fulfillment: context.fulfillment,
      action: 'chadan',
      requestPayload,
      remark: this.normalizeOptionalString(dto.remark),
      updateOrder: async (response) => {
        context.fulfillment.responsePayload = maskSensitivePayload(response);
        const externalOrderNo = this.extractExternalOrderNo(response);
        if (externalOrderNo) {
          context.fulfillment.externalOrderNo = externalOrderNo;
        }
        await this.fulfillmentsRepository.save(context.fulfillment);
      },
    });
  }

  async syncThirdPartyOrderList(
    actor: User,
    dto: ThirdPartyOrderListSyncDto,
  ) {
    assertAdmin(actor);
    return this.syncThirdPartyOrderListInternal(actor, dto);
  }

  async syncThirdPartyOrderListFromTask(dto: ThirdPartyOrderListSyncDto = {}) {
    return this.syncThirdPartyOrderListInternal(null, dto);
  }

  private async syncThirdPartyOrderListInternal(
    actor: User | null,
    dto: ThirdPartyOrderListSyncDto,
  ) {
    const requestPayload = this.buildThirdPartyOrderListPayload(dto);
    const response = await this.thirdPartyClientService.call(
      'orders',
      requestPayload,
    );
    const records = this.extractRecords(response);
    const provider = THIRD_PARTY_ORDER_PROVIDER;
    const remoteOrderNos = Array.from(
      new Set(
        records
          .map((record) => this.readRemoteOrderNo(record))
          .filter(Boolean),
      ),
    ) as string[];
    const now = new Date();

    if (!remoteOrderNos.length) {
      return {
        provider,
        total: records.length,
        matched: 0,
        created: 0,
        updated: 0,
        imported: 0,
        localUpdated: 0,
        unmatched: records.length,
        page: requestPayload.page,
        limit: requestPayload.limit,
        recent: requestPayload.recent,
      };
    }

    const thirdPartyOrders = await this.thirdPartyOrdersRepository
      .createQueryBuilder('thirdPartyOrder')
      .where('thirdPartyOrder.provider = :provider', { provider })
      .andWhere('thirdPartyOrder.externalOrderNo IN (:...remoteOrderNos)', {
        remoteOrderNos,
      })
      .getMany();
    const thirdPartyOrderMap = new Map(
      thirdPartyOrders.map((item) => [item.externalOrderNo, item]),
    );
    const fulfillments = await this.fulfillmentsRepository
      .createQueryBuilder('fulfillment')
      .where('fulfillment.externalOrderNo IN (:...remoteOrderNos)', {
        remoteOrderNos,
      })
      .getMany();
    const fulfillmentMap = new Map(
      fulfillments
        .filter((item) => item.externalOrderNo)
        .map((item) => [item.externalOrderNo as string, item]),
    );
    const orderIds = Array.from(
      new Set([
        ...fulfillments.map((item) => item.orderId),
        ...thirdPartyOrders
          .map((item) => item.localOrderId)
          .filter(Boolean),
      ]),
    );
    const orders = orderIds.length
      ? await this.ordersRepository
          .createQueryBuilder('order')
          .where('order.id IN (:...orderIds)', { orderIds })
          .getMany()
      : [];
    const orderMap = new Map(orders.map((order) => [order.id, order]));
    let matched = 0;
    let created = 0;
    let updated = 0;
    let imported = 0;
    let localUpdated = 0;
    const thirdPartyOrdersToSave: ThirdPartyOrder[] = [];

    for (const record of records) {
      const remoteOrderNo = this.readRemoteOrderNo(record);
      if (!remoteOrderNo) {
        continue;
      }
      let fulfillment = fulfillmentMap.get(remoteOrderNo);
      const maskedRecord = maskSensitivePayload(record) as UnknownRecord;
      const existingThirdPartyOrder = thirdPartyOrderMap.get(remoteOrderNo);
      let order =
        (fulfillment ? orderMap.get(fulfillment.orderId) : null) ??
        (existingThirdPartyOrder?.localOrderId
          ? orderMap.get(existingThirdPartyOrder.localOrderId)
          : null) ??
        null;
      const thirdPartyOrder =
        existingThirdPartyOrder ??
        this.thirdPartyOrdersRepository.create({
          id: randomUUID(),
          provider,
          externalOrderNo: remoteOrderNo,
          firstSeenAt: now,
        });

      thirdPartyOrder.username = this.readThirdPartyOrderUsername(record);
      thirdPartyOrder.school = this.readOptionalString(record, [
        'school',
        'schoolName',
        'xuexiao',
        'xx',
      ]);
      thirdPartyOrder.courseName = this.readOptionalString(record, [
        'course',
        'courseName',
        'course_name',
        'kcname',
        'name',
        'title',
      ]);
      thirdPartyOrder.remoteStatus = this.readOptionalString(record, [
        'status',
        'state',
        'process',
        'progress',
        'order_status',
        'orderStatus',
      ]);
      thirdPartyOrder.localOrderId = order?.id ?? null;
      thirdPartyOrder.rawPayload = maskedRecord;
      thirdPartyOrder.lastSeenAt = now;
      thirdPartyOrdersToSave.push(thirdPartyOrder);

      if (existingThirdPartyOrder) {
        updated += 1;
      } else {
        created += 1;
      }

      if (!order) {
        const importedOrder = await this.importThirdPartyOrderFromRecord({
          actor,
          provider,
          remoteOrderNo,
          requestPayload,
          record,
          maskedRecord,
          now,
        });
        order = importedOrder.order;
        fulfillment = importedOrder.fulfillment;
        thirdPartyOrder.localOrderId = order.id;
        orderMap.set(order.id, order);
        fulfillmentMap.set(remoteOrderNo, fulfillment);
        imported += 1;
      } else if (!fulfillment) {
        fulfillment = await this.ensureThirdPartyFulfillmentForImportedOrder({
          order,
          provider,
          remoteOrderNo,
          requestPayload,
          maskedRecord,
          now,
        });
        fulfillmentMap.set(remoteOrderNo, fulfillment);
      }

      matched += 1;
      fulfillment.responsePayload = maskedRecord;
      const nextStatus = this.mapThirdPartyOrderStatus(record);
      if (nextStatus) {
        fulfillment.status = nextStatus;
        order.fulfillmentStatus = nextStatus;
        if (!fulfillment.submittedAt) {
          fulfillment.submittedAt = new Date();
        }
        if (nextStatus === 'completed' && !fulfillment.completedAt) {
          fulfillment.completedAt = new Date();
        }
        await this.ordersRepository.save(order);
      }
      await this.fulfillmentsRepository.save(fulfillment);
      await this.writeOrderAction({
        actor,
        order,
        action: 'third_party.orders.sync',
        status: 'success',
        requestPayload,
        responsePayload: maskedRecord,
        remark: '同步第三方订单列表',
      });
      localUpdated += 1;
    }

    if (thirdPartyOrdersToSave.length) {
      await this.thirdPartyOrdersRepository.save(thirdPartyOrdersToSave);
    }

    return {
      provider,
      total: records.length,
      matched,
      created,
      updated,
      imported,
      localUpdated,
      unmatched: records.length - matched,
      page: requestPayload.page,
      limit: requestPayload.limit,
      recent: requestPayload.recent,
    };
  }

  async runThirdPartyOrderAction(
    actor: User,
    idOrNo: string,
    action: ThirdPartyOrderAction,
    dto: ThirdPartyOrderActionDto,
  ) {
    assertAdmin(actor);
    const act = THIRD_PARTY_ACTIONS[action];
    if (!act) {
      throw new BadRequestException('第三方订单操作不正确');
    }

    const detail = await this.getOrderDetail(actor, idOrNo);
    this.assertThirdPartyOrder(detail);
    const context = await this.getOrderOperationContext(detail.id);
    const requestPayload = this.buildThirdPartyActionPayload(action, context, dto);
    return this.callThirdPartyOrderAction({
      actor,
      order: context.order,
      fulfillment: context.fulfillment,
      action: act,
      requestPayload,
      remark: this.normalizeOptionalString(dto.remark),
      updateOrder: async (response) => {
        context.fulfillment.responsePayload = maskSensitivePayload(response);
        if (action === 'budan') {
          context.fulfillment.status = 'submitted';
          context.order.fulfillmentStatus = 'submitted';
          await this.ordersRepository.save(context.order);
        }
        if (action === 'stop') {
          context.fulfillment.status = 'canceled';
          context.order.fulfillmentStatus = 'canceled';
          await this.ordersRepository.save(context.order);
        }
        await this.fulfillmentsRepository.save(context.fulfillment);
      },
    });
  }

  async runLocalOrderAction(
    actor: User,
    idOrNo: string,
    action: LocalOrderAction,
    dto: LocalOrderActionDto,
  ) {
    assertAdmin(actor);
    if (!LOCAL_ORDER_ACTIONS.has(action)) {
      throw new BadRequestException('本地订单操作不正确');
    }

    const detail = await this.getOrderDetail(actor, idOrNo);
    if (detail.sourceType !== SELF_OWNED_SOURCE) {
      throw new BadRequestException('只有自营订单支持本地操作');
    }

    const context = await this.getOrderOperationContext(detail.id);
    const remark = this.normalizeOptionalString(dto.remark);
    try {
      await this.applyLocalOrderAction(context, action, remark);
      await this.writeOrderAction({
        actor,
        order: context.order,
        action: `local.${action}`,
        status: 'success',
        requestPayload: { remark },
        responsePayload: {
          paymentStatus: context.order.paymentStatus,
          fulfillmentStatus: context.order.fulfillmentStatus,
        },
        remark,
      });
      return this.getOrderDetail(actor, context.order.id);
    } catch (error) {
      await this.writeOrderAction({
        actor,
        order: context.order,
        action: `local.${action}`,
        status: 'failed',
        requestPayload: { remark },
        responsePayload: null,
        errorMessage: this.getErrorMessage(error),
        remark,
      });
      throw error;
    }
  }

  private async fulfillPaidOrder(orderId: string) {
    const order = await this.ordersRepository.findOneByOrFail({ id: orderId });
    if (order.fulfillmentStatus !== 'pending') {
      return;
    }

    const [item] = await this.orderItemsRepository.find({
      where: { orderId },
      order: { createdAt: 'ASC' },
      take: 1,
    });
    if (!item) {
      throw new BadRequestException('订单明细不存在');
    }

    const product = await this.productsRepository.findOne({
      where: { id: item.productId },
    });
    if (!product) {
      throw new NotFoundException('商品不存在');
    }

    order.fulfillmentStatus = 'submitting';
    await this.ordersRepository.save(order);

    const fulfillment = this.fulfillmentsRepository.create({
      id: randomUUID(),
      orderId: order.id,
      orderItemId: item.id,
      fulfillmentType:
        product.sourceType === THIRD_PARTY_SOURCE ? 'third_party_api' : 'local_only',
      status: 'submitting',
      provider: product.provider,
      externalOrderNo: null,
      requestPayload: null,
      responsePayload: null,
      errorMessage: null,
      submittedAt: null,
      completedAt: null,
    });
    await this.fulfillmentsRepository.save(fulfillment);

    if (product.sourceType === THIRD_PARTY_SOURCE) {
      await this.submitThirdPartyOrder(order, item, product, fulfillment);
      return;
    }

    fulfillment.status =
      product.fulfillmentType === 'manual' ? 'processing' : 'completed';
    fulfillment.submittedAt = new Date();
    fulfillment.completedAt =
      fulfillment.status === 'completed' ? fulfillment.submittedAt : null;
    await this.fulfillmentsRepository.save(fulfillment);

    order.fulfillmentStatus = fulfillment.status;
    await this.ordersRepository.save(order);
  }

  private async submitThirdPartyOrder(
    order: Order,
    item: OrderItem,
    product: Course,
    fulfillment: OrderFulfillment,
  ) {
    const payload = item.payload ?? {};
    const sensitivePayload = this.decryptPayload(item.encryptedPayload);
    const selectedCourse = this.normalizeSelectedCourse(
      payload.selectedCourse as SelectedCourseDto | null,
    );
    const account = this.normalizeRequiredString(
      payload.account ?? payload.user,
      '账号不能为空',
    );
    const school = this.normalizeRequiredString(payload.school, '学校不能为空');
    const password = this.normalizeRequiredString(
      sensitivePayload.password ?? sensitivePayload.pass,
      '密码不能为空',
    );
    const requestPayload: UnknownRecord = {
      platform: this.getThirdPartyPlatform(product),
      school,
      user: account,
      pass: password,
      kcname: selectedCourse?.courseName || item.productName,
    };
    const courseId = selectedCourse?.courseId || product.remoteCourseId;
    if (courseId) {
      requestPayload.kcid = courseId;
    }
    const expand = this.normalizeRecord(payload.expand);
    if (expand) {
      requestPayload.expand = expand;
    }

    fulfillment.requestPayload = maskSensitivePayload(requestPayload);
    await this.fulfillmentsRepository.save(fulfillment);

    try {
      const response = await this.thirdPartyClientService.call('add', requestPayload);
      fulfillment.status = 'submitted';
      fulfillment.submittedAt = new Date();
      fulfillment.responsePayload = maskSensitivePayload(response);
      fulfillment.externalOrderNo = this.extractExternalOrderNo(response);
      await this.fulfillmentsRepository.save(fulfillment);

      order.fulfillmentStatus = 'submitted';
      await this.ordersRepository.save(order);
    } catch (error) {
      fulfillment.status = 'failed';
      fulfillment.errorMessage = this.getErrorMessage(error);
      await this.fulfillmentsRepository.save(fulfillment);

      order.fulfillmentStatus = 'failed';
      await this.ordersRepository.save(order);
      throw error;
    }
  }

  private async callThirdPartyOrderAction(input: {
    actor: User;
    order: Order;
    fulfillment: OrderFulfillment;
    action: string;
    requestPayload: UnknownRecord;
    remark: string | null;
    updateOrder: (response: unknown) => Promise<void>;
  }) {
    const maskedRequestPayload = maskSensitivePayload(input.requestPayload);
    try {
      const response = await this.thirdPartyClientService.call(
        input.action,
        input.requestPayload,
      );
      const maskedResponsePayload = maskSensitivePayload(response);
      await input.updateOrder(maskedResponsePayload);
      await this.writeOrderAction({
        actor: input.actor,
        order: input.order,
        action: `third_party.${input.action}`,
        status: 'success',
        requestPayload: maskedRequestPayload,
        responsePayload: maskedResponsePayload,
        remark: input.remark,
      });
      return this.getOrderDetail(input.actor, input.order.id);
    } catch (error) {
      input.fulfillment.errorMessage = this.getErrorMessage(error);
      await this.fulfillmentsRepository.save(input.fulfillment);
      await this.writeOrderAction({
        actor: input.actor,
        order: input.order,
        action: `third_party.${input.action}`,
        status: 'failed',
        requestPayload: maskedRequestPayload,
        responsePayload: null,
        errorMessage: this.getErrorMessage(error),
        remark: input.remark,
      });
      throw error;
    }
  }

  private async applyLocalOrderAction(
    context: Awaited<ReturnType<OrdersService['getOrderOperationContext']>>,
    action: LocalOrderAction,
    remark: string | null,
  ) {
    const { order, fulfillment } = context;
    if (action === 'remark') {
      order.remark = remark;
      await this.ordersRepository.save(order);
      return;
    }

    if (action === 'close') {
      if (order.paymentStatus === 'paid') {
        throw new BadRequestException('已支付订单不能直接关闭，请走售后处理');
      }
      order.paymentStatus = 'closed';
      order.fulfillmentStatus = 'canceled';
      order.closedAt = new Date();
      fulfillment.status = 'canceled';
      fulfillment.errorMessage = remark;
      await Promise.all([
        this.ordersRepository.save(order),
        this.fulfillmentsRepository.save(fulfillment),
      ]);
      return;
    }

    if (order.paymentStatus !== 'paid') {
      throw new BadRequestException('订单未支付，不能处理履约');
    }

    if (action === 'process') {
      order.fulfillmentStatus = 'processing';
      fulfillment.status = 'processing';
      fulfillment.submittedAt = fulfillment.submittedAt ?? new Date();
    }
    if (action === 'complete') {
      order.fulfillmentStatus = 'completed';
      fulfillment.status = 'completed';
      fulfillment.submittedAt = fulfillment.submittedAt ?? new Date();
      fulfillment.completedAt = new Date();
    }
    if (action === 'cancel') {
      order.fulfillmentStatus = 'canceled';
      fulfillment.status = 'canceled';
      fulfillment.errorMessage = remark;
    }

    await Promise.all([
      this.ordersRepository.save(order),
      this.fulfillmentsRepository.save(fulfillment),
    ]);
  }

  private buildThirdPartyActionPayload(
    action: ThirdPartyOrderAction,
    context: Awaited<ReturnType<OrdersService['getOrderOperationContext']>>,
    dto: ThirdPartyOrderActionDto,
  ) {
    const payload = this.buildThirdPartyOrderIdentityPayload(context, dto, false);
    if (action === 'gaimi') {
      const newPwd =
        this.normalizeOptionalString(dto.newPwd) ??
        this.normalizeOptionalString((dto as UnknownRecord).password);
      if (newPwd) {
        payload.newPwd = newPwd;
      }
      this.assignIfPresent(payload, 'remark', dto.remark);
      this.assignIfPresent(payload, 'city', dto.city);
      this.assignIfPresent(payload, 'tag', dto.tag);
      this.assignIfPresent(payload, 'config', dto.config);
      const autoReset = this.normalizeAutoReset(dto.autoReset);
      if (autoReset !== null) {
        payload.autoReset = autoReset;
      }
    }
    if (action === 'convert') {
      payload.convertToClassId = this.normalizeRequiredString(
        dto.convertToClassId,
        '目标课程 ID 不能为空',
      );
    }
    if (action === 'update-time') {
      payload.time = this.normalizePositiveNumber(dto.time, '时长不能为空');
    }
    if (action === 'update-cycle') {
      payload.cycle = this.normalizePositiveNumber(dto.cycle, '周期不能为空');
    }
    return payload;
  }

  private buildThirdPartyOrderListPayload(dto: ThirdPartyOrderListSyncDto) {
    return {
      page: this.normalizePositiveInteger(dto.page, 1, 1_000, '页码不正确'),
      limit: this.normalizePositiveInteger(
        dto.limit,
        100,
        500,
        '每页数量不正确',
      ),
      recent: this.normalizePositiveInteger(
        dto.recent,
        5,
        365,
        '近几天参数不正确',
      ),
    };
  }

  private readRemoteOrderNo(item: UnknownRecord) {
    return this.readOptionalString(item, [
      'id',
      'oid',
      'order_id',
      'orderNo',
      'order_no',
      'third_order_no',
      'thirdOrderNo',
    ]);
  }

  private mapThirdPartyOrderStatus(item: UnknownRecord): FulfillmentStatus | null {
    const status = this.readOptionalString(item, [
      'status',
      'state',
      'process',
      'progress',
      'order_status',
      'orderStatus',
    ]);
    if (!status) {
      return null;
    }

    const normalized = status.toLowerCase();
    if (/完成|已完成|完结|success|completed|done/.test(normalized)) {
      return 'completed';
    }
    if (/失败|异常|错误|fail|failed|error/.test(normalized)) {
      return 'failed';
    }
    if (/取消|暂停|停止|关闭|cancel|canceled|stop|stopped|closed/.test(normalized)) {
      return 'canceled';
    }
    if (/提交|已提交|submitted/.test(normalized)) {
      return 'submitted';
    }
    if (/处理|进行|学习|运行|processing|running|working/.test(normalized)) {
      return 'processing';
    }
    return null;
  }

  private readThirdPartyOrderUsername(item: UnknownRecord) {
    return this.readOptionalString(item, [
      'username',
      'user',
      'account',
      'studentAccount',
      'student_account',
    ]);
  }

  private async importThirdPartyOrderFromRecord(input: {
    actor: User | null;
    provider: string;
    remoteOrderNo: string;
    requestPayload: UnknownRecord;
    record: UnknownRecord;
    maskedRecord: UnknownRecord;
    now: Date;
  }) {
    const order = this.ordersRepository.create({
      id: randomUUID(),
      orderNo: await this.generateOrderNo(),
      userId: input.actor?.id ?? 'system',
      sourceType: THIRD_PARTY_SOURCE,
      provider: input.provider,
      totalAmount: this.readThirdPartyOrderAmount(input.record),
      payableAmount: this.readThirdPartyOrderAmount(input.record),
      paidAmount: this.readThirdPartyOrderAmount(input.record),
      paymentStatus: 'paid',
      fulfillmentStatus:
        this.mapThirdPartyOrderStatus(input.record) ?? 'processing',
      remark: `第三方订单同步导入：${input.remoteOrderNo}`,
      paidAt: input.now,
      closedAt: null,
    });
    await this.ordersRepository.save(order);

    const orderItem = this.orderItemsRepository.create({
      id: randomUUID(),
      orderId: order.id,
      productId: randomUUID(),
      productName:
        this.readOptionalString(input.record, [
          'course',
          'courseName',
          'course_name',
          'kcname',
          'name',
          'title',
        ]) ?? `第三方订单 ${input.remoteOrderNo}`,
      sourceType: THIRD_PARTY_SOURCE,
      externalProductId: this.readOptionalString(input.record, [
        'cid',
        'classId',
        'class_id',
        'courseId',
        'course_id',
        'goodsId',
        'goods_id',
      ]),
      provider: input.provider,
      unitPrice: this.readThirdPartyOrderAmount(input.record),
      quantity: 1,
      totalAmount: this.readThirdPartyOrderAmount(input.record),
      payload: {
        externalOrderNo: input.remoteOrderNo,
        username: this.readThirdPartyOrderUsername(input.record),
        school: this.readOptionalString(input.record, [
          'school',
          'schoolName',
          'xuexiao',
          'xx',
        ]),
        raw: input.maskedRecord,
      },
      encryptedPayload: null,
      createdAt: input.now,
    });
    await this.orderItemsRepository.save(orderItem);

    const fulfillment = this.fulfillmentsRepository.create({
      id: randomUUID(),
      orderId: order.id,
      orderItemId: orderItem.id,
      fulfillmentType: 'third_party_api',
      status: order.fulfillmentStatus,
      provider: input.provider,
      externalOrderNo: input.remoteOrderNo,
      requestPayload: input.requestPayload,
      responsePayload: input.maskedRecord,
      errorMessage: null,
      submittedAt: input.now,
      completedAt: order.fulfillmentStatus === 'completed' ? input.now : null,
    });
    await this.fulfillmentsRepository.save(fulfillment);

    await this.writeOrderAction({
      actor: input.actor,
      order,
      action: 'third_party.orders.import',
      status: 'success',
      requestPayload: input.requestPayload,
      responsePayload: input.maskedRecord,
      remark: '同步第三方订单列表时导入本地订单',
    });

    return { order, fulfillment };
  }

  private async ensureThirdPartyFulfillmentForImportedOrder(input: {
    order: Order;
    provider: string;
    remoteOrderNo: string;
    requestPayload: UnknownRecord;
    maskedRecord: UnknownRecord;
    now: Date;
  }) {
    const [existingItem] = await this.orderItemsRepository.find({
      where: { orderId: input.order.id },
      order: { createdAt: 'ASC' },
      take: 1,
    });
    const item =
      existingItem ??
      this.orderItemsRepository.create({
        id: randomUUID(),
        orderId: input.order.id,
        productId: randomUUID(),
        productName: `第三方订单 ${input.remoteOrderNo}`,
        sourceType: THIRD_PARTY_SOURCE,
        externalProductId: null,
        provider: input.provider,
        unitPrice: input.order.totalAmount,
        quantity: 1,
        totalAmount: input.order.totalAmount,
        payload: {
          externalOrderNo: input.remoteOrderNo,
          raw: input.maskedRecord,
        },
        encryptedPayload: null,
        createdAt: input.now,
      });
    if (!existingItem) {
      await this.orderItemsRepository.save(item);
    }

    const fulfillment = this.fulfillmentsRepository.create({
      id: randomUUID(),
      orderId: input.order.id,
      orderItemId: item.id,
      fulfillmentType: 'third_party_api',
      status: input.order.fulfillmentStatus,
      provider: input.provider,
      externalOrderNo: input.remoteOrderNo,
      requestPayload: input.requestPayload,
      responsePayload: input.maskedRecord,
      errorMessage: null,
      submittedAt: input.now,
      completedAt:
        input.order.fulfillmentStatus === 'completed' ? input.now : null,
    });
    await this.fulfillmentsRepository.save(fulfillment);
    return fulfillment;
  }

  private readThirdPartyOrderAmount(item: UnknownRecord) {
    const value = this.readOptionalString(item, [
      'amount',
      'price',
      'money',
      'totalAmount',
      'total_amount',
      'payableAmount',
      'payable_amount',
    ]);
    const amount = Number(value ?? 0);
    return Number.isFinite(amount) && amount >= 0 ? amount.toFixed(2) : '0.00';
  }

  private buildThirdPartyOrderIdentityPayload(
    context: Awaited<ReturnType<OrdersService['getOrderOperationContext']>>,
    dto: ThirdPartyOrderActionDto,
    allowUsername: boolean,
  ) {
    const payload: UnknownRecord = {};
    const externalOrderNo =
      this.normalizeOptionalString(dto.externalOrderNo) ??
      this.normalizeOptionalString(context.fulfillment.externalOrderNo);
    if (externalOrderNo) {
      payload.id = externalOrderNo;
      return payload;
    }

    if (allowUsername) {
      const username =
        this.normalizeOptionalString(dto.username) ??
        this.readOrderAccount(context.item.payload);
      if (username) {
        payload.username = username;
        return payload;
      }
    }

    throw new BadRequestException('缺少第三方订单 ID');
  }

  private async getOrderOperationContext(orderId: string) {
    const order = await this.ordersRepository.findOneByOrFail({ id: orderId });
    const [item] = await this.orderItemsRepository.find({
      where: { orderId },
      order: { createdAt: 'ASC' },
      take: 1,
    });
    if (!item) {
      throw new BadRequestException('订单明细不存在');
    }

    let [fulfillment] = await this.fulfillmentsRepository.find({
      where: { orderId },
      order: { createdAt: 'ASC' },
      take: 1,
    });
    if (!fulfillment) {
      fulfillment = this.fulfillmentsRepository.create({
        id: randomUUID(),
        orderId: order.id,
        orderItemId: item.id,
        fulfillmentType:
          order.sourceType === THIRD_PARTY_SOURCE
            ? 'third_party_api'
            : 'manual',
        status: order.fulfillmentStatus,
        provider: order.provider,
        externalOrderNo: null,
        requestPayload: null,
        responsePayload: null,
        errorMessage: null,
        submittedAt: null,
        completedAt: null,
      });
      await this.fulfillmentsRepository.save(fulfillment);
    }

    return { order, item, fulfillment };
  }

  private async writeOrderAction(input: {
    actor: User | null;
    order: Order;
    action: string;
    status: 'success' | 'failed';
    requestPayload: UnknownRecord | null;
    responsePayload: unknown | null;
    errorMessage?: string | null;
    remark?: string | null;
  }) {
    const action = this.orderActionsRepository.create({
      id: randomUUID(),
      orderId: input.order.id,
      action: input.action,
      sourceType: input.order.sourceType,
      provider: input.order.provider,
      status: input.status,
      operatorId: input.actor?.id ?? null,
      requestPayload: input.requestPayload,
      responsePayload: input.responsePayload,
      errorMessage: input.errorMessage ?? null,
      remark: input.remark ?? null,
    });
    await this.orderActionsRepository.save(action);
  }

  private assertThirdPartyOrder(order: { sourceType: string }) {
    if (order.sourceType !== THIRD_PARTY_SOURCE) {
      throw new BadRequestException('只有第三方订单支持该操作');
    }
  }

  private readOrderAccount(payload: OrderItem['payload']) {
    if (!payload) {
      return null;
    }
    return this.normalizeOptionalString(payload.account ?? payload.user);
  }

  private assignIfPresent(
    payload: UnknownRecord,
    key: string,
    value: unknown,
  ) {
    if (value !== undefined && value !== null && value !== '') {
      payload[key] = value;
    }
  }

  private normalizeAutoReset(value: unknown) {
    if (value === undefined || value === null || value === '') {
      return null;
    }
    return ['1', 'true', 'yes', 'on'].includes(
      String(value).trim().toLowerCase(),
    )
      ? 1
      : 0;
  }

  private normalizePositiveNumber(value: unknown, message: string) {
    const normalized = Number(value);
    if (!Number.isFinite(normalized) || normalized <= 0) {
      throw new BadRequestException(message);
    }
    return normalized;
  }

  private normalizePositiveInteger(
    value: unknown,
    fallback: number,
    max: number,
    message: string,
  ) {
    if (value === undefined || value === null || value === '') {
      return fallback;
    }
    const normalized = Number(value);
    if (
      !Number.isInteger(normalized) ||
      normalized <= 0 ||
      normalized > max
    ) {
      throw new BadRequestException(message);
    }
    return normalized;
  }

  private async getProduct(id?: string) {
    const productId = this.normalizeRequiredString(id, '商品不能为空');
    const product = await this.productsRepository.findOne({
      where: { id: productId },
    });
    if (!product) {
      throw new NotFoundException('商品不存在');
    }
    return product;
  }

  private assertPurchasable(product: Course) {
    if (!product.enabled || product.syncStatus === 'removed') {
      throw new BadRequestException('商品未上架，不能下单');
    }
    if (product.stock !== null && product.stock <= 0) {
      throw new BadRequestException('商品库存不足');
    }
  }

  private validateOrderPayload(
    schema: OrderFormField[],
    payload: UnknownRecord,
  ) {
    for (const field of schema) {
      if (!field.required) {
        continue;
      }
      const value = payload[field.key];
      if (value === undefined || value === null || String(value).trim() === '') {
        throw new BadRequestException(`${field.label}不能为空`);
      }
    }
  }

  private splitSensitivePayload(payload: UnknownRecord) {
    const publicPayload: UnknownRecord = {};
    const sensitivePayload: UnknownRecord = {};

    for (const [key, value] of Object.entries(payload)) {
      if (SENSITIVE_ORDER_KEYS.has(key)) {
        sensitivePayload[key] = value;
        publicPayload[key] = this.maskText(value);
      } else {
        publicPayload[key] = value;
      }
    }

    return {
      publicPayload: maskSensitivePayload(publicPayload),
      encryptedPayload: Object.fromEntries(
        Object.entries(sensitivePayload).map(([key, value]) => [
          key,
          this.encryptValue(value),
        ]),
      ),
    };
  }

  private decryptPayload(payload: OrderItem['encryptedPayload']) {
    if (!payload) {
      return {};
    }

    return Object.fromEntries(
      Object.entries(payload).map(([key, value]) => [
        key,
        this.decryptValue(String(value)),
      ]),
    );
  }

  private encryptValue(value: unknown) {
    const key = this.getEncryptionKey();
    const iv = randomBytes(12);
    const cipher = createCipheriv('aes-256-gcm', key, iv);
    const encrypted = Buffer.concat([
      cipher.update(String(value ?? ''), 'utf8'),
      cipher.final(),
    ]);
    const tag = cipher.getAuthTag();
    return [
      iv.toString('base64url'),
      tag.toString('base64url'),
      encrypted.toString('base64url'),
    ].join('.');
  }

  private decryptValue(value: string) {
    const [ivText, tagText, encryptedText] = value.split('.');
    if (!ivText || !tagText || !encryptedText) {
      throw new BadRequestException('订单敏感信息格式不正确');
    }
    const decipher = createDecipheriv(
      'aes-256-gcm',
      this.getEncryptionKey(),
      Buffer.from(ivText, 'base64url'),
    );
    decipher.setAuthTag(Buffer.from(tagText, 'base64url'));
    return Buffer.concat([
      decipher.update(Buffer.from(encryptedText, 'base64url')),
      decipher.final(),
    ]).toString('utf8');
  }

  private getEncryptionKey() {
    const secret = this.configService.getOrThrow<string>('AUTH_SECRET');
    return Buffer.from(secret.padEnd(32, secret).slice(0, 32));
  }

  private extractCourseRecords(response: unknown) {
    const records = this.extractRecords(response);
    return records.map((item, index) => ({
      courseId: this.readOptionalString(item, [
        'id',
        'kcid',
        'cid',
        'course_id',
        'class_id',
        'hash',
      ]),
      courseName:
        this.readOptionalString(item, ['name', 'kcname', 'title', 'label']) ??
        `课程 ${index + 1}`,
      raw: maskSensitivePayload(item),
    }));
  }

  private extractRecords(response: unknown): UnknownRecord[] {
    const value = this.unwrapResponse(response);
    if (Array.isArray(value)) {
      return value.filter(this.isRecord);
    }
    if (this.isRecord(value)) {
      return Object.entries(value).map(([key, item]) =>
        this.isRecord(item)
          ? { id: key, ...item }
          : { id: key, name: item },
      );
    }
    return [];
  }

  private unwrapResponse(response: unknown): unknown {
    if (!this.isRecord(response)) {
      return response;
    }
    return response.data ?? response.rows ?? response.list ?? response.result ?? response;
  }

  private normalizeSelectedCourse(value?: SelectedCourseDto | null) {
    if (!value || typeof value !== 'object') {
      return null;
    }
    const courseName = this.normalizeOptionalString(value.courseName);
    return {
      courseName,
      courseId: this.normalizeOptionalString(value.courseId),
      raw: this.normalizeRecord(value.raw),
    };
  }

  private normalizeRecord(value: unknown): UnknownRecord | null {
    if (value === undefined || value === null || value === '') {
      return null;
    }
    if (!this.isRecord(value)) {
      throw new BadRequestException('扩展信息格式不正确');
    }
    return value;
  }

  private normalizeRequiredString(value: unknown, message: string) {
    const normalized = String(value ?? '').trim();
    if (!normalized) {
      throw new BadRequestException(message);
    }
    return normalized;
  }

  private normalizeOptionalString(value: unknown) {
    const normalized = String(value ?? '').trim();
    return normalized || null;
  }

  private normalizeQuantity(value: unknown) {
    const quantity = Number(value ?? 1);
    if (!Number.isInteger(quantity) || quantity <= 0 || quantity > 999) {
      throw new BadRequestException('购买数量不正确');
    }
    return quantity;
  }

  private normalizeSourceType(sourceType?: OrderListQuery['sourceType']) {
    if (!sourceType || sourceType === SOURCE_ALL) {
      return null;
    }
    if (sourceType === THIRD_PARTY_SOURCE || sourceType === SELF_OWNED_SOURCE) {
      return sourceType;
    }
    throw new BadRequestException('订单来源不正确');
  }

  private normalizePaymentStatus(value?: string) {
    if (!value || value === SOURCE_ALL) {
      return null;
    }
    if (PAYMENT_STATUSES.has(value as PaymentStatus)) {
      return value as PaymentStatus;
    }
    throw new BadRequestException('支付状态不正确');
  }

  private normalizeFulfillmentStatus(value?: string) {
    if (!value || value === SOURCE_ALL) {
      return null;
    }
    if (FULFILLMENT_STATUSES.has(value as FulfillmentStatus)) {
      return value as FulfillmentStatus;
    }
    throw new BadRequestException('履约状态不正确');
  }

  private getThirdPartyPlatform(product: Course) {
    return this.normalizeRequiredString(
      product.remoteCategoryId ?? product.externalId,
      '第三方项目 ID 缺失',
    );
  }

  private toProductSummary(product: Course) {
    return {
      id: product.id,
      name: product.name,
      sourceType: product.sourceType,
      provider: product.provider,
      remoteCategoryId: product.remoteCategoryId,
      remoteCourseId: product.remoteCourseId,
      price: product.price,
    };
  }

  private getPagination(query: OrderListQuery) {
    return {
      page: Math.max(Number(query.page || 1), 1),
      pageSize: Math.min(Math.max(Number(query.pageSize || 10), 1), 100),
    };
  }

  private async getItemsByOrderIds(orderIds: string[]) {
    if (!orderIds.length) {
      return new Map<string, OrderItem[]>();
    }

    const items = await this.orderItemsRepository
      .createQueryBuilder('item')
      .where('item.orderId IN (:...orderIds)', { orderIds })
      .orderBy('item.createdAt', 'ASC')
      .getMany();
    const map = new Map<string, OrderItem[]>();
    for (const item of items) {
      map.set(item.orderId, [...(map.get(item.orderId) ?? []), item]);
    }
    return map;
  }

  private async generateOrderNo() {
    const date = new Date();
    const dateText = [
      date.getFullYear(),
      String(date.getMonth() + 1).padStart(2, '0'),
      String(date.getDate()).padStart(2, '0'),
    ].join('');
    for (let index = 0; index < 5; index += 1) {
      const orderNo = `ORD${dateText}${String(Date.now()).slice(-6)}${randomBytes(2).toString('hex').toUpperCase()}`;
      const exists = await this.ordersRepository.exist({ where: { orderNo } });
      if (!exists) {
        return orderNo;
      }
    }
    throw new BadRequestException('生成订单号失败，请重试');
  }

  private extractExternalOrderNo(response: unknown) {
    const value = this.unwrapResponse(response);
    if (!this.isRecord(value)) {
      return null;
    }
    return this.readOptionalString(value, [
      'id',
      'oid',
      'order_id',
      'orderNo',
      'order_no',
      'third_order_no',
    ]);
  }

  private readOptionalString(item: UnknownRecord, keys: string[]) {
    for (const key of keys) {
      const value = item[key];
      if (value !== undefined && value !== null && String(value).trim()) {
        return String(value).trim();
      }
    }
    return null;
  }

  private maskText(value: unknown) {
    const text = String(value ?? '');
    if (!text) {
      return '';
    }
    if (text.length <= 4) {
      return '****';
    }
    return `${text.slice(0, 2)}****${text.slice(-2)}`;
  }

  private isRecord(value: unknown): value is UnknownRecord {
    return !!value && typeof value === 'object' && !Array.isArray(value);
  }

  private getErrorMessage(error: unknown) {
    if (error instanceof Error) {
      return error.message;
    }
    return String(error);
  }
}
