import { Injectable } from '@nestjs/common';
import { type Prisma, type Order as PrismaOrder, type OrderStatus } from '@prisma/client';
import { PrismaService } from '@modules/shared';
import { OrderEntity, type OrderProps } from '../../domain/entities/order.entity';
import { type IOrderRepository } from '../../domain/repositories/order.repository';
import { Money } from '../../domain/value-objects/money.vo';
import { PlatformFee } from '../../domain/value-objects/platform-fee.vo';

@Injectable()
export class PrismaOrderRepository implements IOrderRepository {
  constructor(private readonly prisma: PrismaService) {}

  async findById(id: string): Promise<OrderEntity | null> {
    const order = await this.prisma.order.findUnique({ where: { id } });
    return order ? this.toDomain(order) : null;
  }

  async findByIdempotencyKey(key: string): Promise<OrderEntity | null> {
    const order = await this.prisma.order.findUnique({
      where: { idempotencyKey: key },
    });
    return order ? this.toDomain(order) : null;
  }

  async findByBuyerId(
    buyerId: string,
    options?: { status?: OrderStatus; limit?: number; offset?: number },
  ): Promise<{ items: OrderEntity[]; total: number }> {
    const where = {
      buyerId,
      ...(options?.status ? { status: options.status } : {}),
    };

    const [orders, total] = await Promise.all([
      this.prisma.order.findMany({
        where,
        orderBy: { createdAt: 'desc' },
        take: options?.limit ?? 20,
        skip: options?.offset ?? 0,
      }),
      this.prisma.order.count({ where }),
    ]);

    return { items: orders.map((o) => this.toDomain(o)), total };
  }

  async findBySellerId(
    sellerId: string,
    options?: { status?: OrderStatus; limit?: number; offset?: number },
  ): Promise<{ items: OrderEntity[]; total: number }> {
    const where = {
      sellerId,
      ...(options?.status ? { status: options.status } : {}),
    };

    const [orders, total] = await Promise.all([
      this.prisma.order.findMany({
        where,
        orderBy: { createdAt: 'desc' },
        take: options?.limit ?? 20,
        skip: options?.offset ?? 0,
      }),
      this.prisma.order.count({ where }),
    ]);

    return { items: orders.map((o) => this.toDomain(o)), total };
  }

  async save(entity: OrderEntity): Promise<void> {
    await this.prisma.order.create({
      data: {
        id: entity.id,
        buyerId: entity.buyerId,
        sellerId: entity.sellerId,
        listingId: entity.listingId,
        status: entity.status,
        amountVND: entity.amount.value,
        platformFeeVND: entity.platformFee.value,
        sellerPayoutVND: entity.sellerPayout.value,
        idempotencyKey: entity.idempotencyKey,
        metadata: entity.metadata as Prisma.InputJsonValue,
      },
    });
  }

  async update(entity: OrderEntity): Promise<void> {
    await this.prisma.order.update({
      where: { id: entity.id },
      data: {
        status: entity.status,
        metadata: entity.metadata as Prisma.InputJsonValue,
      },
    });
  }

  private toDomain(raw: PrismaOrder): OrderEntity {
    const amount = Money.create(raw.amountVND).unwrap();
    const fee = PlatformFee.create(raw.platformFeeVND).unwrap();
    const payout = Money.create(raw.sellerPayoutVND).unwrap();

    const props: OrderProps = {
      buyerId: raw.buyerId,
      sellerId: raw.sellerId,
      listingId: raw.listingId,
      status: raw.status,
      amount,
      platformFee: fee,
      sellerPayout: payout,
      idempotencyKey: raw.idempotencyKey,
      metadata: raw.metadata,
    };

    return new OrderEntity(raw.id, props, raw.createdAt, raw.updatedAt);
  }
}
