import type { Cloudly } from '../classes.cloudly.js';
import * as plugins from '../plugins.js';
import * as paths from '../paths.js';

import { Image } from './classes.image.js';

export class ImageManager {
  cloudlyRef: Cloudly;
  public typedrouter = new plugins.typedrequest.TypedRouter();
  public smartbucketInstance: plugins.smartbucket.SmartBucket;
  public imageDir: plugins.smartbucket.Directory;
  public dockerImageStore: plugins.docker.DockerImageStore;

  get db() {
    return this.cloudlyRef.mongodbConnector.smartdataDb;
  }

  public CImage = plugins.smartdata.setDefaultManagerForDoc(this, Image);

  constructor(cloudlyRefArg: Cloudly) {
    this.cloudlyRef = cloudlyRefArg;

    this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_CreateImage>(
        'createImage',
        async (reqArg, toolsArg) => {
          await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqArg);
          const image = await this.CImage.create({
            name: reqArg.name,
            description: reqArg.description,
            versions: [],
          });
          return {
            image: await image.createSavableObject(),
          };
        },
      ),
    );

    this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetImage>(
      new plugins.typedrequest.TypedHandler('getImage', async (reqArg, toolsArg) => {
        await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqArg);
        const image = await this.CImage.getInstance({
          id: reqArg.imageId,
        });
        return {
          image: await image.createSavableObject(),
        };
      }),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_DeleteImage>(
        'deleteImage',
        async (reqArg, toolsArg) => {
          await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqArg);
          const image = await this.CImage.getInstance({
            id: reqArg.imageId,
          });
          await image.delete();
          return {};
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>(
        'getAllImages',
        async (requestArg, toolsArg) => {
          await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], requestArg);
          const images = await this.CImage.getInstances({});
          return {
            images: await Promise.all(
              images.map((image) => {
                return image.createSavableObject();
              }),
            ),
          };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PushImageVersion>(
        'pushImageVersion',
        async (reqArg, toolsArg) => {
          await plugins.smartguard.passGuardsOrReject(reqArg, [
            this.cloudlyRef.authManager.validIdentityGuard,
          ]);
          const refImage = await this.CImage.getInstance({
            id: reqArg.imageId,
          });
          if (!refImage) {
            throw new plugins.typedrequest.TypedResponseError('Image not found');
          }
          const imageVersion = reqArg.versionString;
          console.log(
            `got request to push image version ${imageVersion} for image ${refImage.data.name}`,
          );
          const imagePushStream = reqArg.imageStream;
          (async () => {
            const smartWebDuplex = new plugins.smartstream.webstream.WebDuplexStream<
              Uint8Array,
              Uint8Array
            >({
              writeFunction: async (chunkArg, toolsArg) => {
                console.log(chunkArg);
                return chunkArg;
              },
            });
            imagePushStream.writeToWebstream(smartWebDuplex.writable);
            await this.dockerImageStore.storeImage(
              refImage.id,
              plugins.smartstream.SmartDuplex.fromWebReadableStream(smartWebDuplex.readable),
            );
          })();
          return {
            allowed: true,
          };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PullImageVersion>(
        'pullImageVersion',
        async (reqArg) => {
          const image = await this.CImage.getInstance({
            id: reqArg.imageId,
          });
          const imageVersion = image.data.versions.find(
            (version) => version.versionString === reqArg.versionString,
          );
          const readable = this.imageDir.fastGetStream(
            {
              path: await image.getStoragePath(reqArg.versionString),
            },
            'webstream',
          );
          const imageVirtualStream = new plugins.typedrequest.VirtualStream();
          return {
            imageStream: imageVirtualStream,
          };
        },
      ),
    );
  }

  public async start() {
    // lets setup s3
    const s3Descriptor: plugins.tsclass.storage.IS3Descriptor =
      await this.cloudlyRef.config.appData.waitForAndGetKey('s3Descriptor');
    console.log(this.cloudlyRef.config.data.s3Descriptor);
    this.smartbucketInstance = new plugins.smartbucket.SmartBucket(
      this.cloudlyRef.config.data.s3Descriptor,
    );
    const bucket = await this.smartbucketInstance.getBucketByName('cloudly-test');
    await bucket.fastPut({ path: 'images/00init', contents: 'init' });

    this.imageDir = await bucket.getDirectoryFromPath({
      path: '/images',
    });

    // lets setup dockerstore
    await plugins.smartfile.fs.ensureDir(paths.dockerImageStoreDir);
    this.dockerImageStore = new plugins.docker.DockerImageStore({
      localDirPath: paths.dockerImageStoreDir,
      bucketDir: this.imageDir,
    });
  }
}