/**
 * Synor Database SDK Client
 * Multi-model database: Key-Value, Document, Vector, and Time Series
 */

import type {
  DatabaseConfig,
  KeyValueEntry,
  SetOptions,
  ListOptions,
  ListResult,
  Document,
  CreateDocumentOptions,
  UpdateDocumentOptions,
  QueryOptions,
  QueryResult,
  AggregateStage,
  VectorEntry,
  UpsertVectorOptions,
  SearchOptions,
  SearchResult,
  VectorCollectionConfig,
  VectorCollectionStats,
  DataPoint,
  WritePointsOptions,
  TimeRange,
  TimeSeriesQueryOptions,
  TimeSeriesResult,
  SeriesInfo,
  DatabaseStats,
} from './types';
import { DatabaseError } from './types';

const DEFAULT_CONFIG = {
  endpoint: 'https://database.synor.io/v1',
  network: 'mainnet' as const,
  timeout: 30000,
  retries: 3,
  debug: false,
};

/**
 * Key-Value store interface
 */
class KeyValueStore {
  constructor(private client: SynorDatabase) {}

  /**
   * Get a value by key
   */
  async get<T = unknown>(key: string): Promise<T | null> {
    const response = await this.client.request<{ value: T | null }>(`/kv/${encodeURIComponent(key)}`);
    return response.value;
  }

  /**
   * Get a full entry with metadata
   */
  async getEntry<T = unknown>(key: string): Promise<KeyValueEntry<T> | null> {
    return this.client.request<KeyValueEntry<T> | null>(`/kv/${encodeURIComponent(key)}/entry`);
  }

  /**
   * Set a value
   */
  async set<T = unknown>(key: string, value: T, options?: SetOptions): Promise<void> {
    await this.client.request(`/kv/${encodeURIComponent(key)}`, {
      method: 'PUT',
      body: { value, ...options },
    });
  }

  /**
   * Delete a key
   */
  async delete(key: string): Promise<boolean> {
    const response = await this.client.request<{ deleted: boolean }>(`/kv/${encodeURIComponent(key)}`, {
      method: 'DELETE',
    });
    return response.deleted;
  }

  /**
   * Check if a key exists
   */
  async exists(key: string): Promise<boolean> {
    const response = await this.client.request<{ exists: boolean }>(`/kv/${encodeURIComponent(key)}/exists`);
    return response.exists;
  }

  /**
   * List keys with optional prefix filtering
   */
  async list<T = unknown>(options?: ListOptions): Promise<ListResult<T>> {
    const params = new URLSearchParams();
    if (options?.prefix) params.set('prefix', options.prefix);
    if (options?.cursor) params.set('cursor', options.cursor);
    if (options?.limit) params.set('limit', options.limit.toString());

    return this.client.request<ListResult<T>>(`/kv?${params}`);
  }

  /**
   * Get multiple values at once
   */
  async mget<T = unknown>(keys: string[]): Promise<Map<string, T | null>> {
    const response = await this.client.request<{ entries: { key: string; value: T | null }[] }>('/kv/mget', {
      method: 'POST',
      body: { keys },
    });
    return new Map(response.entries.map(e => [e.key, e.value]));
  }

  /**
   * Set multiple values at once
   */
  async mset<T = unknown>(entries: { key: string; value: T }[]): Promise<void> {
    await this.client.request('/kv/mset', {
      method: 'POST',
      body: { entries },
    });
  }

  /**
   * Increment a numeric value
   */
  async incr(key: string, by: number = 1): Promise<number> {
    const response = await this.client.request<{ value: number }>(`/kv/${encodeURIComponent(key)}/incr`, {
      method: 'POST',
      body: { by },
    });
    return response.value;
  }
}

/**
 * Document store interface
 */
class DocumentStore {
  constructor(private client: SynorDatabase) {}

  /**
   * Create a new document
   */
  async create<T extends Record<string, unknown>>(
    collection: string,
    data: T,
    options?: CreateDocumentOptions
  ): Promise<Document<T>> {
    return this.client.request<Document<T>>(`/documents/${encodeURIComponent(collection)}`, {
      method: 'POST',
      body: { data, ...options },
    });
  }

