import { ChangeDetectionStrategy, Component, DestroyRef, ElementRef, computed, inject, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
  AbstractControl,
  FormBuilder,
  ReactiveFormsModule,
  ValidationErrors,
  ValidatorFn,
  Validators,
} from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { catchError, of, switchMap } from 'rxjs';

import { Autocomplete } from '../../../../../shared/components/form/autocomplete/autocomplete';
import {
  AutocompleteDisplayFn,
  AutocompleteResolveValueFn,
  AutocompleteSearchFn,
  AutocompleteValueFn,
} from '../../../../../shared/components/form/autocomplete/autocomplete.types';
import { FormDatePicker } from '../../../../../shared/components/form/form-date-picker/form-date-picker';
import { FormInput } from '../../../../../shared/components/form/form-input/form-input';
import { FormSelect } from '../../../../../shared/components/form/form-select/form-select';
import {
  OnboardingStepForm,
  OrganizationLicenseType,
  OrganizationPlanDefaults,
  OrganizationPlanLimitsValue,
  OrganizationPlanLookupValue,
} from '../../models/organization-onboarding.model';
import { OrganizationOnboardingService } from '../../services/organization-onboarding.service';

interface OrganizationPlanLimitsFormModel {
  readonly subscriptionPlanId: string | null;
  readonly licenseType: OrganizationLicenseType | null;
  readonly maximumCompanies: number | null;
  readonly maximumUsers: number | null;
  readonly maximumStorageGb: number | null;
  readonly goLiveDate: string | null;
  readonly systemAccessStartDate: string | null;
  readonly systemAccessEndDate: string | null;
}

const accessDateValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
  const value = control.value as OrganizationPlanLimitsFormModel;

  if (!value) {
    return null;
  }

  const errors: ValidationErrors = {};
  const { licenseType, systemAccessStartDate, systemAccessEndDate } = value;

  if (licenseType === 'Trial' && !systemAccessEndDate) {
    errors['trialEndDateRequired'] = true;
  }

  if (systemAccessStartDate && systemAccessEndDate && systemAccessStartDate > systemAccessEndDate) {
    errors['accessDateRange'] = true;
  }

  if (licenseType === 'Trial' && systemAccessStartDate && systemAccessEndDate && systemAccessStartDate >= systemAccessEndDate) {
    errors['trialEndDateOrder'] = true;
  }

  return Object.keys(errors).length ? errors : null;
};

