import { HttpStatus } from '@nestjs/common';
import { type EscrowStatus } from '@prisma/client';
import { AggregateRoot, DomainException, ErrorCode, Result } from '@modules/shared';
import { EscrowDisputedEvent } from '../events/escrow-disputed.event';
import { EscrowHeldEvent } from '../events/escrow-held.event';
import { EscrowReleasedEvent } from '../events/escrow-released.event';
import { type Money } from '../value-objects/money.vo';

export interface EscrowProps {
  orderId: string;
  amount: Money;
  fee: Money;
  status: EscrowStatus;
  heldAt: Date | null;
  releasedAt: Date | null;
  disputeReason: string | null;
  disputedAt: Date | null;
}

export class EscrowEntity extends AggregateRoot<string> {
  private _orderId: string;
  private _amount: Money;
  private _fee: Money;
  private _status: EscrowStatus;
  private _heldAt: Date | null;
  private _releasedAt: Date | null;
  private _disputeReason: string | null;
  private _disputedAt: Date | null;

  constructor(id: string, props: EscrowProps, createdAt?: Date, updatedAt?: Date) {
    super(id, createdAt, updatedAt);
    this._orderId = props.orderId;
    this._amount = props.amount;
    this._fee = props.fee;
    this._status = props.status;
    this._heldAt = props.heldAt;
    this._releasedAt = props.releasedAt;
    this._disputeReason = props.disputeReason;
    this._disputedAt = props.disputedAt;
  }

  get orderId(): string { return this._orderId; }
  get amount(): Money { return this._amount; }
  get fee(): Money { return this._fee; }
  get status(): EscrowStatus { return this._status; }
  get heldAt(): Date | null { return this._heldAt; }
  get releasedAt(): Date | null { return this._releasedAt; }
  get disputeReason(): string | null { return this._disputeReason; }
  get disputedAt(): Date | null { return this._disputedAt; }

  /** Net amount paid to seller (amount - fee). */
  get netPayout(): bigint {
    return this._amount.value - this._fee.value;
  }

  static createNew(
    id: string,
    orderId: string,
    amount: Money,
    fee: Money,
  ): EscrowEntity {
    return new EscrowEntity(id, {
      orderId,
      amount,
      fee,
      status: 'PENDING',
      heldAt: null,
      releasedAt: null,
      disputeReason: null,
      disputedAt: null,
    });
  }

  hold(): Result<void, DomainException> {
    if (this._status !== 'PENDING') {
      return Result.err(
        new DomainException(
          ErrorCode.ESCROW_INVALID_STATE,
          `Không thể giữ ký quỹ ở trạng thái ${this._status}`,
          HttpStatus.CONFLICT,
        ),
      );
    }
    this._status = 'HELD';
    this._heldAt = new Date();
    this.updatedAt = new Date();

    this.addDomainEvent(
      new EscrowHeldEvent(this.id, this._orderId, this._amount.value),
    );
    return Result.ok(undefined);
  }

  release(): Result<void, DomainException> {
    if (this._status !== 'HELD' && this._status !== 'DISPUTED') {
      return Result.err(
        new DomainException(
          ErrorCode.ESCROW_INVALID_STATE,
          `Không thể giải phóng ký quỹ ở trạng thái ${this._status}`,
          HttpStatus.CONFLICT,
        ),
      );
    }
    this._status = 'RELEASED';
    this._releasedAt = new Date();
    this.updatedAt = new Date();

    this.addDomainEvent(
      new EscrowReleasedEvent(this.id, this._orderId, this.netPayout),
    );
    return Result.ok(undefined);
  }

  dispute(reason: string): Result<void, DomainException> {
    if (this._status !== 'HELD') {
      return Result.err(
        new DomainException(
          ErrorCode.ESCROW_INVALID_STATE,
          `Không thể tranh chấp ký quỹ ở trạng thái ${this._status}`,
          HttpStatus.CONFLICT,
        ),
      );
    }
    this._status = 'DISPUTED';
    this._disputeReason = reason;
    this._disputedAt = new Date();
    this.updatedAt = new Date();

    this.addDomainEvent(
      new EscrowDisputedEvent(this.id, this._orderId, reason),
    );
    return Result.ok(undefined);
  }

  refund(): Result<void, DomainException> {
    if (this._status !== 'HELD' && this._status !== 'DISPUTED') {
      return Result.err(
        new DomainException(
          ErrorCode.ESCROW_INVALID_STATE,
          `Không thể hoàn tiền ký quỹ ở trạng thái ${this._status}`,
          HttpStatus.CONFLICT,
        ),
      );
    }
    this._status = 'REFUNDED';
    this.updatedAt = new Date();
    return Result.ok(undefined);
  }
}