  /**
   * Get a document by ID
   */
  async get<T extends Record<string, unknown>>(
    collection: string,
    id: string
  ): Promise<Document<T> | null> {
    return this.client.request<Document<T> | null>(
      `/documents/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`
    );
  }

  /**
   * Update a document
   */
  async update<T extends Record<string, unknown>>(
    collection: string,
    id: string,
    update: Partial<T>,
    options?: UpdateDocumentOptions
  ): Promise<Document<T>> {
    return this.client.request<Document<T>>(
      `/documents/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`,
      {
        method: 'PATCH',
        body: { update, ...options },
      }
    );
  }

  /**
   * Replace a document
   */
  async replace<T extends Record<string, unknown>>(
    collection: string,
    id: string,
    data: T
  ): Promise<Document<T>> {
    return this.client.request<Document<T>>(
      `/documents/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`,
      {
        method: 'PUT',
        body: { data },
      }
    );
  }

  /**
   * Delete a document
   */
  async delete(collection: string, id: string): Promise<boolean> {
    const response = await this.client.request<{ deleted: boolean }>(
      `/documents/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`,
      { method: 'DELETE' }
    );
    return response.deleted;
  }

  /**
   * Query documents
   */
  async query<T extends Record<string, unknown>>(
    collection: string,
    options?: QueryOptions
  ): Promise<QueryResult<T>> {
    return this.client.request<QueryResult<T>>(
      `/documents/${encodeURIComponent(collection)}/query`,
      {
        method: 'POST',
        body: options || {},
      }
    );
  }

  /**
   * Count documents matching a filter
   */
  async count(collection: string, filter?: QueryOptions['filter']): Promise<number> {
    const response = await this.client.request<{ count: number }>(
      `/documents/${encodeURIComponent(collection)}/count`,
      {
        method: 'POST',
        body: { filter },
      }
    );
    return response.count;
  }

  /**
   * Run an aggregation pipeline
   */
  async aggregate<T = unknown>(
    collection: string,
    pipeline: AggregateStage[]
  ): Promise<T[]> {
    const response = await this.client.request<{ results: T[] }>(
      `/documents/${encodeURIComponent(collection)}/aggregate`,
      {
        method: 'POST',
        body: { pipeline },
      }
    );
    return response.results;
  }

  /**
   * Create an index on a collection
   */
  async createIndex(
    collection: string,
    fields: string[],
    options?: { unique?: boolean; sparse?: boolean }
  ): Promise<{ name: string }> {
    return this.client.request<{ name: string }>(
      `/documents/${encodeURIComponent(collection)}/indexes`,
      {
        method: 'POST',
        body: { fields, ...options },
      }
    );
  }

  /**
   * List indexes on a collection
   */
  async listIndexes(collection: string): Promise<{ name: string; fields: string[]; unique: boolean }[]> {
    const response = await this.client.request<{ indexes: { name: string; fields: string[]; unique: boolean }[] }>(
      `/documents/${encodeURIComponent(collection)}/indexes`
    );
    return response.indexes;
  }
}

/**
 * Vector store interface
 */
class VectorStore {
  constructor(private client: SynorDatabase) {}

  /**
   * Create a vector collection
   */
  async createCollection(config: VectorCollectionConfig): Promise<void> {
    await this.client.request('/vectors/collections', {
      method: 'POST',
      body: config,
    });
  }

  /**
   * Delete a vector collection
   */
  async deleteCollection(name: string): Promise<void> {
    await this.client.request(`/vectors/collections/${encodeURIComponent(name)}`, {
      method: 'DELETE',
    });
  }

  /**
   * Get collection stats
   */
  async getCollectionStats(name: string): Promise<VectorCollectionStats> {
    return this.client.request<VectorCollectionStats>(
      `/vectors/collections/${encodeURIComponent(name)}/stats`
    );
  }

