import { EventEmitter } from 'eventemitter3';
import { Database } from '../../database/database.js';
import { nanoid } from 'nanoid';
import { AddArtifactOptions, FindArtifactsOptions } from './artifacts.schemas.js';
import { createHash } from 'crypto';

type ArtifactRepoEvents = {};

type ArtifactRepoOptions = {
  database: Database;
};

class ArtifactRepo extends EventEmitter<ArtifactRepoEvents> {
  #options: ArtifactRepoOptions;

  constructor(options: ArtifactRepoOptions) {
    super();
    this.#options = options;
  }

  public get = async (id: string) => {
    const { database } = this.#options;
    const db = await database.instance;
    const result = await db('artifacts').where({ id }).first();
    return result || null;
  };

  public add = async (options: AddArtifactOptions) => {
    const { database } = this.#options;
    const db = await database.instance;
    const id = nanoid();

    await db('artifacts').insert({
      id,
      name: options.name,
      runId: options.runId,
      loadId: options.loadId,
      data: Buffer.from(options.data).toString('base64'),
      createdAt: new Date(),
    });
  };

  public prepareRemove = async (options: FindArtifactsOptions) => {
    const { database } = this.#options;
    const db = await database.instance;

    const query = db('artifacts').select('id');

    if (options.ids) {
      query.whereIn('id', options.ids);
    }

    if (options.runId) {
      query.where({ runId: options.runId });
    }

    if (options.loadId) {
      query.where({ loadId: options.loadId });
    }

    if (options.offset) {
      query.offset(options.offset);
    }

    if (options.limit) {
      query.limit(options.limit);
    }

    const result = await query;
    const ids = result.map((row) => row.id);
    const token = ids.map((id) => Buffer.from(id).toString('base64')).join('|');
    const hash = createHash('sha256').update(token).digest('hex');
    return {
      ids,
      hash,
    };
  };

  public remove = async (hash: string, ids: string[]) => {
    const { database } = this.#options;
    const db = await database.instance;
    const token = ids.map((id) => Buffer.from(id).toString('base64')).join('|');
    const actualHash = createHash('sha256').update(token).digest('hex');

    if (hash !== actualHash) {
      throw new Error('Invalid hash');
    }

    await db('artifacts').whereIn('id', ids).delete();
  };

  public find = async (options: FindArtifactsOptions) => {
    const { database } = this.#options;
    const db = await database.instance;

    const query = db('artifacts').select(['id', 'name', 'runId', 'loadId']);

    if (options.ids) {
      query.whereIn('id', options.ids);
    }

    if (options.runId) {
      query.where({ runId: options.runId });
    }

    if (options.loadId) {
      query.where({ loadId: options.loadId });
    }

    if (options.offset) {
      query.offset(options.offset);
    }

    if (options.limit) {
      query.limit(options.limit);
    }

    const results = await query;
    return results;
  };
}

export { addArtifactSchema, findArtifactsSchema } from './artifacts.schemas.js';
export { ArtifactRepo };
