import { Money } from '../value-objects/money.vo';
import Decimal from 'decimal.js';

export enum OrderType {
  BUY = 'BUY',
  SELL = 'SELL',
}

export enum OrderStatus {
  PENDING = 'PENDING',
  PARTIAL = 'PARTIAL',
  FILLED = 'FILLED',
  CANCELLED = 'CANCELLED',
}

export enum OrderSource {
  USER = 'USER',
  MARKET_MAKER = 'MARKET_MAKER',
  DEX_BOT = 'DEX_BOT',
  SYSTEM = 'SYSTEM',
}

export interface TradeInfo {
  tradeNo: string;
  counterpartyOrderId: string;
  counterpartySequence: string;
  price: Money;
  quantity: Money;
  amount: Money;
  createdAt: Date;
}

export class OrderAggregate {
  private _id: string | null;
  private _orderNo: string;
  private _accountSequence: string;
  private _type: OrderType;
  private _status: OrderStatus;
  private _source: OrderSource;
  private _sourceLabel: string | null;
  private _price: Money;
  private _quantity: Money;
  private _filledQuantity: Money;
  private _remainingQuantity: Money;
  private _averagePrice: Money;
  private _totalAmount: Money;
  private _trades: TradeInfo[] = [];
  private _createdAt: Date;
  private _cancelledAt: Date | null = null;
  private _completedAt: Date | null = null;

  private constructor(
    orderNo: string,
    accountSequence: string,
    type: OrderType,
    price: Money,
    quantity: Money,
    source: OrderSource = OrderSource.USER,
    sourceLabel: string | null = null,
    id: string | null = null,
  ) {
    this._id = id;
    this._orderNo = orderNo;
    this._accountSequence = accountSequence;
    this._type = type;
    this._status = OrderStatus.PENDING;
    this._source = source;
    this._sourceLabel = sourceLabel;
    this._price = price;
    this._quantity = quantity;
    this._filledQuantity = Money.zero();
    this._remainingQuantity = quantity;
    this._averagePrice = Money.zero();
    this._totalAmount = Money.zero();
    this._createdAt = new Date();
  }

  static create(
    orderNo: string,
    accountSequence: string,
    type: OrderType,
    price: Money,
    quantity: Money,
    source: OrderSource = OrderSource.USER,
    sourceLabel: string | null = null,
  ): OrderAggregate {
    if (price.isZero()) {
      throw new Error('Price cannot be zero');
    }
    if (quantity.isZero()) {
      throw new Error('Quantity cannot be zero');
    }
    return new OrderAggregate(orderNo, accountSequence, type, price, quantity, source, sourceLabel);
  }

  static reconstitute(props: {
    id: string;
    orderNo: string;
    accountSequence: string;
    type: OrderType;
    status: OrderStatus;
    source: OrderSource;
    sourceLabel: string | null;
    price: Money;
    quantity: Money;
    filledQuantity: Money;
    remainingQuantity: Money;
    averagePrice: Money;
    totalAmount: Money;
    createdAt: Date;
    cancelledAt: Date | null;
    completedAt: Date | null;
  }): OrderAggregate {
    const order = new OrderAggregate(
      props.orderNo,
      props.accountSequence,
      props.type,
      props.price,
      props.quantity,
      props.source,
      props.sourceLabel,
      props.id,
    );
    order._status = props.status;
    order._filledQuantity = props.filledQuantity;
    order._remainingQuantity = props.remainingQuantity;
    order._averagePrice = props.averagePrice;
    order._totalAmount = props.totalAmount;
    order._createdAt = props.createdAt;
    order._cancelledAt = props.cancelledAt;
    order._completedAt = props.completedAt;
    return order;
  }

  // Getters
  get id(): string | null { return this._id; }
  get orderNo(): string { return this._orderNo; }
  get accountSequence(): string { return this._accountSequence; }
  get type(): OrderType { return this._type; }
  get status(): OrderStatus { return this._status; }
  get source(): OrderSource { return this._source; }
  get sourceLabel(): string | null { return this._sourceLabel; }
  get price(): Money { return this._price; }
  get quantity(): Money { return this._quantity; }
  get filledQuantity(): Money { return this._filledQuantity; }
  get remainingQuantity(): Money { return this._remainingQuantity; }
  get averagePrice(): Money { return this._averagePrice; }
  get totalAmount(): Money { return this._totalAmount; }
  get trades(): TradeInfo[] { return [...this._trades]; }
  get createdAt(): Date { return this._createdAt; }
  get cancelledAt(): Date | null { return this._cancelledAt; }
  get completedAt(): Date | null { return this._completedAt; }

  get lockedAmount(): Money {
    if (this._type === OrderType.BUY) {
      return this._remainingQuantity.multiply(this._price.value);
    }
    return this._remainingQuantity;
  }

  fill(trade: TradeInfo): void {
    if (this._status === OrderStatus.CANCELLED || this._status === OrderStatus.FILLED) {
      throw new Error('Cannot fill a cancelled or filled order');
    }

    if (trade.quantity.isGreaterThan(this._remainingQuantity)) {
      throw new Error('Trade quantity exceeds remaining quantity');
    }

    this._trades.push(trade);
    this._filledQuantity = this._filledQuantity.add(trade.quantity);
    this._remainingQuantity = this._remainingQuantity.subtract(trade.quantity);
    this._totalAmount = this._totalAmount.add(trade.amount);

    // 重新计算平均价格
    this._averagePrice = new Money(
      this._totalAmount.value.dividedBy(this._filledQuantity.value),
    );

    if (this._remainingQuantity.isZero()) {
      this._status = OrderStatus.FILLED;
      this._completedAt = new Date();
    } else {
      this._status = OrderStatus.PARTIAL;
    }
  }

  cancel(): void {
    if (this._status === OrderStatus.FILLED) {
      throw new Error('Cannot cancel a filled order');
    }
    if (this._status === OrderStatus.CANCELLED) {
      throw new Error('Order is already cancelled');
    }

    this._status = OrderStatus.CANCELLED;
    this._cancelledAt = new Date();
  }

  canMatch(other: OrderAggregate): boolean {
    if (this._type === other._type) return false;
    if (this._status !== OrderStatus.PENDING && this._status !== OrderStatus.PARTIAL) return false;
    if (other._status !== OrderStatus.PENDING && other._status !== OrderStatus.PARTIAL) return false;

    if (this._type === OrderType.BUY) {
      return this._price.isGreaterThanOrEqual(other._price);
    } else {
      return this._price.value.lessThanOrEqualTo(other._price.value);
    }
  }
}
