import { RadiobuttonGroupDirection } from '@af/digi-ng/_form/form-radiobutton-group';
import { SelectOption } from '@ui/select/select-option.model';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms';
import { ActivatedRoute, Params } from '@angular/router';
import { Activity } from '@msfa-models/activity.model';
import {
  PeriodiskRedovisningActivityRequest,
  PeriodiskRedovisningRequest,
} from '@msfa-models/api/periodisk-redovisning.request.model';
import { AvropPeriod } from '@msfa-models/avrop-period.model';
import { DeltagareAvrop } from '@msfa-models/avrop.model';
import { CustomError } from '@msfa-models/error/custom-error';
import { capitalizeWords } from '@msfa-utils/capitalize-words.util';
import { formatDate, formatToDate } from '@msfa-utils/format-to-date.util';
import { RequiredValidator } from '@msfa-validators/required.validator';
import { Radiobutton } from '@ui/radiobutton-group/radiobutton.model';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { map, shareReplay, switchMap, take } from 'rxjs/operators';
import { extractAvropPeriods } from './extract-avrop-periods';
import {
  ActivityFormErrors,
  PeriodiskRedovisningFormData,
  PeriodiskRedovisningFormErrors,
  PeriodiskRedovisningFormKeys,
} from './periodisk-redovisning-form.model';
import { PeriodiskRedovisningFormService } from './periodisk-redovisning-form.service';
import { PeriodiskRedovisningValidator } from './periodisk-redovisning.validator';

