import Decimal from 'decimal.js';
import {
  UserId, WalletId, AssetType, WalletStatus, Money, Balance, Hashpower,
} from '@/domain/value-objects';
import {
  DomainEvent, DepositCompletedEvent, BalanceDeductedEvent,
  RewardAddedEvent, RewardMovedToSettleableEvent, RewardExpiredEvent,
  SettlementCompletedEvent,
} from '@/domain/events';
import {
  DomainError, InsufficientBalanceError, WalletFrozenError,
} from '@/shared/exceptions/domain.exception';

export interface WalletBalances {
  usdt: Balance;
  dst: Balance;
  bnb: Balance;
  og: Balance;
  rwad: Balance;
}

export interface WalletRewards {
  pendingUsdt: Money;
  pendingHashpower: Hashpower;
  pendingExpireAt: Date | null;
  settleableUsdt: Money;
  settleableHashpower: Hashpower;
  settledTotalUsdt: Money;
  settledTotalHashpower: Hashpower;
  expiredTotalUsdt: Money;
  expiredTotalHashpower: Hashpower;
}

export class WalletAccount {
  private readonly _walletId: WalletId;
  private readonly _accountSequence: string;  // 跨服务关联标识 (全局唯一业务ID)
  private readonly _userId: UserId;           // 保留兼容
  private _balances: WalletBalances;
  private _hashpower: Hashpower;
  private _rewards: WalletRewards;
  private _status: WalletStatus;
  private _hasPlanted: boolean;               // 是否已认种过
  private _version: number;                   // 乐观锁版本号
  private readonly _createdAt: Date;
  private _updatedAt: Date;
  private _domainEvents: DomainEvent[] = [];

  private constructor(
    walletId: WalletId,
    accountSequence: string,
    userId: UserId,
    balances: WalletBalances,
    hashpower: Hashpower,
    rewards: WalletRewards,
    status: WalletStatus,
    hasPlanted: boolean,
    version: number,
    createdAt: Date,
    updatedAt: Date,
  ) {
    this._walletId = walletId;
    this._accountSequence = accountSequence;
    this._userId = userId;
    this._balances = balances;
    this._hashpower = hashpower;
    this._rewards = rewards;
    this._status = status;
    this._hasPlanted = hasPlanted;
    this._version = version;
    this._createdAt = createdAt;
    this._updatedAt = updatedAt;
  }

  // Getters
  get walletId(): WalletId { return this._walletId; }
  get accountSequence(): string { return this._accountSequence; }
  get userId(): UserId { return this._userId; }
  get balances(): WalletBalances { return this._balances; }
  get hashpower(): Hashpower { return this._hashpower; }
  get rewards(): WalletRewards { return this._rewards; }
  get status(): WalletStatus { return this._status; }
  get version(): number { return this._version; }
  get createdAt(): Date { return this._createdAt; }
  get updatedAt(): Date { return this._updatedAt; }
  get hasPlanted(): boolean { return this._hasPlanted; }
  get isActive(): boolean { return this._status === WalletStatus.ACTIVE; }
  get domainEvents(): DomainEvent[] { return [...this._domainEvents]; }

  static createNew(accountSequence: string, userId: UserId): WalletAccount {
    const now = new Date();
    return new WalletAccount(
      WalletId.create(0), // Will be set by database
      accountSequence,
      userId,
      {
        usdt: Balance.zero('USDT'),
        dst: Balance.zero('DST'),
        bnb: Balance.zero('BNB'),
        og: Balance.zero('OG'),
        rwad: Balance.zero('RWAD'),
      },
      Hashpower.zero(),
      {
        pendingUsdt: Money.zero('USDT'),
        pendingHashpower: Hashpower.zero(),
        pendingExpireAt: null,
        settleableUsdt: Money.zero('USDT'),
        settleableHashpower: Hashpower.zero(),
        settledTotalUsdt: Money.zero('USDT'),
        settledTotalHashpower: Hashpower.zero(),
        expiredTotalUsdt: Money.zero('USDT'),
        expiredTotalHashpower: Hashpower.zero(),
      },
      WalletStatus.ACTIVE,
      false, // hasPlanted
      0,     // version (new wallet starts at 0)
      now,
      now,
    );
  }