  /**
   * Upsert vectors
   */
  async upsert(
    collection: string,
    vectors: VectorEntry[],
    options?: UpsertVectorOptions
  ): Promise<{ upserted: number }> {
    return this.client.request<{ upserted: number }>(
      `/vectors/${encodeURIComponent(collection)}/upsert`,
      {
        method: 'POST',
        body: { vectors, ...options },
      }
    );
  }

  /**
   * Search for similar vectors
   */
  async search(
    collection: string,
    vector: number[],
    options?: SearchOptions
  ): Promise<SearchResult[]> {
    const response = await this.client.request<{ results: SearchResult[] }>(
      `/vectors/${encodeURIComponent(collection)}/search`,
      {
        method: 'POST',
        body: { vector, ...options },
      }
    );
    return response.results;
  }

  /**
   * Search using text (requires embedding generation)
   */
  async searchText(
    collection: string,
    text: string,
    options?: SearchOptions
  ): Promise<SearchResult[]> {
    const response = await this.client.request<{ results: SearchResult[] }>(
      `/vectors/${encodeURIComponent(collection)}/search/text`,
      {
        method: 'POST',
        body: { text, ...options },
      }
    );
    return response.results;
  }

  /**
   * Delete vectors by ID
   */
  async delete(
    collection: string,
    ids: string[],
    namespace?: string
  ): Promise<{ deleted: number }> {
    return this.client.request<{ deleted: number }>(
      `/vectors/${encodeURIComponent(collection)}/delete`,
      {
        method: 'POST',
        body: { ids, namespace },
      }
    );
  }

  /**
   * Fetch vectors by ID
   */
  async fetch(
    collection: string,
    ids: string[],
    namespace?: string
  ): Promise<VectorEntry[]> {
    const response = await this.client.request<{ vectors: VectorEntry[] }>(
      `/vectors/${encodeURIComponent(collection)}/fetch`,
      {
        method: 'POST',
        body: { ids, namespace },
      }
    );
    return response.vectors;
  }
}

/**
 * Time series store interface
 */
class TimeSeriesStore {
  constructor(private client: SynorDatabase) {}

  /**
   * Write data points to a series
   */
  async write(
    series: string,
    points: DataPoint[],
    options?: WritePointsOptions
  ): Promise<{ written: number }> {
    return this.client.request<{ written: number }>(
      `/timeseries/${encodeURIComponent(series)}/write`,
      {
        method: 'POST',
        body: { points, ...options },
      }
    );
  }

  /**
   * Query a time series
   */
  async query(
    series: string,
    range: TimeRange,
    options?: TimeSeriesQueryOptions
  ): Promise<TimeSeriesResult> {
    return this.client.request<TimeSeriesResult>(
      `/timeseries/${encodeURIComponent(series)}/query`,
      {
        method: 'POST',
        body: { range, ...options },
      }
    );
  }

  /**
   * Query multiple series
   */
  async queryMulti(
    series: string[],
    range: TimeRange,
    options?: TimeSeriesQueryOptions
  ): Promise<TimeSeriesResult[]> {
    const response = await this.client.request<{ results: TimeSeriesResult[] }>(
      '/timeseries/query',
      {
        method: 'POST',
        body: { series, range, ...options },
      }
    );
    return response.results;
  }

  /**
   * Delete data points in a range
   */
  async delete(
    series: string,
    range: TimeRange,
    tags?: Record<string, string>
  ): Promise<{ deleted: number }> {
    return this.client.request<{ deleted: number }>(
      `/timeseries/${encodeURIComponent(series)}/delete`,
      {
        method: 'POST',
        body: { range, tags },
      }
    );
  }

  /**
   * Get series information
   */
  async getSeriesInfo(series: string): Promise<SeriesInfo> {
    return this.client.request<SeriesInfo>(`/timeseries/${encodeURIComponent(series)}/info`);
  }

  /**
   * List all series
   */
  async listSeries(prefix?: string): Promise<SeriesInfo[]> {
    const params = prefix ? `?prefix=${encodeURIComponent(prefix)}` : '';
    const response = await this.client.request<{ series: SeriesInfo[] }>(`/timeseries${params}`);
    return response.series;
  }

