import * as plugins from './plugins.js';
import * as paths from './paths.js';
import { logger } from './logger.js';
import type { DockerHost } from './classes.host.js';

export interface IDockerImageStoreConstructorOptions {
  /**
   * used for preparing images for longer term storage
   */
  localDirPath: string;
  /**
   * a smartbucket dir for longer term storage.
   */
  bucketDir: plugins.smartbucket.Directory;
}

export class DockerImageStore {
  public options: IDockerImageStoreConstructorOptions;

  constructor(optionsArg: IDockerImageStoreConstructorOptions) {
    this.options = optionsArg;
  }

  // Method to store tar stream
  public async storeImage(imageName: string, tarStream: plugins.smartstream.stream.Readable): Promise<void> {
    logger.log('info', `Storing image ${imageName}...`);
    const uniqueProcessingId = plugins.smartunique.shortId();

    const initialTarDownloadPath = plugins.path.join(this.options.localDirPath, `${uniqueProcessingId}.tar`);
    const extractionDir = plugins.path.join(this.options.localDirPath, uniqueProcessingId);
    // Create a write stream to store the tar file
    const writeStream = plugins.smartfile.fsStream.createWriteStream(initialTarDownloadPath);

    // lets wait for the write stream to finish
    await new Promise((resolve, reject) => {
      tarStream.pipe(writeStream);
      writeStream.on('finish', resolve);
      writeStream.on('error', reject);
    });
    logger.log('info', `Image ${imageName} stored locally for processing. Extracting...`);

    // lets process the image
    const tarArchive = await plugins.smartarchive.SmartArchive.fromArchiveFile(initialTarDownloadPath);
    await tarArchive.exportToFs(extractionDir);
    logger.log('info', `Image ${imageName} extracted.`);
    await plugins.smartfile.fs.remove(initialTarDownloadPath);
    logger.log('info', `deleted original tar to save space.`);
    logger.log('info', `now repackaging for s3...`);
    const smartfileIndexJson = await plugins.smartfile.SmartFile.fromFilePath(plugins.path.join(extractionDir, 'index.json'));
    const smartfileManifestJson = await plugins.smartfile.SmartFile.fromFilePath(plugins.path.join(extractionDir, 'manifest.json'));
    const smartfileOciLayoutJson = await plugins.smartfile.SmartFile.fromFilePath(plugins.path.join(extractionDir, 'oci-layout'));
    const smartfileRepositoriesJson = await plugins.smartfile.SmartFile.fromFilePath(plugins.path.join(extractionDir, 'repositories'));
    const indexJson = JSON.parse(smartfileIndexJson.contents.toString());
    const manifestJson = JSON.parse(smartfileManifestJson.contents.toString());
    const ociLayoutJson = JSON.parse(smartfileOciLayoutJson.contents.toString());
    const repositoriesJson = JSON.parse(smartfileRepositoriesJson.contents.toString());

    indexJson.manifests[0].annotations['io.containerd.image.name'] = imageName;
    manifestJson[0].RepoTags[0] = imageName;
    const repoFirstKey = Object.keys(repositoriesJson)[0];
    const repoFirstValue = repositoriesJson[repoFirstKey];
    repositoriesJson[imageName] = repoFirstValue;
    delete repositoriesJson[repoFirstKey];

    smartfileIndexJson.contents = Buffer.from(JSON.stringify(indexJson, null, 2));
    smartfileManifestJson.contents = Buffer.from(JSON.stringify(manifestJson, null, 2));
    smartfileOciLayoutJson.contents = Buffer.from(JSON.stringify(ociLayoutJson, null, 2));
    smartfileRepositoriesJson.contents = Buffer.from(JSON.stringify(repositoriesJson, null, 2));
    await Promise.all([
      smartfileIndexJson.write(),
      smartfileManifestJson.write(),
      smartfileOciLayoutJson.write(),
      smartfileRepositoriesJson.write(),
    ]);

    logger.log('info', 'repackaging archive for s3...');
    const tartools = new plugins.smartarchive.TarTools();
    const newTarPack = await tartools.packDirectory(extractionDir);
    const finalTarName = `${uniqueProcessingId}.processed.tar`;
    const finalTarPath = plugins.path.join(this.options.localDirPath, finalTarName);
    const finalWriteStream = plugins.smartfile.fsStream.createWriteStream(finalTarPath);
    await new Promise((resolve, reject) => {
      newTarPack.finalize();
      newTarPack.pipe(finalWriteStream);
      finalWriteStream.on('finish', resolve);
      finalWriteStream.on('error', reject);
    });
    logger.log('ok', `Repackaged image ${imageName} for s3.`);
    await plugins.smartfile.fs.remove(extractionDir);
    const finalTarReadStream = plugins.smartfile.fsStream.createReadStream(finalTarPath);
    await this.options.bucketDir.fastPutStream({
      stream: finalTarReadStream,
      path: `${imageName}.tar`,
    });
    await plugins.smartfile.fs.remove(finalTarPath);
  }

  public async start() {
    await plugins.smartfile.fs.ensureEmptyDir(this.options.localDirPath);
  }

  public async stop() {}

  // Method to retrieve tar stream
  public async getImage(imageName: string): Promise<plugins.smartstream.stream.Readable> {
    const imagePath = plugins.path.join(this.options.localDirPath, `${imageName}.tar`);

    if (!(await plugins.smartfile.fs.fileExists(imagePath))) {
      throw new Error(`Image ${imageName} does not exist.`);
    }

    return plugins.smartfile.fsStream.createReadStream(imagePath);
  }
}
