import { AbstractLevel, AbstractIterator, AbstractDatabaseOptions } from "abstract-level";
import { db } from "../db";
import { tinaKv } from "../db/schema";
import { eq, and, gte, lt, sql } from "drizzle-orm";

// Types
interface NodeCallback<T> {
  (err: Error | null, result?: T): void;
}

interface IteratorOptions {
  gt?: string;
  gte?: string;
  lt?: string;
  lte?: string;
  reverse?: boolean;
  limit?: number;
  keys?: boolean;
  values?: boolean;
}

interface PostgresLevelOptions {
  connectionString: string;
  namespace?: string;
}

// Iterator personnalisé pour PostgreSQL
class PostgresIterator extends AbstractIterator<string, any> {
  private namespace: string;
  private options: IteratorOptions;
  private results: { key: string; value: any }[] = [];
  private currentIndex = 0;
  private initialized = false;

  constructor(db: any, options: IteratorOptions, namespace: string) {
    super(db);
    this.options = options;
    this.namespace = namespace;
  }

  private async initialize(): Promise<void> {
    if (this.initialized) return;

    // Construire la requête
    let query = db
      .select({
        key: tinaKv.key,
        value: tinaKv.value,
      })
      .from(tinaKv)
      .where(eq(tinaKv.namespace, this.namespace));

    // Gestion des bounds
    const conditions = [];
    
    if (this.options.gt !== undefined) {
      conditions.push(sql`${tinaKv.key} > ${this.options.gt}`);
    }
    if (this.options.gte !== undefined) {
      conditions.push(sql`${tinaKv.key} >= ${this.options.gte}`);
    }
    if (this.options.lt !== undefined) {
      conditions.push(sql`${tinaKv.key} < ${this.options.lt}`);
    }
    if (this.options.lte !== undefined) {
      conditions.push(sql`${tinaKv.key} <= ${this.options.lte}`);
    }

    if (conditions.length > 0) {
      query = query.where(and(...conditions, eq(tinaKv.namespace, this.namespace)));
    }

    // Ordre
    if (this.options.reverse) {
      query = query.orderBy(sql`${tinaKv.key} DESC`);
    } else {
      query = query.orderBy(tinaKv.key);
    }

    // Limite
    if (this.options.limit !== undefined && this.options.limit > 0) {
      query = query.limit(this.options.limit);
    }

    this.results = await query;
    this.initialized = true;
  }

  async _next(callback: NodeCallback<{ key: string; value: any }>): Promise<void> {
    try {
      await this.initialize();

      if (this.currentIndex >= this.results.length) {
        return callback(null, undefined as any);
      }

      const item = this.results[this.currentIndex++];
      
      // Gestion des options keys/values
      const result: { key?: string; value?: any } = {};
      
      if (this.options.keys !== false) {
        result.key = item.key;
      }
      if (this.options.values !== false) {
        result.value = item.value;
      }

      callback(null, result as { key: string; value: any });
    } catch (error) {
      callback(error as Error);
    }
  }

  _seek(target: string): void {
    this.currentIndex = this.results.findIndex(r => r.key >= target);
    if (this.currentIndex < 0) {
      this.currentIndex = this.results.length;
    }
  }

  _end(callback: NodeCallback<void>): void {
    this.results = [];
    this.initialized = false;
    callback(null);
  }
}

// Classe principale PostgreSQL Level
export class PostgresLevel extends AbstractLevel<string, any> {
  private namespace: string;

  constructor(options: PostgresLevelOptions) {
    // @ts-ignore - abstract-level constructor accepte des options
    super({
      keyEncoding: "utf8",
      valueEncoding: "json",
    });
    this.namespace = options.namespace || "main";
  }

  async _put(key: string, value: any): Promise<void> {
    await db
      .insert(tinaKv)
      .values({
        key,
        value,
        namespace: this.namespace,
      })
      .onConflictDoUpdate({
        target: [tinaKv.key, tinaKv.namespace],
        set: { value },
      });
  }

  async _get(key: string): Promise<any> {
    const result = await db
      .select({ value: tinaKv.value })
      .from(tinaKv)
      .where(and(eq(tinaKv.key, key), eq(tinaKv.namespace, this.namespace)))
      .limit(1);

    if (result.length === 0) {
      const error = new Error(`Key not found: ${key}`) as Error & { notFound?: boolean; status?: number };
      error.notFound = true;
      error.status = 404;
      throw error;
    }

    return result[0].value;
  }

  async _del(key: string): Promise<void> {
    await db
      .delete(tinaKv)
      .where(and(eq(tinaKv.key, key), eq(tinaKv.namespace, this.namespace)));
  }

  async _batch(operations: Array<{ type: "put" | "del"; key: string; value?: any }>): Promise<void> {
    // Utiliser une transaction pour les opérations batch
    await db.transaction(async (tx) => {
      for (const op of operations) {
        if (op.type === "put") {
          await tx
            .insert(tinaKv)
            .values({
              key: op.key,
              value: op.value,
              namespace: this.namespace,
            })
            .onConflictDoUpdate({
              target: [tinaKv.key, tinaKv.namespace],
              set: { value: op.value },
            });
        } else if (op.type === "del") {
          await tx
            .delete(tinaKv)
            .where(and(eq(tinaKv.key, op.key), eq(tinaKv.namespace, this.namespace)));
        }
      }
    });
  }

  _iterator(options: IteratorOptions): PostgresIterator {
    return new PostgresIterator(this, options, this.namespace);
  }

  async _clear(options: { gt?: string; gte?: string; lt?: string; lte?: string }): Promise<void> {
    let query = db.delete(tinaKv).where(eq(tinaKv.namespace, this.namespace));

    const conditions = [];
    
    if (options.gt !== undefined) {
      conditions.push(sql`${tinaKv.key} > ${options.gt}`);
    }
    if (options.gte !== undefined) {
      conditions.push(sql`${tinaKv.key} >= ${options.gte}`);
    }
    if (options.lt !== undefined) {
      conditions.push(sql`${tinaKv.key} < ${options.lt}`);
    }
    if (options.lte !== undefined) {
      conditions.push(sql`${tinaKv.key} <= ${options.lte}`);
    }

    if (conditions.length > 0) {
      query = db
        .delete(tinaKv)
        .where(and(...conditions, eq(tinaKv.namespace, this.namespace)));
    }

    await query;
  }
}