@Component({
  selector: 'msfa-periodisk-redovisning-form',
  templateUrl: './periodisk-redovisning-form.component.html',
  styleUrls: ['./periodisk-redovisning-form.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PeriodiskRedovisningFormComponent implements OnInit {
  readonly PERIOD_FORM_NAME: PeriodiskRedovisningFormKeys = 'period';
  readonly HAS_OFFERED_LANGUAGE_SUPPORT_FORM_NAME: PeriodiskRedovisningFormKeys = 'hasOfferedLanguageSupport';
  readonly HAS_OFFERED_JOB_FORM_NAME: PeriodiskRedovisningFormKeys = 'hasOfferedJob';
  readonly ACTIVITES_FORM_NAME: PeriodiskRedovisningFormKeys = 'activities';
  readonly NO_ACTIVITIES_HAS_BEEN_CONDUCTED_FORM_NAME: PeriodiskRedovisningFormKeys = 'noActivitiesHasBeenConducted';

  today = new Date();
  shouldValidate$ = new BehaviorSubject<boolean>(false);
  changePeriodDialogIsOpen$ = new BehaviorSubject<boolean>(false);
  confirmDialogIsOpen$ = new BehaviorSubject<boolean>(false);
  submittedDate$ = new BehaviorSubject<Date>(null);
  submitIsLoading$ = new BehaviorSubject<boolean>(false);
  radiobuttonGroupDirection = RadiobuttonGroupDirection;
  activitiesFormArrayMetadata: Activity[];
  submitError$ = new BehaviorSubject<CustomError>(null);

  genomforandeReferens$: Observable<number> = this.activatedRoute.params.pipe(
    map((params: Params) => +params.genomforandeReferens)
  );
  avrop$: Observable<DeltagareAvrop> = this.genomforandeReferens$.pipe(
    switchMap(genomforandeReferens =>
      this.periodiskRedovisningFormService.fetchAvropInformation$(genomforandeReferens)
    ),
    shareReplay(1)
  );

  availableActivities$: Observable<Activity[]> = this.genomforandeReferens$.pipe(
    switchMap(genomforandeReferens =>
      this.periodiskRedovisningFormService.getAllChosenActivities(genomforandeReferens)
    ),
    shareReplay(1)
  );

  yesNoRadiobuttons: Radiobutton[] = [
    { label: 'Ja', value: true },
    { label: 'Nej', value: false },
  ];

  periods$: Observable<AvropPeriod[]> = this.avrop$.pipe(
    map(avrop => extractAvropPeriods(avrop.startDate, avrop.endDate))
  );
  formGroup = new FormGroup(
    {
      [this.PERIOD_FORM_NAME]: new FormControl(null, RequiredValidator()),
      [this.HAS_OFFERED_LANGUAGE_SUPPORT_FORM_NAME]: new FormControl(null, RequiredValidator()),
      [this.HAS_OFFERED_JOB_FORM_NAME]: new FormControl(null, RequiredValidator()),
      [this.ACTIVITES_FORM_NAME]: new FormArray([]),
      [this.NO_ACTIVITIES_HAS_BEEN_CONDUCTED_FORM_NAME]: new FormControl(),
    },
    [PeriodiskRedovisningValidator.periodiskRedovisningIsValid()]
  );

  formData$: Observable<PeriodiskRedovisningFormData> = this.formGroup
    .valueChanges as Observable<PeriodiskRedovisningFormData>;

  submitData$ = combineLatest([this.genomforandeReferens$, this.formData$]).pipe(
    map(([genomforandeReferens, formData]) => this.formDataToSubmitData(genomforandeReferens, formData)),
    shareReplay(1)
  );

  periodsToFormselectItems(periods: AvropPeriod[]): SelectOption[] {
    return periods.map(period => ({
      name: this.getPeriodName(period.startDate),
      value: period.periodId,
    }));
  }

  get formErrors(): PeriodiskRedovisningFormErrors {
    return this.formGroup.errors as PeriodiskRedovisningFormErrors;
  }

  constructor(
    private periodiskRedovisningFormService: PeriodiskRedovisningFormService,
    private activatedRoute: ActivatedRoute
  ) {
    this.periods$.pipe(take(1)).subscribe(periods => {
      this.periodFormControl.setValue(periods[periods.length - 1].periodId);
    });
  }

  get periodFormControl(): AbstractControl | undefined {
    return this.formGroup.get(this.PERIOD_FORM_NAME);
  }

  get hasOfferedLanguageSupportFormControl(): AbstractControl | undefined {
    return this.formGroup.get(this.HAS_OFFERED_LANGUAGE_SUPPORT_FORM_NAME);
  }

  get hasOfferedJobFormControl(): AbstractControl | undefined {
    return this.formGroup.get(this.HAS_OFFERED_JOB_FORM_NAME);
  }

  get noActivitiesHasBeenConductedFormControl(): AbstractControl | undefined {
    return this.formGroup.get(this.NO_ACTIVITIES_HAS_BEEN_CONDUCTED_FORM_NAME);
  }

  get activitiesFormArray(): FormArray {
    return this.formGroup.get(this.ACTIVITES_FORM_NAME) as FormArray;
  }

  getPeriodName(period: Date | string): string {
    const date = period instanceof Date ? period : formatToDate(period);
    return capitalizeWords(formatDate(date, 'sv-SE', { month: 'long', year: 'numeric' }));
  }

  openConfirmDialog(): void {
    this.shouldValidate$.next(true);

    if (this.formGroup.invalid) {
      return;
    }
    this.confirmDialogIsOpen$.next(true);
  }

  cancelConfirmDialog(): void {
    this.confirmDialogIsOpen$.next(false);
    this.submitError$.next(null);
  }

  submitAndCloseConfirmDialog(): void {
    this.submitIsLoading$.next(true);
    this.submitData$.pipe(take(1)).subscribe(submitData =>
      this.periodiskRedovisningFormService.submitPeriodiskRedovisning$(submitData).subscribe({
        next: () => {
          this.submitIsLoading$.next(false);
          this.submittedDate$.next(new Date());
          this.confirmDialogIsOpen$.next(false);
        },
        error: (customError: CustomError) => {
          this.submitError$.next({ ...customError, message: customError.error.message });
          this.submitIsLoading$.next(false);
          throw { ...customError, avoidToast: true };
        },
      })
    );
  }

  formControlIsInvalid(formControl: AbstractControl): boolean {
    return formControl.invalid && (formControl.touched || this.shouldValidate$.value);
  }

  private clearActivities(): void {
    this.activitiesFormArray.clear();
    this.activitiesFormArrayMetadata = [];
  }

  private addActivityToForm(activity: Activity): void {
    // FormArray doesnt hold any IDs so we need to store these seperately and rebuild structure at submit
    // It's important that the metadata is updated at the same time as the formArray to avoid sync problems
    this.activitiesFormArrayMetadata.push(activity);

    this.activitiesFormArray.push(
      new FormGroup(
        {
          isSelected: new FormControl(),
          performedRemotely: new FormControl(),
          performedPhysically: new FormControl(),
          hours: new FormControl(),
        },
        PeriodiskRedovisningValidator.activityIsValid()
      )
    );
  }

  ngOnInit(): void {
    this.availableActivities$.subscribe(activities => {
      this.clearActivities();
      activities.forEach(activity => this.addActivityToForm(activity));
    });
  }

  private formDataToSubmitData(
    genomforandeReferens: number,
    formData: PeriodiskRedovisningFormData
  ): PeriodiskRedovisningRequest {
    const { period, hasOfferedJob, hasOfferedLanguageSupport } = formData;
    const activities: PeriodiskRedovisningActivityRequest[] = formData.activities
      .map(({ performedPhysically, performedRemotely, isSelected, hours }, index) => ({
        id: this.activitiesFormArrayMetadata[index].id,
        performedPhysically: performedPhysically ?? false,
        performedRemotely: performedRemotely ?? false,
        hours: +hours,
        isSelected,
      }))
      .filter(activity => activity.isSelected)
      .map(({ id, performedPhysically, performedRemotely, hours }) => ({
        id,
        performedPhysically,
        performedRemotely,
        hours,
      }));
    return {
      genomforandeReferens,
      period,
      hasOfferedJob,
      hasOfferedLanguageSupport,
      activities,
    };
  }

  errorsToArray(errors: { [key: string]: string }): string[] {
    return Object.values(errors);
  }

  activityLocationIsInvalid(activityFormGroup: AbstractControl): boolean {
    const errors = activityFormGroup.errors as ActivityFormErrors;
    return this.formControlIsInvalid(activityFormGroup) && !!errors.locationCheckboxes;
  }

  activityHoursIsInvalid(activityFormGroup: AbstractControl): boolean {
    const errors = activityFormGroup.errors as ActivityFormErrors;
    return this.formControlIsInvalid(activityFormGroup) && !!errors.hours;
  }

  getActivityMetadata(activityId: number): Activity {
    return this.activitiesFormArrayMetadata.find(activity => activity.id === activityId);
  }

  getActivityInfoAsString(activity: PeriodiskRedovisningActivityRequest): string {
    const { name } = this.getActivityMetadata(activity.id);
    const hours = activity.hours === 1 ? '1 timme' : `${activity.hours} timmar`;
    const location =
      activity.performedRemotely && activity.performedPhysically
        ? 'på distans och på plats'
        : activity.performedRemotely
        ? 'på distans'
        : 'på plats';
    return `${name}: ${hours} ${location}`;
  }

  openChangePeriodDialogIfValuesExist(): void {
    const { activities, hasOfferedJob, hasOfferedLanguageSupport, noActivitiesHasBeenConducted } = this.formGroup
      .value as PeriodiskRedovisningFormData;

    if (
      activities.filter(({ isSelected }) => !!isSelected).length ||
      hasOfferedJob !== null ||
      hasOfferedLanguageSupport !== null ||
      noActivitiesHasBeenConducted
    ) {
      this.changePeriodDialogIsOpen$.next(true);
    }
  }

  closePeriodDialogAndResetForm(): void {
    this.hasOfferedJobFormControl.reset();
    this.hasOfferedLanguageSupportFormControl.reset();
    this.noActivitiesHasBeenConductedFormControl.reset();
    this.activitiesFormArray.reset();
    this.shouldValidate$.next(false);
    this.changePeriodDialogIsOpen$.next(false);
  }

  closePeriodDialogAndKeepForm(): void {
    this.changePeriodDialogIsOpen$.next(false);
  }
}
