import { HttpStatus } from '@nestjs/common';
import {
  type PaymentProvider,
  type PaymentStatus,
  type PaymentType,
} from '@prisma/client';
import { AggregateRoot, DomainException, ErrorCode, Result } from '@modules/shared';
import { PaymentCompletedEvent } from '../events/payment-completed.event';
import { PaymentCreatedEvent } from '../events/payment-created.event';
import { PaymentFailedEvent } from '../events/payment-failed.event';
import { PaymentRefundedEvent } from '../events/payment-refunded.event';
import { type Money } from '../value-objects/money.vo';

export interface PaymentProps {
  userId: string;
  transactionId: string | null;
  provider: PaymentProvider;
  type: PaymentType;
  amount: Money;
  status: PaymentStatus;
  providerTxId: string | null;
  callbackData: unknown;
  idempotencyKey: string | null;
}

export class PaymentEntity extends AggregateRoot<string> {
  private _userId: string;
  private _transactionId: string | null;
  private _provider: PaymentProvider;
  private _type: PaymentType;
  private _amount: Money;
  private _status: PaymentStatus;
  private _providerTxId: string | null;
  private _callbackData: unknown;
  private _idempotencyKey: string | null;

  constructor(id: string, props: PaymentProps, createdAt?: Date, updatedAt?: Date) {
    super(id, createdAt, updatedAt);
    this._userId = props.userId;
    this._transactionId = props.transactionId;
    this._provider = props.provider;
    this._type = props.type;
    this._amount = props.amount;
    this._status = props.status;
    this._providerTxId = props.providerTxId;
    this._callbackData = props.callbackData;
    this._idempotencyKey = props.idempotencyKey;
  }

  get userId(): string { return this._userId; }
  get transactionId(): string | null { return this._transactionId; }
  get provider(): PaymentProvider { return this._provider; }
  get type(): PaymentType { return this._type; }
  get amount(): Money { return this._amount; }
  get status(): PaymentStatus { return this._status; }
  get providerTxId(): string | null { return this._providerTxId; }
  get callbackData(): unknown { return this._callbackData; }
  get idempotencyKey(): string | null { return this._idempotencyKey; }

  static createNew(
    id: string,
    userId: string,
    provider: PaymentProvider,
    type: PaymentType,
    amount: Money,
    transactionId?: string,
    idempotencyKey?: string,
  ): PaymentEntity {
    const payment = new PaymentEntity(id, {
      userId,
      transactionId: transactionId ?? null,
      provider,
      type,
      amount,
      status: 'PENDING',
      providerTxId: null,
      callbackData: null,
      idempotencyKey: idempotencyKey ?? null,
    });

    payment.addDomainEvent(
      new PaymentCreatedEvent(id, userId, provider, type, amount.value),
    );

    return payment;
  }

  markProcessing(providerTxId: string): void {
    this._status = 'PROCESSING';
    this._providerTxId = providerTxId;
    this.updatedAt = new Date();
  }

  markCompleted(callbackData: unknown): Result<void, DomainException> {
    if (this._status !== 'PENDING' && this._status !== 'PROCESSING') {
      return Result.err(
        new DomainException(
          ErrorCode.PAYMENT_ALREADY_PROCESSED,
          `Cannot complete payment in status ${this._status}`,
          HttpStatus.CONFLICT,
        ),
      );
    }
    this._status = 'COMPLETED';
    this._callbackData = callbackData;
    this.updatedAt = new Date();

    this.addDomainEvent(
      new PaymentCompletedEvent(this.id, this._userId, this._provider, this._amount.value),
    );
    return Result.ok(undefined);
  }

  markFailed(callbackData: unknown): Result<void, DomainException> {
    if (this._status !== 'PENDING' && this._status !== 'PROCESSING') {
      return Result.err(
        new DomainException(
          ErrorCode.PAYMENT_ALREADY_PROCESSED,
          `Cannot fail payment in status ${this._status}`,
          HttpStatus.CONFLICT,
        ),
      );
    }
    this._status = 'FAILED';
    this._callbackData = callbackData;
    this.updatedAt = new Date();

    this.addDomainEvent(
      new PaymentFailedEvent(this.id, this._userId, this._provider),
    );
    return Result.ok(undefined);
  }

  /** Emit completed event without modifying state (used when DB was already updated atomically). */
  emitCompleted(): void {
    this.addDomainEvent(
      new PaymentCompletedEvent(this.id, this._userId, this._provider, this._amount.value),
    );
  }

  /** Emit failed event without modifying state (used when DB was already updated atomically). */
  emitFailed(): void {
    this.addDomainEvent(
      new PaymentFailedEvent(this.id, this._userId, this._provider),
    );
  }

  markRefunded(): Result<void, DomainException> {
    if (this._status !== 'COMPLETED') {
      return Result.err(
        new DomainException(
          ErrorCode.PAYMENT_ALREADY_PROCESSED,
          'Chỉ có thể hoàn tiền cho thanh toán đã hoàn tất',
          HttpStatus.CONFLICT,
        ),
      );
    }
    this._status = 'REFUNDED';
    this.updatedAt = new Date();

    this.addDomainEvent(
      new PaymentRefundedEvent(this.id, this._userId, this._provider, this._amount.value),
    );
    return Result.ok(undefined);
  }
}
