import * as SQLite from 'expo-sqlite';
import type { ILocalDataSource } from '@/data/datasources/interfaces';
import { databaseManager } from './core/DatabaseManager';

class OperationQueue {
  private queue: Promise<unknown> = Promise.resolve();

  enqueue<T>(operation: () => Promise<T>): Promise<T> {
    const result = this.queue.then(() => operation(), () => operation());
    this.queue = result.catch(() => {});
    return result;
  }

  async drain(): Promise<void> {
    await this.queue;
  }
}

const operationQueue = new OperationQueue();

export class LocalDataSource implements ILocalDataSource {
  private async getDb(): Promise<SQLite.SQLiteDatabase> {
    return databaseManager.getDb();
  }

  async initialize(userId?: string): Promise<void> {
    if (databaseManager.isReady()) {
      return;
    }
    await operationQueue.enqueue(() => databaseManager.initialize(userId));
  }

  async query<T>(sql: string, params?: any[]): Promise<T[]> {
    const db = await this.getDb();
    if (params && params.length > 0) {
      return db.getAllAsync<T>(sql, params as any);
    }
    return db.getAllAsync<T>(sql);
  }

  async getFirst<T>(sql: string, params?: any[]): Promise<T | null> {
    const db = await this.getDb();
    if (params && params.length > 0) {
      return db.getFirstAsync<T>(sql, params as any);
    }
    return db.getFirstAsync<T>(sql);
  }

  async execute(sql: string, params?: any[]): Promise<void> {
    const db = await this.getDb();
    await db.execAsync(sql);
  }

  async run(sql: string, params?: any[]): Promise<{ lastInsertRowId: number; changes: number }> {
    const db = await this.getDb();
    if (params && params.length > 0) {
      return db.runAsync(sql, params as any);
    }
    return db.runAsync(sql);
  }

  async transaction(operations: () => Promise<void>): Promise<void> {
    const db = await this.getDb();
    await db.withTransactionAsync(operations);
  }

  async batch(operations: (db: ILocalDataSource) => Promise<void>): Promise<void> {
    await this.transaction(async () => {
      await operations(this);
    });
  }

  async enqueueWrite<T>(operation: () => Promise<T>): Promise<T> {
    return operationQueue.enqueue(operation);
  }

  async switchDatabase(userId?: string | null): Promise<void> {
    await operationQueue.drain();
    await operationQueue.enqueue(() => databaseManager.initialize(userId));
  }

  async closeDatabase(): Promise<void> {
    await operationQueue.drain();
    await operationQueue.enqueue(() => databaseManager.close());
  }
}

export const localDataSource = new LocalDataSource();