@Component({
  selector: 'app-organization-plan-limits-step',
  standalone: true,
  imports: [ReactiveFormsModule, Autocomplete, FormSelect, FormInput, FormDatePicker],
  templateUrl: './organization-plan-limits.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OrganizationPlanLimitsStepComponent implements OnboardingStepForm<OrganizationPlanLimitsValue> {
  private readonly destroyRef = inject(DestroyRef);
  private readonly formBuilder = inject(FormBuilder);
  private readonly onboardingService = inject(OrganizationOnboardingService);
  private readonly toastr = inject(ToastrService);
  private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);

  readonly submitAttempted = signal(false);
  readonly planSelection = signal<OrganizationPlanLookupValue | null>(null);
  readonly limitsEditable = signal(true);
  readonly licenseTypeOptions = this.onboardingService.getLicenseTypeOptions();

  readonly form = this.formBuilder.group({
    subscriptionPlanId: this.formBuilder.control<string | null>(null, [Validators.required]),
    licenseType: this.formBuilder.control<OrganizationLicenseType | null>(null, [Validators.required]),
    maximumCompanies: this.formBuilder.control<number | null>(null, [Validators.required, Validators.min(1)]),
    maximumUsers: this.formBuilder.control<number | null>(null, [Validators.required, Validators.min(1)]),
    maximumStorageGb: this.formBuilder.control<number | null>(null, [Validators.required, Validators.min(1)]),
    goLiveDate: this.formBuilder.control<string | null>(null, [Validators.required]),
    systemAccessStartDate: this.formBuilder.control<string | null>(null, [Validators.required]),
    systemAccessEndDate: this.formBuilder.control<string | null>(null),
  }, { validators: accessDateValidator });

  readonly requiresAccessEndDate = computed(() => this.form.controls.licenseType.value === 'Trial');

  readonly searchPlans: AutocompleteSearchFn<OrganizationPlanLookupValue> = (term, limit) =>
    this.onboardingService.searchSubscriptionPlans(term, limit).pipe(
      catchError(() => {
        this.toastr.error('Unable to load subscription plans.');
        return of<readonly OrganizationPlanLookupValue[]>([]);
      })
    );

  readonly displayPlan: AutocompleteDisplayFn<OrganizationPlanLookupValue> = item =>
    [item.label, item.code].filter(Boolean).join(' - ');

  readonly planValue: AutocompleteValueFn<OrganizationPlanLookupValue, string> = item => item.id;

  readonly resolvePlan: AutocompleteResolveValueFn<OrganizationPlanLookupValue, string> = value =>
    this.onboardingService.resolveSubscriptionPlan(value);

  constructor() {
    this.form.controls.licenseType.valueChanges
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(licenseType => {
        if (licenseType === 'Trial') {
          this.form.controls.systemAccessEndDate.addValidators([Validators.required]);
        } else {
          this.form.controls.systemAccessEndDate.removeValidators([Validators.required]);
        }

        this.form.controls.systemAccessEndDate.updateValueAndValidity({ emitEvent: false });
      });

    this.form.controls.subscriptionPlanId.valueChanges
      .pipe(
        switchMap(planId => {
          if (!planId) {
            return of<OrganizationPlanDefaults | null>(null);
          }

          return this.onboardingService.getPlanDefaults(planId).pipe(
            catchError(() => {
              this.toastr.error('Unable to load plan defaults.');
              return of<OrganizationPlanDefaults | null>(null);
            })
          );
        }),
        takeUntilDestroyed(this.destroyRef)
      )
      .subscribe(defaults => {
        if (!defaults) {
          return;
        }

        this.applyPlanDefaults(defaults);
      });
  }

  validate(): boolean {
    this.submitAttempted.set(true);

    if (this.form.valid) {
      return true;
    }

    this.form.markAllAsTouched();
    this.focusFirstInvalidControl();
    return false;
  }

  getValue(): OrganizationPlanLimitsValue {
    const value = this.form.getRawValue() as OrganizationPlanLimitsFormModel;
    const subscriptionPlan = this.planSelection();

    if (!subscriptionPlan || !value.licenseType || !value.maximumCompanies || !value.maximumUsers || !value.maximumStorageGb || !value.goLiveDate || !value.systemAccessStartDate) {
      throw new Error('Organization plan and limits values are incomplete.');
    }

    return {
      subscriptionPlan,
      licenseType: value.licenseType,
      maximumCompanies: value.maximumCompanies,
      maximumUsers: value.maximumUsers,
      maximumStorageGb: value.maximumStorageGb,
      goLiveDate: value.goLiveDate,
      systemAccessStartDate: value.systemAccessStartDate,
      systemAccessEndDate: value.systemAccessEndDate,
      limitsEditable: this.limitsEditable(),
    };
  }

  getDraftValue(): Partial<OrganizationPlanLimitsValue> {
    const value = this.form.getRawValue() as OrganizationPlanLimitsFormModel;

    return {
      subscriptionPlan: this.planSelection() ?? undefined,
      licenseType: value.licenseType ?? undefined,
      maximumCompanies: value.maximumCompanies ?? undefined,
      maximumUsers: value.maximumUsers ?? undefined,
      maximumStorageGb: value.maximumStorageGb ?? undefined,
      goLiveDate: value.goLiveDate ?? undefined,
      systemAccessStartDate: value.systemAccessStartDate ?? undefined,
      systemAccessEndDate: value.systemAccessEndDate,
      limitsEditable: this.limitsEditable(),
    };
  }

  patchValue(value: Partial<OrganizationPlanLimitsValue>): void {
    this.form.patchValue({
      subscriptionPlanId: value.subscriptionPlan?.id ?? null,
      licenseType: value.licenseType ?? null,
      maximumCompanies: value.maximumCompanies ?? null,
      maximumUsers: value.maximumUsers ?? null,
      maximumStorageGb: value.maximumStorageGb ?? null,
      goLiveDate: value.goLiveDate ?? null,
      systemAccessStartDate: value.systemAccessStartDate ?? null,
      systemAccessEndDate: value.systemAccessEndDate ?? null,
    }, { emitEvent: false });

    this.planSelection.set(value.subscriptionPlan ?? null);
    this.limitsEditable.set(value.limitsEditable ?? true);
    this.applyLimitsEditableState();
    this.submitAttempted.set(false);
  }

  onPlanSelected(item: OrganizationPlanLookupValue): void {
    this.planSelection.set(item);
  }

  onPlanCleared(): void {
    this.planSelection.set(null);
  }

  private applyPlanDefaults(defaults: OrganizationPlanDefaults): void {
    this.planSelection.set(defaults.plan);
    this.limitsEditable.set(defaults.limitsEditable);
    this.form.patchValue({
      licenseType: defaults.licenseType,
      maximumCompanies: defaults.maximumCompanies,
      maximumUsers: defaults.maximumUsers,
      maximumStorageGb: defaults.maximumStorageGb,
    }, { emitEvent: false });
    this.applyLimitsEditableState();
  }

  private applyLimitsEditableState(): void {
    const controls = [
      this.form.controls.maximumCompanies,
      this.form.controls.maximumUsers,
      this.form.controls.maximumStorageGb,
    ];

    if (this.limitsEditable()) {
      controls.forEach(control => control.enable({ emitEvent: false }));
      return;
    }

    controls.forEach(control => control.disable({ emitEvent: false }));
  }

  private focusFirstInvalidControl(): void {
    queueMicrotask(() => {
      const invalidElement = this.elementRef.nativeElement.querySelector<HTMLElement>(
        '.ng-invalid [data-form-control], [data-form-control].ng-invalid, .ng-invalid .ng-select-container, .ng-invalid input'
      );

      invalidElement?.focus();
    });
  }
}