// Copyright 2017 The Kubernetes Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {HttpClient} from '@angular/common/http';
import {Component, OnInit} from '@angular/core';
import {AbstractControl, FormArray, FormBuilder, FormGroup, Validators} from '@angular/forms';
import {MatDialog} from '@angular/material';
import {ActivatedRoute} from '@angular/router';
import {
  AppDeploymentSpec,
  EnvironmentVariable,
  Namespace,
  NamespaceList,
  PortMapping,
  Protocols,
  SecretList,
} from '@api/backendapi';

import {CreateService} from '../../../common/services/create/service';
import {HistoryService} from '../../../common/services/global/history';
import {NamespaceService} from '../../../common/services/global/namespace';

import {CreateNamespaceDialog} from './createnamespace/dialog';
import {CreateSecretDialog} from './createsecret/dialog';
import {DeployLabel} from './deploylabel/deploylabel';
import {validateUniqueName} from './validator/uniquename.validator';
import {FormValidators} from './validator/validators';

// Label keys for predefined labels
const APP_LABEL_KEY = 'k8s-app';

@Component({
  selector: 'kd-create-from-form',
  templateUrl: './template.html',
  styleUrls: ['./style.scss'],
})
export class CreateFromFormComponent implements OnInit {
  readonly nameMaxLength = 24;

  showMoreOptions_ = false;
  namespaces: string[];
  protocols: string[];
  secrets: string[];
  isExternal = false;
  labelArr: DeployLabel[] = [];

  form: FormGroup;

  constructor(
    private readonly namespace_: NamespaceService,
    private readonly create_: CreateService,
    private readonly history_: HistoryService,
    private readonly http_: HttpClient,
    private readonly route_: ActivatedRoute,
    private readonly fb_: FormBuilder,
    private readonly dialog_: MatDialog,
  ) {}

  ngOnInit(): void {
    this.form = this.fb_.group({
      name: ['', Validators.compose([Validators.required, FormValidators.namePattern])],
      containerImage: ['', Validators.required],
      replicas: [1, Validators.compose([Validators.required, FormValidators.isInteger])],
      description: [''],
      namespace: [this.route_.snapshot.params.namespace || '', Validators.required],
      imagePullSecret: [''],
      cpuRequirement: ['', Validators.compose([Validators.min(0), FormValidators.isInteger])],
      memoryRequirement: ['', Validators.compose([Validators.min(0), FormValidators.isInteger])],
      containerCommand: [''],
      containerCommandArgs: [''],
      runAsPrivileged: [false],
      portMappings: this.fb_.control([]),
      variables: this.fb_.control([]),
      labels: this.fb_.control([]),
    });
    this.labelArr = [new DeployLabel(APP_LABEL_KEY, '', false), new DeployLabel()];
    this.name.valueChanges.subscribe(v => {
      this.labelArr[0].value = v;
      this.labels.patchValue([{index: 0, value: v}]);
    });
    this.namespace.valueChanges.subscribe((namespace: string) => {
      this.name.clearAsyncValidators();
      this.name.setAsyncValidators(validateUniqueName(this.http_, namespace));
      this.name.updateValueAndValidity();
    });
    this.http_.get('api/v1/namespace').subscribe((result: NamespaceList) => {
      this.namespaces = result.namespaces.map((namespace: Namespace) => namespace.objectMeta.name);
      this.namespace.patchValue(
        !this.namespace_.areMultipleNamespacesSelected()
          ? this.route_.snapshot.params.namespace || this.namespaces[0]
          : this.namespaces[0],
      );
    });
    this.http_
      .get('api/v1/appdeployment/protocols')
      .subscribe((protocols: Protocols) => (this.protocols = protocols.protocols));
  }

  get name(): AbstractControl {
    return this.form.get('name');
  }

  get containerImage(): AbstractControl {
    return this.form.get('containerImage');
  }

  get replicas(): AbstractControl {
    return this.form.get('replicas');
  }

  get description(): AbstractControl {
    return this.form.get('description');
  }

  get namespace(): AbstractControl {
    return this.form.get('namespace');
  }

  get imagePullSecret(): AbstractControl {
    return this.form.get('imagePullSecret');
  }

  get cpuRequirement(): AbstractControl {
    return this.form.get('cpuRequirement');
  }

  get memoryRequirement(): AbstractControl {
    return this.form.get('memoryRequirement');
  }

  get containerCommand(): AbstractControl {
    return this.form.get('containerCommand');
  }

  get containerCommandArgs(): AbstractControl {
    return this.form.get('containerCommandArgs');
  }

  get runAsPrivileged(): AbstractControl {
    return this.form.get('runAsPrivileged');
  }

