import { AsyncPipe } from '@angular/common';
import {
  ChangeDetectorRef,
  Component,
  Input,
  OnDestroy,
  OnInit,
  Optional,
} from '@angular/core';
import {
  FormsModule,
  ReactiveFormsModule,
  UntypedFormBuilder,
  UntypedFormControl,
  UntypedFormGroup,
  ValidatorFn,
  Validators,
} from '@angular/forms';
import { Router } from '@angular/router';
import {
  TranslateModule,
  TranslateService,
} from '@ngx-translate/core';
import {
  BehaviorSubject,
  combineLatest,
  Observable,
  of,
  Subscription,
  switchMap,
} from 'rxjs';
import {
  map,
  startWith,
  take,
} from 'rxjs/operators';

import { ConfigurationDataService } from '../core/data/configuration-data.service';
import { EpersonRegistrationService } from '../core/data/eperson-registration.service';
import { RemoteData } from '../core/data/remote-data';
import {
  CAPTCHA_NAME,
  GoogleRecaptchaService,
} from '../core/google-recaptcha/google-recaptcha.service';
import { CookieService } from '../core/services/cookie.service';
import { ConfigurationProperty } from '../core/shared/configuration-property.model';
import {
  getAllSucceededRemoteDataPayload,
  getFirstSucceededRemoteDataPayload,
} from '../core/shared/operators';
import { Registration } from '../core/shared/registration.model';
import { AlertComponent } from '../shared/alert/alert.component';
import { AlertType } from '../shared/alert/alert-type';
import { BtnDisabledDirective } from '../shared/btn-disabled.directive';
import { OrejimeService } from '../shared/cookies/orejime.service';
import { isNotEmpty } from '../shared/empty.util';
import { GoogleRecaptchaComponent } from '../shared/google-recaptcha/google-recaptcha.component';
import { NotificationsService } from '../shared/notifications/notifications.service';

export const TYPE_REQUEST_FORGOT = 'forgot';
export const TYPE_REQUEST_REGISTER = 'register';

@Component({
  selector: 'ds-base-register-email-form',
  templateUrl: './register-email-form.component.html',
  standalone: true,
  imports: [
    AlertComponent,
    AsyncPipe,
    BtnDisabledDirective,
    FormsModule,
    GoogleRecaptchaComponent,
    ReactiveFormsModule,
    TranslateModule,
  ],
})
/**
 * Component responsible to render an email registration form.
 */
export class RegisterEmailFormComponent implements OnDestroy, OnInit {

  /**
   * The form containing the mail address
   */
  form: UntypedFormGroup;

  /**
   * The message prefix
   */
  @Input()
  MESSAGE_PREFIX: string;

  /**
   * Type of register request to be done, register new email or forgot password (same endpoint)
   */
  @Input()
  typeRequest: string = null;

  public AlertTypeEnum = AlertType;

  /**
   * registration verification configuration
   */
  registrationVerification = false;

  /**
   * Return true if the user completed the reCaptcha verification (checkbox mode)
   */
  checkboxCheckedSubject$ = new BehaviorSubject<boolean>(false);

  disableUntilChecked = true;

  validMailDomains: string[];
  TYPE_REQUEST_REGISTER = TYPE_REQUEST_REGISTER;

  subscriptions: Subscription[] = [];

  captchaVersion$: Observable<string>;

  captchaMode$: Observable<string>;

  constructor(
    private epersonRegistrationService: EpersonRegistrationService,
    private notificationService: NotificationsService,
    private translateService: TranslateService,
    private router: Router,
    private formBuilder: UntypedFormBuilder,
    private configService: ConfigurationDataService,
    public googleRecaptchaService: GoogleRecaptchaService,
    public cookieService: CookieService,
    @Optional() public orejimeService: OrejimeService,
    private changeDetectorRef: ChangeDetectorRef,
    private notificationsService: NotificationsService,
  ) {
  }

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

  ngOnInit(): void {
    this.captchaVersion$ = this.googleRecaptchaService.captchaVersion();
    this.captchaMode$ = this.googleRecaptchaService.captchaMode();
    const validators: ValidatorFn[] = [
      Validators.required,
      Validators.email,
      // Regex pattern borrowed from HTML5 specs for a valid email address:
      // https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
      Validators.pattern('^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$'),
    ];
    this.form = this.formBuilder.group({
      email: new UntypedFormControl('', {
        validators: validators,
      }),
    });
    this.validMailDomains = [];
    if (this.typeRequest === TYPE_REQUEST_REGISTER) {
      this.subscriptions.push(this.configService.findByPropertyName('authentication-password.domain.valid')
        .pipe(getAllSucceededRemoteDataPayload())
        .subscribe((remoteData: ConfigurationProperty) => {
          this.validMailDomains = remoteData.values;
          for (const remoteValue of remoteData.values) {
            if (this.validMailDomains.length !== 0) {
              this.form.get('email').setValidators([
                ...validators,
                Validators.pattern(this.validMailDomains.map((domain: string) => '(^.*' + domain.replace(new RegExp('\\.', 'g'), '\\.') + '$)').join('|')),
              ]);
              this.form.updateValueAndValidity();
            }
          }
          this.changeDetectorRef.detectChanges();
        }));
    }
    this.subscriptions.push(this.configService.findByPropertyName('registration.verification.enabled').pipe(
      getFirstSucceededRemoteDataPayload(),
      map((res: ConfigurationProperty) => res?.values[0].toLowerCase() === 'true'),
    ).subscribe((res: boolean) => {
      this.registrationVerification = res;
    }));

    this.subscriptions.push(this.disableUntilCheckedFcn().subscribe((res) => {
      this.disableUntilChecked = res;
      this.changeDetectorRef.detectChanges();
    }));
  }

