import * as plugins from './plugins.js';

export type TClientType = 'api' | 'ci' | 'coreflow' | 'cli' | 'serverconfig';

import { Image } from './classes.image.js';
import { Service } from './classes.service.js';
import { Cluster } from './classes.cluster.js';
import { SecretBundle } from './classes.secretbundle.js';
import { SecretGroup } from './classes.secretgroup.js';
import { ExternalRegistry } from './classes.externalregistry.js';

export class CloudlyApiClient {
  private cloudlyUrl: string;
  private registerAs: string;

  public typedrouter = new plugins.typedrequest.TypedRouter();
  public typedsocketClient: plugins.typedsocket.TypedSocket;

  // Subjects
  public configUpdateSubject = new plugins.smartrx.rxjs.Subject<
    plugins.servezoneInterfaces.requests.config.IRequest_Cloudly_Coreflow_PushClusterConfig['request']
  >();

  public serverActionSubject = new plugins.smartrx.rxjs.Subject<
    plugins.servezoneInterfaces.requests.server.IRequest_TriggerServerAction['request']
  >();

  constructor(optionsArg: {
    registerAs: TClientType;
    cloudlyUrl?: string;
  }) {
    this.registerAs = optionsArg.registerAs;
    this.cloudlyUrl =
      optionsArg?.cloudlyUrl || process.env.CLOUDLY_URL || 'https://cloudly.layer.io:443';

    console.log(
      `creating LoleCloudlyClient: registering as ${this.registerAs} and target url ${this.cloudlyUrl}`
    );

    this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.config.IRequest_Cloudly_Coreflow_PushClusterConfig>(
      new plugins.typedrequest.TypedHandler('pushClusterConfig', async (dataArg) => {
        this.configUpdateSubject.next(dataArg);
        return {};
      })
    );

    this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.server.IRequest_TriggerServerAction>(
      new plugins.typedrequest.TypedHandler('triggerServerAction', async (dataArg) => {
        this.serverActionSubject.next(dataArg);
        return {
          actionConfirmed: true,
        };
      })
    );
  }

  public async start() {
    this.typedsocketClient = await plugins.typedsocket.TypedSocket.createClient(
      this.typedrouter,
      this.cloudlyUrl
    );
    console.log(
      `CloudlyClient connected to cloudly at ${this.cloudlyUrl}. Remember to get an identity.`
    );
  }

  public async stop() {
    await this.typedsocketClient.stop();
  }

  public identity: plugins.servezoneInterfaces.data.IIdentity;
  public async getIdentityByToken(
    token: string,
    optionsArg?: {
      tagConnection?: boolean;
      statefullIdentity?: boolean;
    }
  ): Promise<plugins.servezoneInterfaces.data.IIdentity> {
    optionsArg = Object.assign({}, {
      tagConnection: false,
      statefullIdentity: true,
    }, optionsArg);

    const identityRequest =
      this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByToken>(
        'getIdentityByToken'
      );
    console.log(`trying to get identity from cloudly with supplied jumpCodeArg: ${token}`);
    const response = await identityRequest.fire({
      token: token,
    });
    console.log('got identity response');
    const identity = response.identity;

    if (optionsArg.tagConnection) {
      this.typedsocketClient.addTag('identity', identity);
    }

    if (optionsArg.statefullIdentity) {
      this.identity = identity;
    }

    return identity;
  }

  /**
   * will use statefull identity by default
   */
  public async getClusterConfigFromCloudlyByIdentity(
    identityArg: plugins.servezoneInterfaces.data.IIdentity = this.identity
  ): Promise<plugins.servezoneInterfaces.data.ICluster> {
    const clusterConfigRequest =
      this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetClusterConfig>(
        'getClusterConfig'
      );
    const response = await clusterConfigRequest.fire({
      identity: identityArg,
    });
    return response.configData;
  }