  static reconstruct(params: {
    walletId: bigint;
    accountSequence: string;
    userId: bigint;
    usdtAvailable: Decimal;
    usdtFrozen: Decimal;
    dstAvailable: Decimal;
    dstFrozen: Decimal;
    bnbAvailable: Decimal;
    bnbFrozen: Decimal;
    ogAvailable: Decimal;
    ogFrozen: Decimal;
    rwadAvailable: Decimal;
    rwadFrozen: Decimal;
    hashpower: Decimal;
    pendingUsdt: Decimal;
    pendingHashpower: Decimal;
    pendingExpireAt: Date | null;
    settleableUsdt: Decimal;
    settleableHashpower: Decimal;
    settledTotalUsdt: Decimal;
    settledTotalHashpower: Decimal;
    expiredTotalUsdt: Decimal;
    expiredTotalHashpower: Decimal;
    status: string;
    hasPlanted: boolean;
    version: number;
    createdAt: Date;
    updatedAt: Date;
  }): WalletAccount {
    return new WalletAccount(
      WalletId.create(params.walletId),
      params.accountSequence,
      UserId.create(params.userId),
      {
        usdt: Balance.create(Money.USDT(params.usdtAvailable), Money.USDT(params.usdtFrozen)),
        dst: Balance.create(Money.DST(params.dstAvailable), Money.DST(params.dstFrozen)),
        bnb: Balance.create(Money.BNB(params.bnbAvailable), Money.BNB(params.bnbFrozen)),
        og: Balance.create(Money.OG(params.ogAvailable), Money.OG(params.ogFrozen)),
        rwad: Balance.create(Money.RWAD(params.rwadAvailable), Money.RWAD(params.rwadFrozen)),
      },
      Hashpower.create(params.hashpower),
      {
        pendingUsdt: Money.USDT(params.pendingUsdt),
        pendingHashpower: Hashpower.create(params.pendingHashpower),
        pendingExpireAt: params.pendingExpireAt,
        settleableUsdt: Money.USDT(params.settleableUsdt),
        settleableHashpower: Hashpower.create(params.settleableHashpower),
        settledTotalUsdt: Money.USDT(params.settledTotalUsdt),
        settledTotalHashpower: Hashpower.create(params.settledTotalHashpower),
        expiredTotalUsdt: Money.USDT(params.expiredTotalUsdt),
        expiredTotalHashpower: Hashpower.create(params.expiredTotalHashpower),
      },
      params.status as WalletStatus,
      params.hasPlanted,
      params.version,
      params.createdAt,
      params.updatedAt,
    );
  }

  // 标记为已认种
  markAsPlanted(): void {
    this._hasPlanted = true;
    this._updatedAt = new Date();
  }

  // 充值入账
  deposit(amount: Money, chainType: string, txHash: string): void {
    this.ensureActive();

    const balance = this.getBalance(amount.currency as AssetType);
    const newBalance = balance.add(amount);
    this.setBalance(amount.currency as AssetType, newBalance);
    this._updatedAt = new Date();

    this.addDomainEvent(new DepositCompletedEvent({
      userId: this._userId.toString(),
      walletId: this._walletId.toString(),
      amount: amount.value.toString(),
      assetType: amount.currency,
      chainType,
      txHash,
      balanceAfter: newBalance.available.value.toString(),
    }));
  }

  // 直接增加可用余额（用于系统账户分配）
  addAvailableBalance(amount: Money): void {
    this.ensureActive();

    const balance = this.getBalance(amount.currency as AssetType);
    const newBalance = balance.add(amount);
    this.setBalance(amount.currency as AssetType, newBalance);
    this._updatedAt = new Date();
  }

  // 扣款 (如认种支付)
  deduct(amount: Money, reason: string, refOrderId?: string): void {
    this.ensureActive();

    const balance = this.getBalance(amount.currency as AssetType);
    if (balance.available.lessThan(amount)) {
      throw new InsufficientBalanceError(
        amount.currency,
        amount.value.toString(),
        balance.available.value.toString(),
      );
    }

    const newBalance = balance.deduct(amount);
    this.setBalance(amount.currency as AssetType, newBalance);
    this._updatedAt = new Date();

    this.addDomainEvent(new BalanceDeductedEvent({
      userId: this._userId.toString(),
      walletId: this._walletId.toString(),
      amount: amount.value.toString(),
      assetType: amount.currency,
      reason,
      refOrderId,
      balanceAfter: newBalance.available.value.toString(),
    }));
  }

  // 冻结资金
  freeze(amount: Money): void {
    this.ensureActive();

    const balance = this.getBalance(amount.currency as AssetType);
    const newBalance = balance.freeze(amount);
    this.setBalance(amount.currency as AssetType, newBalance);
    this._updatedAt = new Date();
  }