  /**
   * Set retention policy for a series
   */
  async setRetention(series: string, retentionDays: number): Promise<void> {
    await this.client.request(`/timeseries/${encodeURIComponent(series)}/retention`, {
      method: 'PUT',
      body: { retentionDays },
    });
  }
}

/**
 * Synor Database SDK Client
 *
 * Multi-model database supporting Key-Value, Document, Vector, and Time Series data.
 *
 * @example
 * ```typescript
 * const db = new SynorDatabase({ apiKey: 'your-api-key' });
 *
 * // Key-Value operations
 * await db.kv.set('user:1', { name: 'Alice' });
 * const user = await db.kv.get('user:1');
 *
 * // Document operations
 * const doc = await db.documents.create('users', { name: 'Bob', age: 30 });
 * const results = await db.documents.query('users', { filter: { age: { $gt: 25 } } });
 *
 * // Vector operations
 * await db.vectors.upsert('embeddings', [{ id: '1', vector: [0.1, 0.2, ...] }]);
 * const similar = await db.vectors.search('embeddings', queryVector, { topK: 10 });
 *
 * // Time series operations
 * await db.timeseries.write('metrics', [{ timestamp: Date.now(), value: 42.5 }]);
 * const data = await db.timeseries.query('metrics', { start: '-1h', end: 'now' });
 * ```
 */
export class SynorDatabase {
  private config: Required<DatabaseConfig>;
  private closed = false;

  /** Key-Value store */
  readonly kv: KeyValueStore;
  /** Document store */
  readonly documents: DocumentStore;
  /** Vector store */
  readonly vectors: VectorStore;
  /** Time series store */
  readonly timeseries: TimeSeriesStore;

  constructor(config: DatabaseConfig) {
    this.config = {
      ...DEFAULT_CONFIG,
      ...config,
    };

    if (!this.config.apiKey) {
      throw new DatabaseError('API key is required');
    }

    this.kv = new KeyValueStore(this);
    this.documents = new DocumentStore(this);
    this.vectors = new VectorStore(this);
    this.timeseries = new TimeSeriesStore(this);
  }

  /**
   * Get database statistics
   */
  async getStats(): Promise<DatabaseStats> {
    return this.request<DatabaseStats>('/stats');
  }

  /**
   * Health check
   */
  async healthCheck(): Promise<boolean> {
    try {
      const response = await this.request<{ status: string }>('/health');
      return response.status === 'healthy';
    } catch {
      return false;
    }
  }

  /**
   * Close the client
   */
  close(): void {
    this.closed = true;
  }

  /**
   * Check if the client is closed
   */
  isClosed(): boolean {
    return this.closed;
  }

  /**
   * Internal request method
   */
  async request<T>(
    path: string,
    options: { method?: string; body?: unknown } = {}
  ): Promise<T> {
    if (this.closed) {
      throw new DatabaseError('Client has been closed');
    }

    const { method = 'GET', body } = options;
    let lastError: Error | null = null;

    for (let attempt = 0; attempt < this.config.retries; attempt++) {
      try {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);

        const response = await fetch(`${this.config.endpoint}${path}`, {
          method,
          headers: {
            'Authorization': `Bearer ${this.config.apiKey}`,
            'Content-Type': 'application/json',
            'X-SDK-Version': 'js/0.1.0',
          },
          body: body ? JSON.stringify(body) : undefined,
          signal: controller.signal,
        });

        clearTimeout(timeoutId);

        if (!response.ok) {
          const errorData = await response.json().catch(() => ({}));
          throw new DatabaseError(
            errorData.message || errorData.error || `HTTP ${response.status}`,
            errorData.code,
            response.status
          );
        }

        return await response.json();
      } catch (error) {
        lastError = error instanceof Error ? error : new Error(String(error));

        if (this.config.debug) {
          console.error(`Attempt ${attempt + 1} failed:`, lastError.message);
        }

        if (attempt < this.config.retries - 1) {
          await new Promise(resolve => setTimeout(resolve, (attempt + 1) * 1000));
        }
      }
    }

    throw lastError || new DatabaseError('Unknown error after retries');
  }
}
