/**
 * PartyShare Entity
 *
 * Represents a party's share of a distributed key.
 * This is the core aggregate root for the MPC service.
 */

import {
  ShareId,
  PartyId,
  SessionId,
  ShareData,
  PublicKey,
  Threshold,
} from '../value-objects';
import {
  PartyShareType,
  PartyShareStatus,
} from '../enums';
import {
  DomainEvent,
  ShareCreatedEvent,
  ShareRotatedEvent,
  ShareRevokedEvent,
  ShareUsedEvent,
} from '../events';

export interface PartyShareCreateParams {
  partyId: PartyId;
  sessionId: SessionId;
  shareType: PartyShareType;
  shareData: ShareData;
  publicKey: PublicKey;
  threshold: Threshold;
}

export interface PartyShareReconstructParams {
  id: ShareId;
  partyId: PartyId;
  sessionId: SessionId;
  shareType: PartyShareType;
  shareData: ShareData;
  publicKey: PublicKey;
  threshold: Threshold;
  status: PartyShareStatus;
  createdAt: Date;
  updatedAt: Date;
  lastUsedAt?: Date;
}

export class PartyShare {
  // ============================================================================
  // Private Fields
  // ============================================================================
  private readonly _id: ShareId;
  private readonly _partyId: PartyId;
  private readonly _sessionId: SessionId;
  private _shareType: PartyShareType;
  private _shareData: ShareData;
  private readonly _publicKey: PublicKey;
  private readonly _threshold: Threshold;
  private _status: PartyShareStatus;
  private readonly _createdAt: Date;
  private _updatedAt: Date;
  private _lastUsedAt?: Date;

  // Domain events collection
  private readonly _domainEvents: DomainEvent[] = [];

  // ============================================================================
  // Constructor (private - use factory methods)
  // ============================================================================
  private constructor(
    id: ShareId,
    partyId: PartyId,
    sessionId: SessionId,
    shareType: PartyShareType,
    shareData: ShareData,
    publicKey: PublicKey,
    threshold: Threshold,
    status: PartyShareStatus,
    createdAt: Date,
    updatedAt: Date,
    lastUsedAt?: Date,
  ) {
    this._id = id;
    this._partyId = partyId;
    this._sessionId = sessionId;
    this._shareType = shareType;
    this._shareData = shareData;
    this._publicKey = publicKey;
    this._threshold = threshold;
    this._status = status;
    this._createdAt = createdAt;
    this._updatedAt = updatedAt;
    this._lastUsedAt = lastUsedAt;
  }

  // ============================================================================
  // Factory Methods
  // ============================================================================

  /**
   * Create a new party share (typically after keygen)
   */
  static create(params: PartyShareCreateParams): PartyShare {
    const id = ShareId.generate();
    const now = new Date();

    const share = new PartyShare(
      id,
      params.partyId,
      params.sessionId,
      params.shareType,
      params.shareData,
      params.publicKey,
      params.threshold,
      PartyShareStatus.ACTIVE,
      now,
      now,
    );

    // Emit domain event
    share.addDomainEvent(new ShareCreatedEvent(
      id.value,
      params.partyId.value,
      params.sessionId.value,
      params.shareType,
      params.publicKey.toHex(),
      params.threshold.toString(),
    ));

    return share;
  }

  /**
   * Reconstruct from persistence (no events emitted)
   */
  static reconstruct(params: PartyShareReconstructParams): PartyShare {
    return new PartyShare(
      params.id,
      params.partyId,
      params.sessionId,
      params.shareType,
      params.shareData,
      params.publicKey,
      params.threshold,
      params.status,
      params.createdAt,
      params.updatedAt,
      params.lastUsedAt,
    );
  }

  // ============================================================================
  // Getters (read-only access)
  // ============================================================================
  get id(): ShareId {
    return this._id;
  }

  get partyId(): PartyId {
    return this._partyId;
  }

  get sessionId(): SessionId {
    return this._sessionId;
  }

  get shareType(): PartyShareType {
    return this._shareType;
  }

  get shareData(): ShareData {
    return this._shareData;
  }

  get publicKey(): PublicKey {
    return this._publicKey;
  }

  get threshold(): Threshold {
    return this._threshold;
  }

  get status(): PartyShareStatus {
    return this._status;
  }

  get createdAt(): Date {
    return this._createdAt;
  }

  get updatedAt(): Date {
    return this._updatedAt;
  }

  get lastUsedAt(): Date | undefined {
    return this._lastUsedAt;
  }

  get domainEvents(): DomainEvent[] {
    return [...this._domainEvents];
  }

  // ============================================================================
  // Business Methods
  // ============================================================================

  /**
   * Mark the share as used (updates lastUsedAt timestamp)
   */
  markAsUsed(messageHash?: string): void {
    this.ensureActive();

    this._lastUsedAt = new Date();
    this._updatedAt = new Date();

    if (messageHash) {
      this.addDomainEvent(new ShareUsedEvent(
        this._id.value,
        this._sessionId.value,
        messageHash,
      ));
    }
  }

  /**
   * Rotate the share with new share data (from key refresh)
   */
  rotate(newShareData: ShareData, newSessionId: SessionId): PartyShare {
    this.ensureActive();

    // Mark current share as rotated
    this._status = PartyShareStatus.ROTATED;
    this._updatedAt = new Date();

    // Create new share with updated data
    const newShare = new PartyShare(
      ShareId.generate(),
      this._partyId,
      newSessionId,
      this._shareType,
      newShareData,
      this._publicKey, // Public key remains the same after rotation
      this._threshold,
      PartyShareStatus.ACTIVE,
      new Date(),
      new Date(),
    );

    // Emit rotation event
    newShare.addDomainEvent(new ShareRotatedEvent(
      newShare._id.value,
      this._id.value,
      this._partyId.value,
      newSessionId.value,
    ));

    return newShare;
  }

  /**
   * Revoke the share
   */
  revoke(reason: string): void {
    if (this._status === PartyShareStatus.REVOKED) {
      throw new Error('Share is already revoked');
    }

    this._status = PartyShareStatus.REVOKED;
    this._updatedAt = new Date();

    this.addDomainEvent(new ShareRevokedEvent(
      this._id.value,
      this._partyId.value,
      reason,
    ));
  }

  /**
   * Validate if the given participant count meets threshold requirements
   */
  validateThreshold(participantsCount: number): boolean {
    return this._threshold.validateParticipants(participantsCount);
  }

  /**
   * Check if share is active
   */
  isActive(): boolean {
    return this._status === PartyShareStatus.ACTIVE;
  }

  /**
   * Check if this share belongs to the given party
   */
  belongsToParty(partyId: PartyId): boolean {
    return this._partyId.equals(partyId);
  }

  /**
   * Check if this share was created in the given session
   */
  fromSession(sessionId: SessionId): boolean {
    return this._sessionId.equals(sessionId);
  }

  /**
   * Check if this share has the given public key
   */
  hasPublicKey(publicKey: PublicKey): boolean {
    return this._publicKey.equals(publicKey);
  }

  // ============================================================================
  // Domain Event Management
  // ============================================================================

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

  clearDomainEvents(): void {
    this._domainEvents.length = 0;
  }

  // ============================================================================
  // Private Helper Methods
  // ============================================================================

  private ensureActive(): void {
    if (this._status !== PartyShareStatus.ACTIVE) {
      throw new Error(`Cannot perform operation on ${this._status} share`);
    }
  }
}
