import { Component, OnInit, Inject, ViewChild, ElementRef } from '@angular/core';
import { UntypedFormControl } from '@angular/forms';
import { MatAutocomplete, MatAutocompleteSelectedEvent, MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { ToastrService } from 'ngx-toastr';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators'
import { ProductSpecification, ProductSpecificationUpdate } from 'src/app/openApis/productCatalogManagement/models';
import { ProductSpecificationService } from 'src/app/openApis/productCatalogManagement/services';
import { ServiceSpecification } from 'src/app/openApis/serviceCatalogManagement/models';
import { ServiceSpecificationService } from 'src/app/openApis/serviceCatalogManagement/services';
import { ConfirmCharacteristicAssignmentComponent } from './confirm-characteristic-assignment/confirm-characteristic-assignment.component';


@Component({
  selector: 'app-assign-service-specification',
  templateUrl: './assign-service-specification.component.html',
  styleUrls: ['./assign-service-specification.component.scss']
})
export class AssignServiceSpecificationComponent implements OnInit {

  constructor(
    @Inject(MAT_DIALOG_DATA) public data: {
      productSpec: ProductSpecification
    },
    private dialogRef: MatDialogRef<AssignServiceSpecificationComponent>,
    private productSpecService: ProductSpecificationService,
    private serviceSpecService: ServiceSpecificationService,
    private toast: ToastrService,
    private dialog: MatDialog
    
  ) { }

  @ViewChild('specInput') specInput: ElementRef<HTMLInputElement>;  
  @ViewChild('specInput', { read: MatAutocompleteTrigger }) matAutocompleteTrigger: MatAutocompleteTrigger;
  @ViewChild('autoComplete') matAutocomplete: MatAutocomplete;


  displayedColumns = ['name', 'actions']
  dataSource  = new MatTableDataSource<ProductSpecification>()
  @ViewChild(MatSort, {static: true}) sort: MatSort;

  specInputCtrl = new UntypedFormControl();
  nonSelectedSpecs: ProductSpecification[] = [];
  filteredSpecs$: Observable<ProductSpecification[]>
  selectedSpecs: ProductSpecification[] = []

  // the subject holds the data when it arrives from the api
  private availableSpecsSubject = new BehaviorSubject<ProductSpecification[]>([]);


  ngOnInit(): void {
    this.listServiceSpecs();

    // for the filtering to work when user starts typing in the input
    this.filteredSpecs$ = combineLatest([
      this.specInputCtrl.valueChanges.pipe(startWith('')),
      this.availableSpecsSubject.asObservable()
      ]).pipe(
        map(([inputValue, specs]) => {
          const filterValue = typeof inputValue === 'string' ? inputValue.toLowerCase() : '';
          return this.performFilter(filterValue, specs);
        })
      );
  }

  listServiceSpecs() {
    this.serviceSpecService.listServiceSpecification({}).subscribe(
      data => {
        // remove the already assigned specs
        const initiallyAssignedIDs = this.data.productSpec.serviceSpecification.map(el => el.id);
        const filteredData = data.filter(spec => !initiallyAssignedIDs.includes(spec.id));
        
        // push data into subject
        this.availableSpecsSubject.next(filteredData);
        
        this.selectedSpecs = this.data.productSpec.serviceSpecification.slice();
        this.dataSource.data = this.selectedSpecs;
        this.dataSource.sort = this.sort;
      },
    error => this.toast.error('Failed to fetch Service Specifications', error)
    );
  }

  private performFilter(filterValue: string, specs: ProductSpecification[]): ProductSpecification[] {
    if (!filterValue) {
      return specs;
    }
    return specs.filter(spec => {
      const name = spec.name ? spec.name.toLowerCase() : '';
      return name.indexOf(filterValue) !== -1;
    });
  }

  selected(event: MatAutocompleteSelectedEvent): void {
    const selectedLiteSpec = event.option.value;
    //retrieve full service specs details, @type and spec characteristics once a service spec is selected
    this.specInput.nativeElement.value = '';
    this.specInputCtrl.setValue(null);

    this.serviceSpecService.retrieveServiceSpecification({ 
      id: selectedLiteSpec.id 
    }).subscribe(
      (fullSpec: ServiceSpecification) => {

        this.selectedSpecs.push(fullSpec);
        this.dataSource.data = this.selectedSpecs;
        this.nonSelectedSpecs = this.nonSelectedSpecs.filter(
          el => el.name != selectedLiteSpec.name
        );
      },
      (error) => {
        this.toast.error('Failed to fetch full service specification details', error);
      }
    );
  }

  private _filter(value: string): ProductSpecification[] {
    const filterValue = value.toLowerCase();
    return this.nonSelectedSpecs.filter(cat => cat.name.toLowerCase().indexOf(filterValue) !== -1);
  }

  openList() {
    if (!this.matAutocomplete.isOpen) this.matAutocompleteTrigger.openPanel()
  }

  
  removeProductSpec(spec:ProductSpecification) {
    const index = this.selectedSpecs.indexOf(spec);
    if (index >= 0) {
      this.selectedSpecs.splice(index, 1);
      this.dataSource.data = this.selectedSpecs
      this.nonSelectedSpecs.push(spec);
    }
  }

  
  closeDialog() {
    this.dialogRef.close()
  }

  confirmAssignment() {
    
    const hasConfigurableCFSS = this.selectedSpecs.some(spec => {
      const s = spec as any;
      const type = s['@type'] || 'ServiceSpecification';
      const chars = s.serviceSpecCharacteristic || [];
      const hasConfigurable = chars.some(c => c.configurable === true);
      
      return type === 'CustomerFacingServiceSpecification' && hasConfigurable;
    });

    if (hasConfigurableCFSS) {
      // Open the dialog to ask the user
      const dialogRef = this.dialog.open(ConfirmCharacteristicAssignmentComponent, {
        width: '600px',
        disableClose: true
      });

      dialogRef.afterClosed().subscribe(result => {
        this.executeAssignment(result);
      });
    }
    else {
      this.executeAssignment(null);
    }
  }

  executeAssignment(importCharacteristics: boolean) {
    const serviceRefs: any[] = [];
    
    let tempCharacteristics = [...(this.data.productSpec.productSpecCharacteristic || [])];

    this.selectedSpecs.forEach(spec => {
      const sourceSpec = spec as any;
      const type = sourceSpec['@type'] || 'ServiceSpecification';

      serviceRefs.push({
        id: sourceSpec.id,
        name: sourceSpec.name,
        '@type': type
      });

      // If user said YES, we manually prepare the chars to add
      if (importCharacteristics) {
        const serviceChars = sourceSpec.serviceSpecCharacteristic || [];
        const configurableChars = serviceChars.filter(char => char.configurable === true);

        configurableChars.forEach(sourceChar => {
          const cleanValues = (sourceChar.serviceSpecCharacteristicValue || []).map(val => ({
            valueType: val.valueType,
            isDefault: val.isDefault,
            value: val.value,
            unitOfMeasure: val.unitOfMeasure,
            valueFrom: val.valueFrom,
            valueTo: val.valueTo,
            rangeInterval: val.rangeInterval,
            alias: val.alias
          }));

          const newCharObj = {
            name: sourceChar.name,
            description: sourceChar.description,
            valueType: sourceChar.valueType,
            configurable: sourceChar.configurable,
            extensible: sourceChar.extensible,
            regex: sourceChar.regex,
            uiType: sourceChar.uiType,
            unitOfMeasure: sourceChar.unitOfMeasure,
            defaultValue: sourceChar.defaultValue,
            minCardinality: sourceChar.minCardinality,
            maxCardinality: sourceChar.maxCardinality,
            productSpecCharacteristicValue: cleanValues,
            productSpecCharRelationship: [],
            validFor: sourceChar.validFor || { startDateTime: new Date().toISOString(), endDateTime: null },
            lifecycleStatus: "In design",
            '@type': 'ProductSpecCharacteristic',
            '@baseType': 'BaseEntity',
            '@schemaLocation': null
          };

          // if characteristic already exists by name, overwrite it anyway
          const existingCharIndex = tempCharacteristics.findIndex(c => c.name === sourceChar.name);
          if (existingCharIndex > -1) {
            tempCharacteristics[existingCharIndex] = { ...newCharObj, uuid: tempCharacteristics[existingCharIndex].uuid };
          } else {
            tempCharacteristics.push(newCharObj);
          }
        });
      }
    });

    const payloadStep1: ProductSpecificationUpdate = {
      serviceSpecification: serviceRefs,
      productSpecCharacteristic: tempCharacteristics
    };

    this.productSpecService.patchProductSpecification({
      id: this.data.productSpec.id,
      productSpecification: payloadStep1
    }).subscribe(
      (updatedSpec) => { 
        
        // If user said NO, and we have CFSS, we need to check if backend added them 
        // and remove them (patching the list without them)
        if (!importCharacteristics) {
          this.removeUnwantedCharacteristics(updatedSpec);
        } else {
          this.toast.success('Product Specification updated successfully');
          this.dialogRef.close('updated');
        }
      },
      error => {
        this.toast.error('Failed to update Product Specification');
      }
    );
  }

  removeUnwantedCharacteristics(spec: ProductSpecification) {
    const unwantedNames: string[] = [];
    
    this.selectedSpecs.forEach(s => {
      const sourceSpec = s as any;
      if (sourceSpec['@type'] === 'CustomerFacingServiceSpecification') {
         const chars = sourceSpec.serviceSpecCharacteristic || [];
         chars.filter(c => c.configurable).forEach(c => unwantedNames.push(c.name));
      }
    });

    const originalNames = (this.data.productSpec.productSpecCharacteristic || []).map(c => c.name);

    const currentChars = spec.productSpecCharacteristic || [];
    
    const newSpecCharacteristicArray = currentChars.filter(char => {
      const isUnwanted = unwantedNames.includes(char.name);
      const wasPreExisting = originalNames.includes(char.name);

      // If it is NOT in the unwanted list, keep it.
      if (!isUnwanted) return true;

      // If it IS in the unwanted list, BUT it existed before we started, keep it.
      if (wasPreExisting) return true;

      return false;
    });

    // If there is nothing to change (lengths match), just close
    if (newSpecCharacteristicArray.length === currentChars.length) {
      this.toast.success('Product Specification updated successfully');
      this.dialogRef.close('updated');
      return;
    }

    const updateCharacteristicObj: ProductSpecificationUpdate = {
      productSpecCharacteristic: newSpecCharacteristicArray
    }

    this.productSpecService.patchProductSpecification({
      id: spec.id, 
      productSpecification: updateCharacteristicObj
    }).subscribe(
      data => {}, 
      error => {
        this.toast.warning('Service assigned, but failed to clean up auto-assigned characteristics.');
        this.dialogRef.close('updated'); 
      },
      () => {
        this.toast.success("Product Specification Characteristics list was successfully updated");
        this.dialogRef.close('updated');
      }
    )
  }

}