  /**
   * execute the captcha function for v2 invisible
   */
  executeRecaptcha() {
    this.googleRecaptchaService.executeRecaptcha();
  }

  /**
   * Register an email address
   */
  register(tokenV2?) {
    if (!this.form.invalid) {
      if (this.registrationVerification) {
        this.subscriptions.push(combineLatest([this.captchaVersion$, this.captchaMode$]).pipe(
          switchMap(([captchaVersion, captchaMode])  => {
            if (captchaVersion === 'v3') {
              return this.googleRecaptchaService.getRecaptchaToken('register_email');
            } else if (captchaVersion === 'v2' && captchaMode === 'checkbox') {
              return of(this.googleRecaptchaService.getRecaptchaTokenResponse());
            } else if (captchaVersion === 'v2' && captchaMode === 'invisible') {
              return of(tokenV2);
            } else {
              console.error(`Invalid reCaptcha configuration: version = ${captchaVersion}, mode = ${captchaMode}`);
              this.showNotification('error');
            }
          }),
          take(1),
        ).subscribe((token) => {
          if (isNotEmpty(token)) {
            this.registration(token);
          } else {
            console.error('reCaptcha error');
            this.showNotification('error');
          }
        },
        ));
      } else {
        this.registration();
      }
    }
  }

  /**
   * Registration of an email address
   */
  registration(captchaToken = null) {
    const registerEmail$ = captchaToken ?
      this.epersonRegistrationService.registerEmail(this.email.value, captchaToken, this.typeRequest) :
      this.epersonRegistrationService.registerEmail(this.email.value, null, this.typeRequest);
    this.subscriptions.push(registerEmail$.subscribe((response: RemoteData<Registration>) => {
      if (response.hasSucceeded) {
        this.notificationService.success(this.translateService.get(`${this.MESSAGE_PREFIX}.success.head`),
          this.translateService.get(`${this.MESSAGE_PREFIX}.success.content`, { email: this.email.value }));
        this.router.navigate(['/home']);
      } else if (response.statusCode === 422) {
        this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), this.translateService.get(`${this.MESSAGE_PREFIX}.error.maildomain`, { domains: this.validMailDomains.join(', ') }));
      } else {
        this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`),
          this.translateService.get(`${this.MESSAGE_PREFIX}.error.content`, { email: this.email.value }));
      }
    }));
  }

  /**
   * Return true if the user has accepted the required cookies for reCaptcha
   */
  isRecaptchaCookieAccepted(): boolean {
    const orejimeAnonymousCookie = this.cookieService.get('orejime-anonymous');
    return isNotEmpty(orejimeAnonymousCookie) ? orejimeAnonymousCookie[CAPTCHA_NAME] : false;
  }

  /**
   * Return true if the user has not completed the reCaptcha verification (checkbox mode)
   */
  disableUntilCheckedFcn(): Observable<boolean> {
    const checked$ = this.checkboxCheckedSubject$.asObservable();
    return combineLatest([this.captchaVersion$, this.captchaMode$, checked$]).pipe(
      // disable if checkbox is not checked or if reCaptcha is not in v2 checkbox mode
      switchMap(([captchaVersion, captchaMode, checked])  => captchaVersion === 'v2' && captchaMode === 'checkbox' ? of(!checked) : of(false)),
      startWith(true),
    );
  }

  get email() {
    return this.form.get('email');
  }

  onCheckboxChecked(checked: boolean) {
    this.checkboxCheckedSubject$.next(checked);
  }

  /**
   * Show a notification to the user
   * @param key
   */
  showNotification(key) {
    const notificationTitle = this.translateService.get(this.MESSAGE_PREFIX + '.google-recaptcha.notification.title');
    const notificationErrorMsg = this.translateService.get(this.MESSAGE_PREFIX + '.google-recaptcha.notification.message.error');
    const notificationExpiredMsg = this.translateService.get(this.MESSAGE_PREFIX + '.google-recaptcha.notification.message.expired');
    switch (key) {
      case 'expired':
        this.notificationsService.warning(notificationTitle, notificationExpiredMsg);
        break;
      case 'error':
        this.notificationsService.error(notificationTitle, notificationErrorMsg);
        break;
      default:
        console.warn(`Unimplemented notification '${key}' from reCaptcha service`);
    }
  }
}
