import { FormSelectItem } from '@af/digi-ng/_form/form-select';
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { AvvikelseReportRequest, AvvikelseRequest } from '@msfa-models/api/avvikelse-request.model';
import { DeltagareAvrop } from '@msfa-models/avrop.model';
import { AvvikelseQuestion } from '@msfa-models/avvikelse-question.model';
import { AvvikelseReason } from '@msfa-models/avvikelse-reason.model';
import { CustomError } from '@msfa-models/error/custom-error';
import { markControlsAsDirty } from '@msfa-utils/mark-controls-as-dirty.util';
import { RegexValidator } from '@msfa-utils/validators/regex.validator';
import { RequiredValidator } from '@msfa-validators/required.validator';
import { addDays } from 'date-fns';
import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs';
import { map, shareReplay, switchMap, take } from 'rxjs/operators';
import { AvvikelseReportFormService } from './avvikelse-report-form.service';

interface Params {
  genomforandeReferens: string;
}

interface AvvikelseFormData {
  reason: string;
  questions: string[];
  reportingDate: string;
}

type AvvikelseFormKeys = keyof AvvikelseFormData;

@Component({
  selector: 'msfa-avvikelse-report-form',
  templateUrl: './avvikelse-report-form.component.html',
  styleUrls: ['./avvikelse-report-form.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AvvikelseReportFormComponent implements OnInit, OnDestroy {
  shouldValidate$ = new BehaviorSubject<boolean>(false);

  reasonFormName: AvvikelseFormKeys = 'reason';
  questionsFormName: AvvikelseFormKeys = 'questions';
  reportingDateFormName: AvvikelseFormKeys = 'reportingDate';

  submitIsLoading$ = new BehaviorSubject<boolean>(false);
  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.avvikelseReportFormService.fetchAvropInformation$(genomforandeReferens)),
    shareReplay(1)
  );

  reasons$: Observable<AvvikelseReason[]> = this.avvikelseReportFormService.fetchAvvikelseReasons$;
  reasonsAsNgDigiFormSelectItems$: Observable<FormSelectItem[]> = this.reasons$.pipe(
    map(reasons => reasons.map(reason => ({ name: reason.name, value: reason.id.toString() })))
  );

  allAvvikelseQuestions$ = this.avvikelseReportFormService.avvikelseQuestions$;
  chosenReasonId$: Observable<string>;
  chosenReason$: Observable<AvvikelseReason>;
  questionsForChosenReason$: Observable<AvvikelseQuestion[]>;
  avvikelseSubmitData$: Observable<AvvikelseReportRequest>;
  confirmDialogIsOpen$ = new BehaviorSubject<boolean>(false);
  submittedDate$ = new BehaviorSubject<Date | null>(null);
  private subscriptions: Subscription[] = [];
  private todayDateISO = new Date().toISOString().slice(0, 10);
  avvikelseFormGroup = new FormGroup({
    [this.reasonFormName]: new FormControl(null, [RequiredValidator('Orsak är obligatorisk')]),
    [this.reportingDateFormName]: new FormControl(this.todayDateISO, [RequiredValidator('Datum är obligatoriskt')]),
    [this.questionsFormName]: new FormArray([]),
  });
  private formData$: Observable<AvvikelseFormData> = this.avvikelseFormGroup
    .valueChanges as Observable<AvvikelseFormData>;
  private currentQuestions: AvvikelseQuestion[];

  constructor(private avvikelseReportFormService: AvvikelseReportFormService, private activatedRoute: ActivatedRoute) {}

  get reasonFormControl(): AbstractControl | undefined {
    return this.avvikelseFormGroup.get(this.reasonFormName);
  }

  get avvikelseDateFormControl(): AbstractControl | undefined {
    return this.avvikelseFormGroup.get(this.reportingDateFormName);
  }

  get questionsFormArray(): FormArray {
    return this.avvikelseFormGroup.get('questions') as FormArray;
  }

  getCurrentQuestionFromId(id: string): AvvikelseQuestion {
    return this.currentQuestions.find(currentQuestions => currentQuestions.id === id);
  }

  ngOnInit(): void {
    this.chosenReasonId$ = this.reasonFormControl.valueChanges as Observable<string>;
    this.chosenReason$ = combineLatest([this.chosenReasonId$, this.reasons$]).pipe(
      map(([chosenReasonId, reasons]) => reasons.find(reason => reason.id.toString() === chosenReasonId))
    );

    this.questionsForChosenReason$ = combineLatest([this.chosenReasonId$, this.allAvvikelseQuestions$]).pipe(
      map(([chosenOrsak, allAvvikelseQuestions]) => {
        return allAvvikelseQuestions.filter(question => question.id.startsWith(chosenOrsak.toString() + '_'));
      })
    );

    this.subscriptions.push(
      this.chosenReason$.subscribe(() => {
        this.shouldValidate$.next(false);
      }),
      this.questionsForChosenReason$.subscribe(questions => {
        this.clearQuestions();
        questions.forEach(question => this.addQuestionToForm(question));
      })
    );

    this.avvikelseSubmitData$ = combineLatest([this.genomforandeReferens$, this.chosenReasonId$, this.formData$]).pipe(
      map(([genomforandeReferens, chosenReason, formData]) =>
        this.makeAvvikelseSubmitData(genomforandeReferens, chosenReason, formData)
      ),
      shareReplay(1)
    );
  }

  questionIsRequired(question: AvvikelseQuestion): boolean {
    return !(question.id === '19_2' || question.id === '20_2');
  }

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

  maxDate(endDate: Date): Date {
    const now = new Date();
    return now < endDate ? now : endDate;
  }

  private _isAfterStartDate(startDate: Date): boolean {
    return new Date() > startDate;
  }
  private _isBeforeLastPossibleReportDay(endDate: Date): boolean {
    const lastPossibleReportDay = addDays(endDate, 5); // Reporting is allowed at latest 5 days past avrop end date.
    return lastPossibleReportDay > new Date();
  }

  isAllowedToReport(avrop: DeltagareAvrop): boolean {
    return this._isAfterStartDate(avrop.startDate) && this._isBeforeLastPossibleReportDay(avrop.endDate);
  }

  notAllowedToReportWarning(avrop: DeltagareAvrop): string {
    if (!this._isBeforeLastPossibleReportDay(avrop.endDate)) {
      return 'Det går inte att göra Avvikelserapport (avvikelse) eftersom tjänsten har avslutats.';
    }
    if (!this._isAfterStartDate(avrop.startDate)) {
      return 'Det går inte att göra Avvikelserapport (avvikelse) eftersom tjänsten inte har startat ännu.';
    }
  }

  openConfirmDialog(): void {
    this.shouldValidate$.next(true);
    markControlsAsDirty(Object.values(this.avvikelseFormGroup.controls));
    this.avvikelseFormGroup.markAllAsTouched();

    if (this.avvikelseFormGroup.valid) {
      this.confirmDialogIsOpen$.next(true);
    }
  }

  submitAndCloseConfirmDialog(): void {
    this.submitIsLoading$.next(true);
    this.avvikelseSubmitData$.pipe(take(1)).subscribe(avvikelseSubmitData =>
      this.avvikelseReportFormService.createAvvikelse$(avvikelseSubmitData).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 };
        },
      })
    );
  }

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

  ngOnDestroy(): void {
    this.subscriptions.forEach(subscription => subscription.unsubscribe());
  }

  private makeAvvikelseSubmitData(
    genomforandeReferens: number,
    chosenReason: string,
    formData: AvvikelseFormData
  ): AvvikelseReportRequest {
    const avvikelseAlternativ: AvvikelseRequest = {
      avvikelseOrsakskod: chosenReason,
      frageformular: formData.questions.map((question, index) => ({
        fraga: this.currentQuestions[index].id,
        svar: question,
      })),
      rapporteringsdatum: formData.reportingDate,
    };

    return { genomforandeReferens, avvikelseAlternativ };
  }

  private clearQuestions(): void {
    this.questionsFormArray.clear();
    this.currentQuestions = [];
  }

  private addQuestionToForm(question: AvvikelseQuestion): void {
    // FormArray doesnt hold any IDs so we need to store these seperately and rebuild structure at submit
    this.currentQuestions.push(question);

    this.questionsFormArray.push(
      new FormControl(
        '',
        this.questionIsRequired(question)
          ? [RequiredValidator('Frågan är obligatorisk'), RegexValidator()]
          : [RegexValidator()]
      )
    );
  }
}
