import { type ListingStatus, type TransactionType } from '@prisma/client';
import { AggregateRoot, ValidationException } from '@modules/shared';
import { ListingApprovedEvent } from '../events/listing-approved.event';
import { ListingCreatedEvent } from '../events/listing-created.event';
import { ListingOwnershipTransferredEvent } from '../events/listing-ownership-transferred.event';
import { ListingPriceChangedEvent } from '../events/listing-price-changed.event';
import { ListingSoldEvent } from '../events/listing-sold.event';
import { ListingStatusChangedEvent } from '../events/listing-status-changed.event';
import { ListingUpdatedEvent } from '../events/listing-updated.event';
import { Price } from '../value-objects/price.vo';

const VALID_TRANSITIONS: Record<ListingStatus, ListingStatus[]> = {
  DRAFT: ['PENDING_REVIEW'],
  PENDING_REVIEW: ['ACTIVE', 'REJECTED'],
  ACTIVE: ['RESERVED', 'SOLD', 'RENTED', 'EXPIRED'],
  RESERVED: ['ACTIVE', 'SOLD', 'RENTED'],
  SOLD: [],
  RENTED: [],
  EXPIRED: ['DRAFT'],
  REJECTED: ['DRAFT'],
};

export interface ListingProps {
  propertyId: string;
  agentId: string | null;
  sellerId: string;
  transactionType: TransactionType;
  status: ListingStatus;
  price: Price;
  pricePerM2: number | null;
  rentPriceMonthly: bigint | null;
  commissionPct: number | null;
  aiPriceEstimate: bigint | null;
  aiConfidence: number | null;
  moderationScore: number | null;
  moderationNotes: string | null;
  viewCount: number;
  saveCount: number;
  inquiryCount: number;
  featuredUntil: Date | null;
  expiresAt: Date | null;
  publishedAt: Date | null;
}

export class ListingEntity extends AggregateRoot<string> {
  private _propertyId: string;
  private _agentId: string | null;
  private _sellerId: string;
  private _transactionType: TransactionType;
  private _status: ListingStatus;
  private _price: Price;
  private _pricePerM2: number | null;
  private _rentPriceMonthly: bigint | null;
  private _commissionPct: number | null;
  private _aiPriceEstimate: bigint | null;
  private _aiConfidence: number | null;
  private _moderationScore: number | null;
  private _moderationNotes: string | null;
  private _viewCount: number;
  private _saveCount: number;
  private _inquiryCount: number;
  private _featuredUntil: Date | null;
  private _expiresAt: Date | null;
  private _publishedAt: Date | null;

  constructor(id: string, props: ListingProps, createdAt?: Date, updatedAt?: Date) {
    super(id, createdAt, updatedAt);
    this._propertyId = props.propertyId;
    this._agentId = props.agentId;
    this._sellerId = props.sellerId;
    this._transactionType = props.transactionType;
    this._status = props.status;
    this._price = props.price;
    this._pricePerM2 = props.pricePerM2;
    this._rentPriceMonthly = props.rentPriceMonthly;
    this._commissionPct = props.commissionPct;
    this._aiPriceEstimate = props.aiPriceEstimate;
    this._aiConfidence = props.aiConfidence;
    this._moderationScore = props.moderationScore;
    this._moderationNotes = props.moderationNotes;
    this._viewCount = props.viewCount;
    this._saveCount = props.saveCount;
    this._inquiryCount = props.inquiryCount;
    this._featuredUntil = props.featuredUntil;
    this._expiresAt = props.expiresAt;
    this._publishedAt = props.publishedAt;
  }

  get propertyId(): string { return this._propertyId; }
  get agentId(): string | null { return this._agentId; }
  get sellerId(): string { return this._sellerId; }
  get transactionType(): TransactionType { return this._transactionType; }
  get status(): ListingStatus { return this._status; }
  get price(): Price { return this._price; }
  get pricePerM2(): number | null { return this._pricePerM2; }
  get rentPriceMonthly(): bigint | null { return this._rentPriceMonthly; }
  get commissionPct(): number | null { return this._commissionPct; }
  get aiPriceEstimate(): bigint | null { return this._aiPriceEstimate; }
  get aiConfidence(): number | null { return this._aiConfidence; }
  get moderationScore(): number | null { return this._moderationScore; }
  get moderationNotes(): string | null { return this._moderationNotes; }
  get viewCount(): number { return this._viewCount; }
  get saveCount(): number { return this._saveCount; }
  get inquiryCount(): number { return this._inquiryCount; }
  get featuredUntil(): Date | null { return this._featuredUntil; }
  get expiresAt(): Date | null { return this._expiresAt; }
  get publishedAt(): Date | null { return this._publishedAt; }

  static createNew(
    id: string,
    propertyId: string,
    sellerId: string,
    transactionType: TransactionType,
    price: Price,
    areaM2: number,
    agentId?: string,
    rentPriceMonthly?: bigint,
    commissionPct?: number,
  ): ListingEntity {
    const listing = new ListingEntity(id, {
      propertyId,
      agentId: agentId ?? null,
      sellerId,
      transactionType,
      status: 'DRAFT',
      price,
      pricePerM2: price.calculatePerM2(areaM2),
      rentPriceMonthly: rentPriceMonthly ?? null,
      commissionPct: commissionPct ?? 2.0,
      aiPriceEstimate: null,
      aiConfidence: null,
      moderationScore: null,
      moderationNotes: null,
      viewCount: 0,
      saveCount: 0,
      inquiryCount: 0,
      featuredUntil: null,
      expiresAt: null,
      publishedAt: null,
    });

    listing.addDomainEvent(new ListingCreatedEvent(id, propertyId, sellerId, transactionType));
    return listing;
  }