  // 解冻资金
  unfreeze(amount: Money): void {
    this.ensureActive();

    const balance = this.getBalance(amount.currency as AssetType);
    const newBalance = balance.unfreeze(amount);
    this.setBalance(amount.currency as AssetType, newBalance);
    this._updatedAt = new Date();
  }

  // 从冻结余额扣款
  deductFrozen(amount: Money, reason: string, refOrderId?: string): void {
    this.ensureActive();

    const balance = this.getBalance(amount.currency as AssetType);
    const newBalance = balance.deductFrozen(amount);
    this.setBalance(amount.currency as AssetType, newBalance);
    this._updatedAt = new Date();

    this.addDomainEvent(new BalanceDeductedEvent({
      userId: this._userId.toString(),
      walletId: this._walletId.toString(),
      amount: amount.value.toString(),
      assetType: amount.currency,
      reason,
      refOrderId,
      balanceAfter: newBalance.available.value.toString(),
    }));
  }

  // 添加待领取奖励
  addPendingReward(usdtAmount: Money, hashpowerAmount: Hashpower, expireAt: Date, refOrderId?: string): void {
    this.ensureActive();

    this._rewards = {
      ...this._rewards,
      pendingUsdt: this._rewards.pendingUsdt.add(usdtAmount),
      pendingHashpower: this._rewards.pendingHashpower.add(hashpowerAmount),
      pendingExpireAt: expireAt,
    };
    this._updatedAt = new Date();

    this.addDomainEvent(new RewardAddedEvent({
      userId: this._userId.toString(),
      walletId: this._walletId.toString(),
      usdtAmount: usdtAmount.value.toString(),
      hashpowerAmount: hashpowerAmount.value.toString(),
      expireAt: expireAt.toISOString(),
      refOrderId,
    }));
  }

  // 待领取 -> 可结算
  movePendingToSettleable(): void {
    this.ensureActive();

    if (this._rewards.pendingUsdt.isZero() && this._rewards.pendingHashpower.isZero()) {
      throw new DomainError('No pending rewards to move');
    }

    const movedUsdt = this._rewards.pendingUsdt;
    const movedHashpower = this._rewards.pendingHashpower;

    this._rewards = {
      ...this._rewards,
      pendingUsdt: Money.zero('USDT'),
      pendingHashpower: Hashpower.zero(),
      pendingExpireAt: null,
      settleableUsdt: this._rewards.settleableUsdt.add(movedUsdt),
      settleableHashpower: this._rewards.settleableHashpower.add(movedHashpower),
    };

    // 增加算力
    this._hashpower = this._hashpower.add(movedHashpower);
    this._updatedAt = new Date();

    this.addDomainEvent(new RewardMovedToSettleableEvent({
      userId: this._userId.toString(),
      walletId: this._walletId.toString(),
      usdtAmount: movedUsdt.value.toString(),
      hashpowerAmount: movedHashpower.value.toString(),
    }));
  }

  /**
   * 直接增加可结算余额（用于 pending_rewards 表方案）
   * 当用户认种后，从 pending_rewards 表中结算的金额直接加到可结算余额
   */
  addSettleableReward(usdtAmount: Money, hashpowerAmount: Hashpower): void {
    this.ensureActive();

    this._rewards = {
      ...this._rewards,
      settleableUsdt: this._rewards.settleableUsdt.add(usdtAmount),
      settleableHashpower: this._rewards.settleableHashpower.add(hashpowerAmount),
    };

    // 增加算力
    this._hashpower = this._hashpower.add(hashpowerAmount);
    this._updatedAt = new Date();

    this.addDomainEvent(new RewardMovedToSettleableEvent({
      userId: this._userId.toString(),
      walletId: this._walletId.toString(),
      usdtAmount: usdtAmount.value.toString(),
      hashpowerAmount: hashpowerAmount.value.toString(),
    }));
  }

  // 奖励过期
  expirePendingRewards(): void {
    if (this._rewards.pendingUsdt.isZero() && this._rewards.pendingHashpower.isZero()) {
      return;
    }

    const expiredUsdt = this._rewards.pendingUsdt;
    const expiredHashpower = this._rewards.pendingHashpower;

    this._rewards = {
      ...this._rewards,
      pendingUsdt: Money.zero('USDT'),
      pendingHashpower: Hashpower.zero(),
      pendingExpireAt: null,
      expiredTotalUsdt: this._rewards.expiredTotalUsdt.add(expiredUsdt),
      expiredTotalHashpower: this._rewards.expiredTotalHashpower.add(expiredHashpower),
    };
    this._updatedAt = new Date();

    this.addDomainEvent(new RewardExpiredEvent({
      userId: this._userId.toString(),
      walletId: this._walletId.toString(),
      usdtAmount: expiredUsdt.value.toString(),
      hashpowerAmount: expiredHashpower.value.toString(),
    }));
  }