  get portMappings(): FormArray {
    return this.form.get('portMappings') as FormArray;
  }

  get variables(): FormArray {
    return this.form.get('variables') as FormArray;
  }

  get labels(): FormArray {
    return this.form.get('labels') as FormArray;
  }

  changeExternal(isExternal: boolean): void {
    this.isExternal = isExternal;
  }

  resetImagePullSecret(): void {
    this.imagePullSecret.patchValue('');
  }

  isCreateDisabled(): boolean {
    return !this.form.valid || this.create_.isDeployDisabled();
  }

  getSecrets(): void {
    this.http_.get(`api/v1/secret/${this.namespace.value}`).subscribe((result: SecretList) => {
      this.secrets = result.secrets.map(secret => secret.objectMeta.name);
    });
  }

  cancel(): void {
    this.history_.goToPreviousState('overview');
  }

  areMultipleNamespacesSelected(): boolean {
    return this.namespace_.areMultipleNamespacesSelected();
  }

  /**
   * Returns true if more options have been enabled and should be shown, false otherwise.
   */
  isMoreOptionsEnabled(): boolean {
    return this.showMoreOptions_;
  }

  /**
   * Shows or hides more options.
   * @export
   */
  switchMoreOptions(): void {
    this.showMoreOptions_ = !this.showMoreOptions_;
  }

  handleNamespaceDialog(): void {
    const dialogData = {data: {namespaces: this.namespaces}};
    const dialogDef = this.dialog_.open(CreateNamespaceDialog, dialogData);
    dialogDef
      .afterClosed()
      .take(1)
      .subscribe(answer => {
        /**
         * Handles namespace dialog result. If namespace was created successfully then it
         * will be selected, otherwise first namespace will be selected.
         */
        if (answer) {
          this.namespaces.push(answer);
          this.namespace.patchValue(answer);
        } else {
          this.namespace.patchValue(this.namespaces[0]);
        }
      });
  }

  handleCreateSecretDialog(): void {
    const dialogData = {data: {namespace: this.namespace.value}};
    const dialogDef = this.dialog_.open(CreateSecretDialog, dialogData);
    dialogDef
      .afterClosed()
      .take(1)
      .subscribe(response => {
        /**
         * Handles create secret dialog result. If the secret was created successfully, then it
         * will be selected,
         * otherwise None is selected.
         */
        if (response) {
          this.secrets.push(response);
          this.imagePullSecret.patchValue(response);
        } else {
          this.imagePullSecret.patchValue('');
        }
      });
  }

  /**
   * Returns true when the given port mapping is filled by the user, i.e., is not empty.
   */
  isPortMappingFilled(portMapping: PortMapping): boolean {
    return !!portMapping.port && !!portMapping.targetPort;
  }

  isVariableFilled(variable: EnvironmentVariable): boolean {
    return !!variable.name;
  }

  isNumber(value: string): boolean {
    return typeof value === 'number' && !isNaN(value);
  }

  /**
   * Converts array of DeployLabel to array of backend api label.
   */
  toBackendApiLabels(labels: DeployLabel[]): DeployLabel[] {
    labels[0].key = this.labelArr[0].key;
    labels[0].value = this.labelArr[0].value;
    return labels.filter((label: DeployLabel) => {
      return label.key.length !== 0 && label.value.length !== 0;
    });
  }

  deploy(): void {
    const portMappings = this.portMappings.value.portMappings || [];
    const variables = this.variables.value.variables || [];
    const labels = this.labels.value.labels || [];
    const spec: AppDeploymentSpec = {
      containerImage: this.containerImage.value,
      imagePullSecret: this.imagePullSecret.value ? this.imagePullSecret.value : null,
      containerCommand: this.containerCommand.value ? this.containerCommand.value : null,
      containerCommandArgs: this.containerCommandArgs.value
        ? this.containerCommandArgs.value
        : null,
      isExternal: this.isExternal,
      name: this.name.value,
      description: this.description.value ? this.description.value : null,
      portMappings: portMappings.filter(this.isPortMappingFilled),
      variables: variables.filter(this.isVariableFilled),
      replicas: this.replicas.value,
      namespace: this.namespace.value,
      cpuRequirement: this.isNumber(this.cpuRequirement.value) ? this.cpuRequirement.value : null,
      memoryRequirement: this.isNumber(this.memoryRequirement.value)
        ? `${this.memoryRequirement.value}Mi`
        : null,
      labels: this.toBackendApiLabels(labels),
      runAsPrivileged: this.runAsPrivileged.value,
    };
    this.create_.deploy(spec);
  }
}
