/**
 * Cloud-agnostic datastore interfaces.
 *
 * Provides DocumentCollection and DatastoreProvider abstractions
 * that work with Cosmos DB, MongoDB, or in-memory storage.
 */

// ── Base document type ──────────────────────────────────────────────────────

export interface BaseDocument {
  id: string;
  productId: string;
  /**
   * Optimistic-concurrency tag surfaced by the provider (Cosmos `_etag`). Optional
   * and provider-managed — callers read it back and pass it to `updateIfMatch`.
   */
  _etag?: string;
}

// ── Filter operators ────────────────────────────────────────────────────────

export type FilterValue = string | number | boolean | null;

export interface FilterOperator {
  $gt?: FilterValue;
  $gte?: FilterValue;
  $lt?: FilterValue;
  $lte?: FilterValue;
  $ne?: FilterValue;
  $exists?: boolean;
  $startsWith?: string;
  $contains?: FilterValue;
  $in?: FilterValue[];
}

export type FilterCondition = FilterValue | FilterOperator;

export interface FilterMap {
  [field: string]: FilterCondition | FilterMap[] | undefined;
  $or?: FilterMap[];
}

// ── Query types ─────────────────────────────────────────────────────────────

export interface CollectionQuery<T = BaseDocument> {
  filter?: FilterMap;
  sort?: Partial<Record<keyof T & string, 1 | -1>>;
  limit?: number;
  offset?: number;
  select?: (keyof T & string)[];
}

export interface AggregateQuery {
  groupBy: string;
  aggregations: AggregationField[];
  filter?: FilterMap;
}

export interface AggregationField {
  field: string;
  op: 'count' | 'sum' | 'avg' | 'min' | 'max';
  alias: string;
}

// ── Optimistic concurrency ──────────────────────────────────────────────────

/**
 * The version a conditional write expects the stored document to still be at.
 * `etag` is the provider tag (Cosmos `_etag`); `rev` is a portable monotonic
 * counter maintained for parity (and used by the memory provider). Provide either
 * or both — the write succeeds only if every supplied token still matches.
 */
export interface ConcurrencyToken {
  etag?: string;
  rev?: number;
}

/** Result of a conditional (compare-and-swap) write. */
export type UpdateIfMatchResult<T> =
  | { ok: true; doc: T }
  | { ok: false; reason: 'conflict' | 'not_found' };

// ── Document collection interface ───────────────────────────────────────────

export interface DocumentCollection<T extends BaseDocument = BaseDocument> {
  /** Find a single document by ID + partition key. */
  findById(id: string, partitionKey: string): Promise<T | null>;

  /** Find multiple documents matching a query. */
  findMany(query?: CollectionQuery<T>): Promise<T[]>;

  /** Find the first document matching a query. */
  findOne(query?: CollectionQuery<T>): Promise<T | null>;

  /** Count documents matching a filter. */
  count(filter?: FilterMap): Promise<number>;

  /** Create a new document. */
  create(doc: T): Promise<T>;

  /** Update a document by ID + partition key (merge semantics). */
  update(id: string, partitionKey: string, updates: Partial<T>): Promise<T>;

  /**
   * Optimistic-concurrency update: merge `patch` into the document only if its
   * stored version still matches `expected` (Cosmos If-Match on `_etag`; an
   * atomic compare-and-set on `rev` for the memory provider). Bumps `rev` and
   * surfaces a fresh `_etag` on success. Never overwrites a doc that changed —
   * returns `{ ok: false, reason: 'conflict' }` instead, and `'not_found'` when
   * the document is absent. This is the single-winner primitive for atomic claims.
   */
  updateIfMatch(
    id: string,
    partitionKey: string,
    expected: ConcurrencyToken,
    patch: Partial<T>
  ): Promise<UpdateIfMatchResult<T>>;

  /** Upsert a document (create or replace). */
  upsert(doc: T): Promise<T>;

  /** Delete a document by ID + partition key. */
  delete(id: string, partitionKey: string): Promise<void>;

  /** Run an aggregation query. */
  aggregate<R = Record<string, unknown>>(query: AggregateQuery): Promise<R[]>;

  /**
   * Execute a raw provider-specific query (escape hatch).
   * Returns results as-is from the underlying provider.
   */
  rawQuery<R = unknown>(query: string, parameters?: Record<string, unknown>): Promise<R[]>;
}

// ── Datastore provider interface ────────────────────────────────────────────

export interface DatastoreProvider {
  /** Get a collection by name with a partition key path. */
  getCollection<T extends BaseDocument = BaseDocument>(
    name: string,
    partitionKeyPath: string
  ): DocumentCollection<T>;

  /** Check if the datastore is healthy / reachable. */
  isHealthy(): Promise<boolean>;
}

// ── Provider config ─────────────────────────────────────────────────────────

export type DatastoreProviderType = 'cosmos' | 'memory';