  /**
   * will use statefull identity by default
   */
  public async getServerConfigFromCloudlyByIdentity(
    identityArg: plugins.servezoneInterfaces.data.IIdentity = this.identity
  ): Promise<plugins.servezoneInterfaces.data.IServer> {
    const serverConfigRequest =
      this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetServerConfig>(
        'getServerConfig'
      );
    const response = await serverConfigRequest.fire({
      identity: identityArg,
      serverId: '', // TODO: get server id here
    });
    return response.configData;
  }

  /**
   * gets a certificate for a domain used by a service
   */
  public async getCertificateForDomain(optionsArg: {
    domainName: string;
    type: plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetCertificateForDomain['request']['type'];
    identity?: plugins.servezoneInterfaces.data.IIdentity;
  }): Promise<plugins.tsclass.network.ICert> {
    optionsArg.identity = optionsArg.identity || this.identity;
    if (!optionsArg.identity) {
      throw new Error('identity is required. Either provide one or login first.');
    }
    const typedCertificateRequest =
      this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetCertificateForDomain>(
        'getCertificateForDomain'
      );
    const typedResponse = await typedCertificateRequest.fire({
      identity: this.identity, // do proper auth here
      domainName: optionsArg.domainName,
      type: optionsArg.type,
    });
    return typedResponse.certificate;
  }

  public externalRegistry = {
    // ExternalRegistry
    getRegistryById: async (registryNameArg: string) => {
      return ExternalRegistry.getExternalRegistryById(this, registryNameArg);
    },
    getRegistries: async () => {
      return ExternalRegistry.getExternalRegistries(this);
    },
    createRegistry: async (optionsArg: Parameters<typeof ExternalRegistry.createExternalRegistry>[1]) => {
      return ExternalRegistry.createExternalRegistry(this, optionsArg);
    }
  }

  public image = {
    // Images
    getImageById: async (imageIdArg: string) => {
      return Image.getImageById(this, imageIdArg);
    },
    getImages: async () => {
      return Image.getImages(this);
    },
    createImage: async (optionsArg: Parameters<typeof Image.createImage>[1]) => {
      return Image.createImage(this, optionsArg);
    }
  }

  public services = {
    // Services
    getServiceById: async (serviceIdArg: string) => {
      return Service.getServiceById(this, serviceIdArg);
    },
    getServices: async () => {
      return Service.getServices(this);
    },
    createService: async (optionsArg: Parameters<typeof Service.createService>[1]) => {
      return Service.createService(this, optionsArg);
    }
  }

  public cluster = {
    // Clusters
    getClusterById: async (clusterIdArg: string) => {
      return Cluster.getClusterById(this, clusterIdArg);
    },
    getClusters: async () => {
      return Cluster.getClusters(this);
    },
    createCluster: async (optionsArg: Parameters<typeof Cluster.createCluster>[1]) => {
      return Cluster.createCluster(this, optionsArg);
    }
  }

  public secretbundle = {
    // SecretBundles
    getSecretBundleById: async (secretBundleIdArg: string) => {
      return SecretBundle.getSecretBundleById(this, secretBundleIdArg);
    },
    getSecretBundles: async () => {
      return SecretBundle.getSecretBundles(this);
    },
    createSecretBundle: async (optionsArg: Parameters<typeof SecretBundle.createSecretBundle>[1]) => {
      return SecretBundle.createSecretBundle(this, optionsArg);
    }
  }

  public secretgroup = {
    // SecretGroups
    getSecretGroupById: async (secretGroupIdArg: string) => {
      return SecretGroup.getSecretGroupById(this, secretGroupIdArg);
    },
    getSecretGroups: async () => {
      return SecretGroup.getSecretGroups(this);
    },
    createSecretGroup: async (optionsArg: Parameters<typeof SecretGroup.createSecretGroup>[1]) => {
      return SecretGroup.createSecretGroup(this, optionsArg);
    }
  }
}