  transitionTo(newStatus: ListingStatus): void {
    const allowed = VALID_TRANSITIONS[this._status];
    if (!allowed?.includes(newStatus)) {
      throw new ValidationException(
        `Không thể chuyển trạng thái từ ${this._status} sang ${newStatus}`,
        { currentStatus: this._status, targetStatus: newStatus },
      );
    }

    const previousStatus = this._status;
    this._status = newStatus;
    this.updatedAt = new Date();

    // Always emit generic status change event for all transitions
    this.addDomainEvent(
      new ListingStatusChangedEvent(this.id, this._propertyId, previousStatus, newStatus),
    );

    if (newStatus === 'ACTIVE' && previousStatus === 'PENDING_REVIEW') {
      this._publishedAt = new Date();
      this.addDomainEvent(new ListingApprovedEvent(this.id, this._propertyId));
    }

    if (newStatus === 'SOLD' || newStatus === 'RENTED') {
      this.addDomainEvent(new ListingSoldEvent(this.id, this._propertyId, newStatus));
    }
  }

  submitForReview(): void {
    this.transitionTo('PENDING_REVIEW');
  }

  approve(): void {
    this.transitionTo('ACTIVE');
  }

  reject(notes: string): void {
    this._moderationNotes = notes;
    this.transitionTo('REJECTED');
  }

  setModerationScore(score: number, notes?: string): void {
    this._moderationScore = score;
    if (notes) this._moderationNotes = notes;
    this.updatedAt = new Date();
  }

  incrementViewCount(): void {
    this._viewCount++;
  }

  /**
   * Updates listing content fields (price, rent price).
   * If the listing is ACTIVE, transitions it to PENDING_REVIEW for re-moderation.
   * Returns list of changed field names.
   */
  updateContent(fields: {
    priceVND?: bigint;
    rentPriceMonthly?: bigint;
    areaM2?: number;
  }): string[] {
    // Immutable fields after ACTIVE: propertyType, address, location are on Property entity
    // and are blocked at the handler level.

    const updatedFields: string[] = [];

    if (fields.priceVND !== undefined) {
      const oldPriceValue = this._price.amountVND;
      const priceResult = Price.create(fields.priceVND);
      if (priceResult.isErr) {
        throw new ValidationException(priceResult.unwrapErr(), { field: 'priceVND' });
      }
      this._price = priceResult.unwrap();
      if (fields.areaM2 !== undefined) {
        this._pricePerM2 = this._price.calculatePerM2(fields.areaM2);
      }
      if (oldPriceValue !== fields.priceVND) {
        this.addDomainEvent(new ListingPriceChangedEvent(this.id, oldPriceValue, fields.priceVND));
      }
      updatedFields.push('priceVND');
    }

    if (fields.rentPriceMonthly !== undefined) {
      this._rentPriceMonthly = fields.rentPriceMonthly;
      updatedFields.push('rentPriceMonthly');
    }

    if (updatedFields.length > 0) {
      this.updatedAt = new Date();
    }

    return updatedFields;
  }

  /**
   * Transfers ownership of the listing to a different agent (or un-assigns if
   * `newAgentId` is null). Authorization (admin or current-owner-agent only)
   * MUST be enforced by the application layer before calling this method.
   *
   * No-op when `newAgentId` matches the current `agentId` — returns false so
   * callers can skip bumping moderation / updating timestamps.
   *
   * Emits `ListingOwnershipTransferredEvent` on successful transfer.
   */
  transferOwnership(
    newAgentId: string | null,
    byUserId: string,
    byRole: string,
  ): boolean {
    if (newAgentId === this._agentId) {
      return false;
    }

    if (newAgentId !== null && newAgentId === this._sellerId) {
      throw new ValidationException(
        'Không thể giao tin đăng cho chính người bán',
        { sellerId: this._sellerId },
      );
    }

    const fromAgentId = this._agentId;
    this._agentId = newAgentId;
    this.updatedAt = new Date();

    this.addDomainEvent(
      new ListingOwnershipTransferredEvent(
        this.id,
        fromAgentId,
        newAgentId,
        byUserId,
        byRole,
      ),
    );

    return true;
  }

  /**
   * If listing is ACTIVE, transitions to PENDING_REVIEW for re-moderation after content edit.
   * Emits ListingUpdatedEvent with changed fields.
   */
  markEditedForReModeration(propertyId: string, updatedFields: string[]): void {
    const previousStatus = this._status;

    if (this._status === 'ACTIVE') {
      this._status = 'PENDING_REVIEW';
      this.addDomainEvent(
        new ListingStatusChangedEvent(this.id, propertyId, previousStatus, 'PENDING_REVIEW'),
      );
    }

    this.addDomainEvent(
      new ListingUpdatedEvent(
        this.id,
        propertyId,
        this._sellerId,
        previousStatus,
        this._status,
        updatedFields,
      ),
    );
  }
}
