import { Component, OnInit, ViewChild } from '@angular/core';
import { DetailsComponentBase } from '@objects-view/details-component-base';
import { RefreshService } from '@shared/ui-api/refresh.service';
import { ConfigService } from '@shared/config/config.service';
import { NotificationService } from '@shared/ui-api/notification.service';
import { IZoneModel } from '@fleio-api/openstack/zone/model/zone.model';
import { ZonesApiService } from '@fleio-api/openstack/zone/zones-api.service';
import { UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { IRecordListModel } from '@fleio-api/openstack/zone/model/record-list.model';
import { IRecordsetModel } from '@fleio-api/openstack/zone/model/recordset.model';
import { IPermissionsModel } from '@fleio-api/base-model/IPermissionsModel';
import { IIcon } from '@shared/ui/common/interfaces/icon';

@Component({
  selector: 'app-zone-details-overview',
  templateUrl: './zone-details-overview.component.html',
  styleUrls: ['./zone-details-overview.component.scss']
})
export class ZoneDetailsOverviewComponent extends DetailsComponentBase<IZoneModel> implements OnInit {
  @ViewChild('formErrors') formErrors;
  recordsForm = this.formBuilder.group({
    recordsets: this.formBuilder.array([]),
  });
  minTTL = null;
  deleteRecordTooltip = $localize`Delete record`;
  addRecordTooltip = $localize`Add new record`;
  saveRecordsetTooltip = $localize`Save recordset`;
  deleteRecordsetTooltip = $localize`Delete recordset`;
  cancelRecordTooltip = $localize`Cancel edit`;
  changesWereMade = false;
  splitTxtInfoIcon: IIcon = {
    name: 'info',
    tooltip: $localize`Split data over 255 chars into multiple\nquoted strings using spaces\ne.g.:"firstsecond" becomes "first" "second"`,
    marginLeft: '-20px',
    marginTop: '-20px',
  };

  recordsetControls = this.recordsForm.controls.recordsets as UntypedFormArray;
  recordsetGroups: {
    recordset: UntypedFormGroup;
    recordsetData: IRecordsetModel;
    recordArray?: UntypedFormGroup[];
    canDeleteRecords?: boolean;
    fieldNames: string[];
    flex: string;
    flexLtLg: string;
  }[] = [];
  recordsList: IRecordListModel;
  permissions: IPermissionsModel;
  displayRecordsets: IRecordsetModel[];
  totalCount: number;
  loadedCount: number;
  editableRows = {
    undefined: true
  };

  constructor(
    private zonesApiService: ZonesApiService,
    private refreshService: RefreshService,
    public config: ConfigService,
    private notificationService: NotificationService,
    private formBuilder: UntypedFormBuilder,
  ) {
    super();
  }

  ngOnInit() {
    super.ngOnInit();

    if (this.objectController) {
      if (this.objectController.additionalObjects) {
        this.recordsList = this.objectController.additionalObjects.records;
        this.permissions = this.objectController.additionalObjects.permissions;
      }
    }

    if (this.recordsList) {
      if (this.recordsList.min_ttl && typeof this.recordsList.min_ttl === 'number') {
        this.minTTL = this.recordsList.min_ttl;
      }
      this.totalCount = this.recordsList.read_only_recordsets.length + this.recordsList.recordsets.length;
      this.loadedCount = this.recordsList.read_only_recordsets.length;
      this.displayRecordsets = this.recordsList.recordsets;

      this.createRecordsetControls();
    }
  }

  getFieldNames(recordType: string): string[] {
    switch (recordType) {
      case 'MX':
        return ['priority', 'server'];
      case 'SSHFP':
        return ['algorithm', 'type', 'fingerprint'];
      case 'SRV':
        return ['priority', 'weight', 'port', 'hostname'];
      default:
        return ['text'];
    }
  }

  startEditingRow(recordset) {
    if (!this.editableRows[recordset.id]) {
      this.editableRows[recordset.id] = JSON.parse(JSON.stringify(recordset));
    }
  }

  createRecordGroup(fieldNames: string[]): UntypedFormGroup {
    const recordGroup = this.formBuilder.group({});
    for (const fieldName of fieldNames) {
      recordGroup.addControl(
        fieldName,
        this.formBuilder.control(
          {value: ''}, [Validators.required]
        )
      );
    }

    return recordGroup;
  }

  textToObj(text: string, fieldNames: string[]): Record<any, any> {
    const parts = text.split(' ');
    const obj = {};
    for (const [index, fieldName] of fieldNames.entries()) {
      if (index + 1 === fieldNames.length) {
        // last field
        obj[fieldName] = parts.length > index ? parts.splice(index).join(' ') : '';
      } else {
        obj[fieldName] = parts.length > index ? parts[index] : '';
      }
    }
    return obj;
  }

  objToText(obj: any, fieldNames: string[]): string {
    let text = '';
    for (const fieldName of fieldNames) {
      if (text) {
        text += ' ';
      }
      text += obj[fieldName] ? obj[fieldName] : '';
    }

    return text;
  }

  addSingleRecordsetControls(recordsetIndex: number, recordset: IRecordsetModel) {
    let ttlEntry;
    if (this.minTTL !== null) {
      ttlEntry = [recordset.ttl, Validators.min(this.minTTL)];
    } else {
      ttlEntry = [recordset.ttl];
    }
    const recordsetGroup = this.formBuilder.group({
      name: [recordset.name, Validators.required],
      ttl: ttlEntry,
      type: [recordset.type, Validators.required],
      records: this.formBuilder.array([]),
    });

    const recordControls = recordsetGroup.controls.records as UntypedFormArray;
    const recordArray = [];
    const fieldNames = this.getFieldNames(recordset.type);

    for (const record of recordset.records) {
      const recordControl = this.createRecordGroup(fieldNames);
      recordControl.setValue(this.textToObj(record, fieldNames));
      recordControls.push(recordControl);
      recordArray.push(recordControl);
    }

    if (!recordset.created) {
      recordsetGroup.controls.name.disable();
      recordsetGroup.controls.type.disable();
    }

    recordsetGroup.valueChanges.subscribe(() => {
      recordset.changed = true;
      this.changesWereMade = true;
    });

    recordsetGroup.controls.type.valueChanges.subscribe((newType) => {
      this.updateRecordsetValues();
      this.recordsList.recordsets[recordsetIndex].type = newType;
      this.createRecordsetControls();
    });

    this.recordsetControls.controls.push(recordsetGroup);
    this.recordsetGroups.push({
      recordset: recordsetGroup,
      recordArray,
      recordsetData: recordset,
      canDeleteRecords: recordset.records.length > 1,
      fieldNames,
      flex: ((470 - 10 * (fieldNames.length - 1)) / fieldNames.length).toString() + 'px',
      flexLtLg: ((270 - 10 * (fieldNames.length - 1)) / fieldNames.length).toString() + 'px',
    });
  }

  createRecordsetControls() {
    this.recordsetControls.clear();
    this.recordsetGroups = [];
    for (const [recordsetIndex, recordset] of this.recordsList.recordsets.entries()) {
      if (this.recordsetControls)
        {this.addSingleRecordsetControls(recordsetIndex, recordset);}
    }
  }

  updateRecordsetValues() {
    for (const [index, recordsetGroup] of this.recordsetGroups.entries()) {
      if (recordsetGroup.recordset.dirty) {
        this.recordsList.recordsets[index] = {...this.recordsList.recordsets[index], ...recordsetGroup.recordset.value};
        const textRecords = [];
        for (const record of this.recordsList.recordsets[index].records) {
          textRecords.push(this.objToText(record, recordsetGroup.fieldNames));
        }
        this.recordsList.recordsets[index].records = textRecords;
      }
    }
  }

  addRecordset() {
    this.updateRecordsetValues();
    const recordset = {
      name: '',
      records: [''],
      ttl: 1800,
      type: 'A',
      created: true,
      changed: true, // newly added recordset should be treated as a change
      zone_id: this.object.id,
    };
    this.recordsList.recordsets.push(recordset);
    this.displayRecordsets = [...this.recordsList.recordsets];
    this.createRecordsetControls();
  }

  deleteRecordset(recordsetIndex: number) {
    const recordset = this.recordsList.recordsets[recordsetIndex];
    this.notificationService.confirmDialog({
      message: $localize`Are you sure?`,
      title: $localize`Delete recordset ${recordset.id || ''}`,
    }).subscribe((result) => {
      if (result?.button === 'yes') {
        this.updateRecordsetValues();
        if (this.recordsList.recordsets[recordsetIndex].created) {
          // newly created, not saved, just remove it
          this.recordsList.recordsets.splice(recordsetIndex, 1);
        } else {
          // to be removed
          this.recordsList.recordsets[recordsetIndex].deleted = true;
        }
        this.displayRecordsets = [...this.recordsList.recordsets];
        this.createRecordsetControls();
        if (!recordset.created) {
          this.saveRecordset(recordset.id, recordsetIndex, true);
        }
      }
    });
  }

  addRecord(recordsetIndex: number) {
    this.updateRecordsetValues();
    this.recordsList.recordsets[recordsetIndex].records.push('');
    this.displayRecordsets = [...this.recordsList.recordsets];
    this.createRecordsetControls();
  }

  deleteRecord(recordsetIndex: number, recordIndex: number) {
    this.updateRecordsetValues();
    this.recordsList.recordsets[recordsetIndex].records.splice(recordIndex, 1);
    this.recordsList.recordsets[recordsetIndex].changed = true;
    this.createRecordsetControls();
  }

  recordsetToSend(recordset, includeDeleted=true) {
    return includeDeleted ? recordset.changed || (recordset.deleted && !recordset.created) : recordset.changed;
  }

  isInvalid(): boolean {
    const invalid = this.recordsetGroups.filter(
      recordsetGroup =>
        // block form submit only for invalid & changed recordsets (those who are actually submitted)
         (
          recordsetGroup.recordset.valid === false &&
          this.recordsetToSend(recordsetGroup.recordsetData, false)
        )

    ).length > 0;
    if (invalid) {
      // form is invalid, disable save button
      return true;
    }

    const recordsetsToSend = [];
    for (const [index, recordset] of this.recordsList.recordsets.entries()) {
      if (this.recordsetToSend(recordset)) {
        // there is at least one recordset to be sent, enable save button
        return false;
      }
    }

    // no updated recordset to be sent found, disable save button
    return true;
  }

  saveChanges(recordsetId=null, recordsetIndex=null, fromDelete=false) {
    this.recordsForm.markAllAsTouched();
    if (this.isInvalid()) {
      return;
    }

    const recordsets: IRecordsetModel[] = [];
    this.updateRecordsetValues();
    for (const [index, recordset] of this.recordsList.recordsets.entries()) {
      if (this.recordsetToSend(recordset)) {
        if (recordsetId) {
          if (recordset.id === recordsetId) {
            // if recordset id was provided, update only that
            recordsets.push(recordset);
          }
        } else if ((typeof recordsetId === 'undefined' || recordsetId === null) && recordsetIndex) {
          if (recordsetIndex === index) {
            // if newly created recordset (undefined id) save only this
            recordsets.push(recordset);
          }
        } else {
          // no recordset id provided, update all changes
          recordsets.push(recordset);
        }
      }
    }

    if (recordsets && Array.isArray(recordsets) && recordsets.length === 0) {
      // do not proceed to submit if there are no changes
      return;
    }

    this.formErrors.hide();
    this.objectController.loading = true;
    this.zonesApiService.synchronizeRecords(
      this.object.id, recordsets
    ).subscribe(
      (recordList: IRecordListModel) => {
        if (recordsetId) {
          if (fromDelete) {
            // just remove entry by index
            this.recordsList.recordsets.splice(recordsetIndex, 1);
          } else {
            // update only the changed recordset from response
            for (const [localIndex, localRecordset] of this.recordsList.recordsets.entries()) {
              if (localRecordset.id === recordsetId) {
                for (const [responseIndex, responseRecordset] of recordList.recordsets.entries()) {
                  if (responseRecordset.id === recordsetId) {
                    this.recordsList.recordsets[localIndex] = responseRecordset;
                    break;
                  }
                }
                break;
              }
            }
          }
        } else if ((typeof recordsetId === 'undefined' || recordsetId === null) && recordsetIndex) {
          // update newly created recordset
          for (const responseRecordset of recordList.recordsets) {
            if (responseRecordset.id === recordList.created_recordsets[0]) {
              this.recordsList.recordsets[recordsetIndex] = responseRecordset;
            }
          }
        } else {
          // update all
          this.recordsList = recordList;
        }
        this.displayRecordsets = [...this.recordsList.recordsets];
        this.createRecordsetControls();
        this.notificationService.showMessage(recordList.detail);
        if (recordsetId) {
          delete this.editableRows[recordsetId];
        } else if ((typeof recordsetId === 'undefined' || recordsetId === null) && recordsetIndex) {
          // newly created, do nothing
        } else {
          this.editableRows = {
            undefined: true
          };
        }
      },
      (response) => {
        const internalServerError = response.status === 500;
        let backendErrors = {};
        if (response.error) {
          if (internalServerError) {
            if (this.authSvc.feature('ui.staff')) {
              backendErrors['detail'] = $localize`Internal server error while saving records, check logs for details.`;
            } else {
              backendErrors['detail'] = $localize`Internal server error while saving records, please contact support for more details.`;
            }
          } else {
            if (response.error instanceof Array) {
              let index = 0;
              for (const recordsetGroup of this.recordsetGroups) {
                if (this.recordsetToSend(recordsetGroup.recordsetData, false)) {
                  // recordset was sent, set related error
                  for (const key in response.error[index]) {
                    if (response.error[index].hasOwnProperty(key)) {
                      if (recordsetGroup.recordset.contains(key)) {
                        recordsetGroup.recordset.controls[key].setErrors(response.error[index][key]);
                      } else {
                        backendErrors = {...backendErrors, ...response.error[index]};
                      }
                    }
                  }
                  index += 1;
                }
              }
            } else {
              backendErrors = {...backendErrors, ...response.error};
            }
          }
        }
        this.formErrors.setBackendErrors(backendErrors);
        if (!internalServerError) {
          this.notificationService.showMessage($localize`Save changes failed, fix errors and try again`);
        }
      }
    ).add(() => {
      this.objectController.loading = false;
    });
  }

  saveRecordset(id, recordsetIndex=null, fromDelete=false) {
    this.saveChanges(id, recordsetIndex, fromDelete);
  }

  cancelEdit(recordset, recordsetIndex) {
    this.updateRecordsetValues();
    this.recordsList.recordsets[recordsetIndex] = JSON.parse(JSON.stringify(this.editableRows[recordset.id]));
    this.displayRecordsets = [...this.recordsList.recordsets];
    delete this.editableRows[recordset.id];
    this.createRecordsetControls();
  }
}