  // 结算奖励
  settleRewards(usdtAmount: Money, settleCurrency: string, receivedAmount: Money, settlementOrderId: string, swapTxHash?: string): void {
    this.ensureActive();

    if (this._rewards.settleableUsdt.lessThan(usdtAmount)) {
      throw new InsufficientBalanceError(
        'USDT (settleable)',
        usdtAmount.value.toString(),
        this._rewards.settleableUsdt.value.toString(),
      );
    }

    // 扣减可结算USDT
    this._rewards = {
      ...this._rewards,
      settleableUsdt: this._rewards.settleableUsdt.subtract(usdtAmount),
      settledTotalUsdt: this._rewards.settledTotalUsdt.add(usdtAmount),
    };

    // 增加目标币种余额
    const targetBalance = this.getBalance(settleCurrency as AssetType);
    this.setBalance(settleCurrency as AssetType, targetBalance.add(receivedAmount));
    this._updatedAt = new Date();

    this.addDomainEvent(new SettlementCompletedEvent({
      userId: this._userId.toString(),
      walletId: this._walletId.toString(),
      settlementOrderId,
      usdtAmount: usdtAmount.value.toString(),
      settleCurrency,
      receivedAmount: receivedAmount.value.toString(),
      swapTxHash,
    }));
  }

  /**
   * 结算到余额 (可结算USDT → 钱包USDT余额，无币种兑换)
   * 用于将可结算收益直接转入钱包 USDT 余额
   */
  settleToBalance(usdtAmount: Money, settlementId: string): void {
    this.ensureActive();

    if (this._rewards.settleableUsdt.lessThan(usdtAmount)) {
      throw new InsufficientBalanceError(
        'USDT (settleable)',
        usdtAmount.value.toString(),
        this._rewards.settleableUsdt.value.toString(),
      );
    }

    // 扣减可结算USDT
    this._rewards = {
      ...this._rewards,
      settleableUsdt: this._rewards.settleableUsdt.subtract(usdtAmount),
      settledTotalUsdt: this._rewards.settledTotalUsdt.add(usdtAmount),
    };

    // 增加 USDT 可用余额
    this._balances.usdt = this._balances.usdt.add(usdtAmount);
    this._updatedAt = new Date();

    // 发布结算完成事件
    this.addDomainEvent(new SettlementCompletedEvent({
      userId: this._userId.toString(),
      walletId: this._walletId.toString(),
      settlementOrderId: settlementId,
      usdtAmount: usdtAmount.value.toString(),
      settleCurrency: 'USDT',
      receivedAmount: usdtAmount.value.toString(),
      swapTxHash: undefined,
    }));
  }

  // 冻结钱包
  freezeWallet(): void {
    if (this._status === WalletStatus.FROZEN) {
      throw new DomainError('Wallet is already frozen');
    }
    this._status = WalletStatus.FROZEN;
    this._updatedAt = new Date();
  }

  // 解冻钱包
  unfreezeWallet(): void {
    if (this._status !== WalletStatus.FROZEN) {
      throw new DomainError('Wallet is not frozen');
    }
    this._status = WalletStatus.ACTIVE;
    this._updatedAt = new Date();
  }

  private getBalance(assetType: AssetType): Balance {
    switch (assetType) {
      case AssetType.USDT: return this._balances.usdt;
      case AssetType.DST: return this._balances.dst;
      case AssetType.BNB: return this._balances.bnb;
      case AssetType.OG: return this._balances.og;
      case AssetType.RWAD: return this._balances.rwad;
      default: throw new DomainError(`Unknown asset type: ${assetType}`);
    }
  }

  private setBalance(assetType: AssetType, balance: Balance): void {
    switch (assetType) {
      case AssetType.USDT: this._balances.usdt = balance; break;
      case AssetType.DST: this._balances.dst = balance; break;
      case AssetType.BNB: this._balances.bnb = balance; break;
      case AssetType.OG: this._balances.og = balance; break;
      case AssetType.RWAD: this._balances.rwad = balance; break;
      default: throw new DomainError(`Unknown asset type: ${assetType}`);
    }
  }

  private ensureActive(): void {
    if (this._status !== WalletStatus.ACTIVE) {
      throw new WalletFrozenError(this._walletId.toString());
    }
  }

  private addDomainEvent(event: DomainEvent): void {
    this._domainEvents.push(event);
  }

  clearDomainEvents(): void {
    this._domainEvents = [];
  }
}
