import {
  Operation,
  OpType,
  EntityType,
  VectorClock,
  ActionType,
} from '../../../core/operation.types';
import { mergeVectorClocks } from '../../../../core/util/vector-clock';
import { CURRENT_SCHEMA_VERSION } from '../../../persistence/schema-migration.service';

/**
 * Counter for generating deterministic test UUIDs.
 * Reset between tests via resetTestUuidCounter().
 */
let testUuidCounter = 0;

/**
 * Generates a deterministic test UUID for reproducible tests.
 * Format: test-uuid-{counter}
 */
export const testUuid = (): string => `test-uuid-${++testUuidCounter}`;

/**
 * Resets the test UUID counter. Call this in beforeEach() for isolation.
 */
export const resetTestUuidCounter = (): void => {
  testUuidCounter = 0;
};

/**
 * Simulates a client with its own clientId and vector clock state.
 * Allows creating operations from multiple "devices" for integration testing.
 *
 * Each TestClient tracks its own view of the distributed system's state:
 * - Its own clientId (unique identifier)
 * - Its own vector clock (causal knowledge)
 */
export class TestClient {
  readonly clientId: string;
  private vectorClock: VectorClock;

  constructor(clientId: string) {
    if (!clientId || clientId.length < 5) {
      throw new Error('clientId must be at least 5 characters');
    }
    this.clientId = clientId;
    this.vectorClock = { [clientId]: 0 };
  }

  /**
   * Creates an operation as if generated by this client.
   * Automatically increments the vector clock.
   *
   * @param params Operation parameters (without causality/meta fields)
   * @returns A complete Operation with vector clock and metadata
   */
  createOperation(params: {
    actionType: string;
    opType: OpType;
    entityType: EntityType;
    entityId: string;
    payload: unknown;
    entityIds?: string[];
  }): Operation {
    // Increment our clock component before creating the operation
    this.vectorClock[this.clientId] = (this.vectorClock[this.clientId] || 0) + 1;

    return {
      id: testUuid(),
      actionType: params.actionType as ActionType,
      opType: params.opType,
      entityType: params.entityType,
      entityId: params.entityId,
      entityIds: params.entityIds,
      payload: params.payload,
      clientId: this.clientId,
      vectorClock: { ...this.vectorClock },
      timestamp: Date.now(),
      schemaVersion: CURRENT_SCHEMA_VERSION,
    };
  }

  /**
   * Merges a remote operation's vector clock into this client's knowledge.
   * Simulates receiving and applying a remote operation.
   *
   * Call this after "receiving" an operation from another client to update
   * this client's causal knowledge.
   *
   * @param remoteClock The vector clock from the received operation
   */
  mergeRemoteClock(remoteClock: VectorClock): void {
    this.vectorClock = mergeVectorClocks(this.vectorClock, remoteClock);
  }

  /**
   * Gets the current vector clock state (for assertions).
   * @returns A copy of the current vector clock
   */
  getCurrentClock(): VectorClock {
    return { ...this.vectorClock };
  }

  /**
   * Sets the vector clock to a specific state.
   * Useful for setting up specific test scenarios.
   *
   * @param clock The vector clock state to set
   */
  setVectorClock(clock: VectorClock): void {
    this.vectorClock = { ...clock };
  }
}
