import {
  PlantingOrder as PrismaPlantingOrder,
  FundAllocation as PrismaFundAllocation,
  Prisma,
} from '@prisma/client';
import {
  PlantingOrder,
  PlantingOrderData,
} from '../../../domain/aggregates/planting-order.aggregate';
import { PlantingOrderStatus } from '../../../domain/value-objects/planting-order-status.enum';
import { FundAllocation } from '../../../domain/value-objects/fund-allocation.vo';
import { FundAllocationTargetType } from '../../../domain/value-objects/fund-allocation-target-type.enum';

type PlantingOrderWithAllocations = PrismaPlantingOrder & {
  fundAllocations?: PrismaFundAllocation[];
};

export class PlantingOrderMapper {
  static toDomain(prismaOrder: PlantingOrderWithAllocations): PlantingOrder {
    const data: PlantingOrderData = {
      id: prismaOrder.id,
      orderNo: prismaOrder.orderNo,
      userId: prismaOrder.userId,
      accountSequence: prismaOrder.accountSequence,
      treeCount: prismaOrder.treeCount,
      totalAmount: Number(prismaOrder.totalAmount),
      priceSupplement: prismaOrder.priceSupplement ?? 0,
      status: prismaOrder.status as PlantingOrderStatus,
      selectedProvince: prismaOrder.selectedProvince,
      selectedCity: prismaOrder.selectedCity,
      provinceCitySelectedAt: prismaOrder.provinceCitySelectedAt,
      provinceCityConfirmedAt: prismaOrder.provinceCityConfirmedAt,
      poolInjectionBatchId: prismaOrder.poolInjectionBatchId,
      poolInjectionScheduledTime: prismaOrder.poolInjectionScheduledTime,
      poolInjectionActualTime: prismaOrder.poolInjectionActualTime,
      poolInjectionTxHash: prismaOrder.poolInjectionTxHash,
      miningEnabledAt: prismaOrder.miningEnabledAt,
      createdAt: prismaOrder.createdAt,
      paidAt: prismaOrder.paidAt,
      fundAllocatedAt: prismaOrder.fundAllocatedAt,
    };

    return PlantingOrder.reconstitute(data);
  }

  static toPersistence(order: PlantingOrder): {
    orderData: {
      id?: bigint;
      orderNo: string;
      userId: bigint;
      accountSequence: string;
      treeCount: number;
      totalAmount: Prisma.Decimal;
      selectedProvince: string | null;
      selectedCity: string | null;
      provinceCitySelectedAt: Date | null;
      provinceCityConfirmedAt: Date | null;
      status: string;
      poolInjectionBatchId: bigint | null;
      poolInjectionScheduledTime: Date | null;
      poolInjectionActualTime: Date | null;
      poolInjectionTxHash: string | null;
      miningEnabledAt: Date | null;
      createdAt: Date;
      paidAt: Date | null;
      fundAllocatedAt: Date | null;
    };
    allocations: {
      orderId: bigint;
      targetType: string;
      amount: Prisma.Decimal;
      targetAccountId: string | null;
      metadata: Prisma.InputJsonValue | null;
    }[];
  } {
    const data = order.toPersistence();

    const orderData = {
      id: data.id,
      orderNo: data.orderNo,
      userId: data.userId,
      accountSequence: data.accountSequence,
      treeCount: data.treeCount,
      totalAmount: new Prisma.Decimal(data.totalAmount),
      priceSupplement: data.priceSupplement ?? 0,
      selectedProvince: data.selectedProvince || null,
      selectedCity: data.selectedCity || null,
      provinceCitySelectedAt: data.provinceCitySelectedAt || null,
      provinceCityConfirmedAt: data.provinceCityConfirmedAt || null,
      status: data.status,
      poolInjectionBatchId: data.poolInjectionBatchId || null,
      poolInjectionScheduledTime: data.poolInjectionScheduledTime || null,
      poolInjectionActualTime: data.poolInjectionActualTime || null,
      poolInjectionTxHash: data.poolInjectionTxHash || null,
      miningEnabledAt: data.miningEnabledAt || null,
      createdAt: data.createdAt || new Date(),
      paidAt: data.paidAt || null,
      fundAllocatedAt: data.fundAllocatedAt || null,
    };

    const allocations = order.fundAllocations.map((a) => ({
      orderId: data.id!,
      targetType: a.targetType,
      amount: new Prisma.Decimal(a.amount),
      targetAccountId: a.targetAccountId,
      metadata: (a.metadata as Prisma.InputJsonValue) || null,
    }));

    return { orderData, allocations };
  }

  static mapFundAllocation(prisma: PrismaFundAllocation): FundAllocation {
    return new FundAllocation(
      prisma.targetType as FundAllocationTargetType,
      Number(prisma.amount),
      prisma.targetAccountId,
      prisma.metadata as Record<string, unknown> | undefined,
    );
  }
}
