// SPDX-License-Identifier: Apache-2.0

import {type Secrets} from '../../../resources/secret/secrets.js';
import {type CoreV1Api, V1ObjectMeta, V1Secret, type V1SecretList} from '@kubernetes/client-node';
import {type NamespaceName} from '../../../../../types/namespace/namespace-name.js';
import {type Optional} from '../../../../../types/index.js';
import {ResourceNotFoundError} from '../../../errors/resource-operation-errors.js';
import {ResourceType} from '../../../resources/resource-type.js';
import {Duration} from '../../../../../core/time/duration.js';
import {type SecretType} from '../../../resources/secret/secret-type.js';
import {type Secret} from '../../../resources/secret/secret.js';
import {KubeApiResponse} from '../../../kube-api-response.js';
import {ResourceOperation} from '../../../resources/resource-operation.js';

export class K8ClientSecrets implements Secrets {
  public constructor(private readonly kubeClient: CoreV1Api) {}

  public async create(
    namespace: NamespaceName,
    name: string,
    secretType: SecretType,
    data: Record<string, string>,
    labels: Optional<Record<string, string>>,
  ): Promise<boolean> {
    return await this.createOrReplaceWithForce(namespace, name, secretType, data, labels, false, true);
  }

  public async createOrReplace(
    namespace: NamespaceName,
    name: string,
    secretType: SecretType,
    data: Record<string, string>,
    labels: Optional<Record<string, string>>,
  ): Promise<boolean> {
    return await this.createOrReplaceWithForce(namespace, name, secretType, data, labels, false, false);
  }

  public async delete(namespace: NamespaceName, name: string): Promise<boolean> {
    try {
      await this.kubeClient.deleteNamespacedSecret({name, namespace: namespace.name});
    } catch (error) {
      if (KubeApiResponse.isNotFound(error)) {
        return true;
      }
      KubeApiResponse.throwError(error, ResourceOperation.DELETE, ResourceType.SECRET, namespace, name);
    }
    return true;
  }

  public async replace(
    namespace: NamespaceName,
    name: string,
    secretType: SecretType,
    data: Record<string, string>,
    labels: Optional<Record<string, string>>,
  ): Promise<boolean> {
    return this.createOrReplaceWithForce(namespace, name, secretType, data, labels, true);
  }

  public async read(namespace: NamespaceName, name: string): Promise<Secret> {
    let result: V1Secret;
    try {
      result = await this.kubeClient.readNamespacedSecret({name, namespace: namespace.name});
    } catch (error) {
      KubeApiResponse.throwError(error, ResourceOperation.READ, ResourceType.SECRET, namespace, name);
    }
    return {
      name: result.metadata!.name as string,
      labels: result.metadata!.labels as Record<string, string>,
      namespace: result.metadata!.namespace as string,
      type: result.type as string,
      data: result.data as Record<string, string>,
    };
  }

  public async list(namespace: NamespaceName, labels?: string[]): Promise<Array<Secret>> {
    const labelSelector: string = labels ? labels.join(',') : undefined;
    let secretList: V1SecretList;
    try {
      secretList = await this.kubeClient.listNamespacedSecret({
        namespace: namespace.toString(),
        labelSelector,
        timeoutSeconds: Duration.ofMinutes(5).toMillis(),
      });
    } catch (error) {
      KubeApiResponse.throwError(error, ResourceOperation.LIST, ResourceType.SECRET, namespace, '');
    }

    return secretList.items.map((secret: V1Secret): Secret => {
      return {
        name: secret.metadata!.name as string,
        labels: secret.metadata!.labels as Record<string, string>,
        namespace: secret.metadata!.namespace as string,
        type: secret.type as string,
        data: secret.data as Record<string, string>,
      };
    });
  }

  public async exists(namespace: NamespaceName, name: string): Promise<boolean> {
    try {
      const cm: Secret = await this.read(namespace, name);
      return !!cm;
    } catch (error) {
      if (error instanceof ResourceNotFoundError) {
        return false;
      } else {
        throw error;
      }
    }
  }

  private async createOrReplaceWithForce(
    namespace: NamespaceName,
    name: string,
    secretType: SecretType,
    data: Record<string, string>,
    labels: Optional<Record<string, string>>,
    forceReplace?: boolean,
    forceCreate?: boolean,
  ): Promise<boolean> {
    const replace: boolean = await this.shouldReplace(namespace, name, forceReplace, forceCreate);
    const v1Secret: V1Secret = new V1Secret();
    v1Secret.apiVersion = 'v1';
    v1Secret.kind = 'Secret';
    v1Secret.type = secretType;
    v1Secret.data = data;
    v1Secret.metadata = new V1ObjectMeta();
    v1Secret.metadata.name = name;
    v1Secret.metadata.labels = labels;

    try {
      await (replace
        ? this.kubeClient.replaceNamespacedSecret({name, namespace: namespace.name, body: v1Secret})
        : this.kubeClient.createNamespacedSecret({namespace: namespace.name, body: v1Secret}));
    } catch (error) {
      KubeApiResponse.throwError(
        error,
        replace ? ResourceOperation.REPLACE : ResourceOperation.CREATE,
        ResourceType.SECRET,
        namespace,
        name,
      );
    }
    return true;
  }

  private async shouldReplace(
    namespace: NamespaceName,
    name: string,
    forceReplace?: boolean,
    forceCreate?: boolean,
  ): Promise<boolean> {
    if (forceReplace && !forceCreate) {
      return true;
    }

    if (forceCreate) {
      return false;
    }

    return await this.exists(namespace, name);
  }
}
