import { Clipboard } from '@angular/cdk/clipboard'
import { Component, OnInit, inject, signal } from '@angular/core'
import {
  FormControl,
  FormGroup,
  FormsModule,
  ReactiveFormsModule,
} from '@angular/forms'
import {
  NgbAccordionModule,
  NgbActiveModal,
  NgbPopoverModule,
} from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { takeUntil } from 'rxjs'
import {
  SocialAccount,
  SocialAccountProvider,
  TotpSettings,
} from 'src/app/data/user-profile'
import { ProfileService } from 'src/app/services/profile.service'
import { ToastService } from 'src/app/services/toast.service'
import { setLocationHref } from 'src/app/utils/navigation'
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
import { ConfirmButtonComponent } from '../confirm-button/confirm-button.component'
import { PasswordComponent } from '../input/password/password.component'
import { TextComponent } from '../input/text/text.component'

@Component({
  selector: 'pngx-profile-edit-dialog',
  templateUrl: './profile-edit-dialog.component.html',
  styleUrls: ['./profile-edit-dialog.component.scss'],
  imports: [
    ConfirmButtonComponent,
    TextComponent,
    PasswordComponent,
    FormsModule,
    ReactiveFormsModule,
    NgbAccordionModule,
    NgbPopoverModule,
    NgxBootstrapIconsModule,
  ],
})
export class ProfileEditDialogComponent
  extends LoadingComponentWithPermissions
  implements OnInit
{
  private profileService = inject(ProfileService)
  activeModal = inject(NgbActiveModal)
  private toastService = inject(ToastService)
  private clipboard = inject(Clipboard)

  readonly networkActive = signal(false)
  readonly error = signal<any>(undefined)
  readonly showPasswordConfirm = signal(false)
  readonly showEmailConfirm = signal(false)
  readonly copied = signal(false)
  readonly codesCopied = signal(false)
  readonly hasUsablePassword = signal(false)
  readonly isTotpEnabled = signal(false)
  readonly totpSettings = signal<TotpSettings>(undefined)
  readonly totpSettingsLoading = signal(false)
  readonly totpLoading = signal(false)
  readonly recoveryCodes = signal<string[]>(undefined)
  readonly socialAccounts = signal<SocialAccount[]>([])
  readonly socialAccountProviders = signal<SocialAccountProvider[]>([])

  public form = new FormGroup({
    email: new FormControl(''),
    email_confirm: new FormControl({ value: null, disabled: true }),
    password: new FormControl(null),
    password_confirm: new FormControl({ value: null, disabled: true }),
    first_name: new FormControl(''),
    last_name: new FormControl(''),
    auth_token: new FormControl(''),
    totp_code: new FormControl(''),
  })

  private currentPassword: string
  private newPassword: string
  private passwordConfirm: string

  private currentEmail: string
  private newEmail: string
  private emailConfirm: string

  get qrSvgDataUrl(): string | null {
    if (!this.totpSettings()?.qr_svg) {
      return null
    }
    return `data:image/svg+xml;utf8,${encodeURIComponent(this.totpSettings().qr_svg)}`
  }

  ngOnInit(): void {
    this.networkActive.set(true)
    this.profileService
      .get()
      .pipe(takeUntil(this.unsubscribeNotifier))
      .subscribe((profile) => {
        this.networkActive.set(false)
        this.form.patchValue(profile)
        this.currentEmail = profile.email
        this.form.get('email').valueChanges.subscribe((newEmail) => {
          this.newEmail = newEmail
          this.onEmailChange()
        })
        this.currentPassword = profile.password
        this.hasUsablePassword.set(profile.has_usable_password)
        this.form.get('password').valueChanges.subscribe((newPassword) => {
          this.newPassword = newPassword
          this.onPasswordChange()
        })
        this.socialAccounts.set(profile.social_accounts ?? [])
        this.isTotpEnabled.set(profile.is_mfa_enabled)
      })

    this.profileService
      .getSocialAccountProviders()
      .pipe(takeUntil(this.unsubscribeNotifier))
      .subscribe((providers) => {
        this.socialAccountProviders.set(providers ?? [])
      })
  }

  get saveDisabled(): boolean {
    return this.error()?.password_confirm || this.error()?.email_confirm
  }

  onEmailKeyUp(event: KeyboardEvent): void {
    this.newEmail = (event.target as HTMLInputElement)?.value
    this.onEmailChange()
  }

  onEmailConfirmKeyUp(event: KeyboardEvent): void {
    this.emailConfirm = (event.target as HTMLInputElement)?.value
    this.onEmailChange()
  }

  onEmailChange(): void {
    this.showEmailConfirm.set(this.currentEmail !== this.newEmail)
    if (this.showEmailConfirm()) {
      this.form.get('email_confirm').enable()
      if (this.newEmail !== this.emailConfirm) {
        this.setFieldError('email_confirm', $localize`Emails must match`)
      } else {
        this.clearFieldError('email_confirm')
      }
    } else {
      this.form.get('email_confirm').disable()
      this.clearFieldError('email_confirm')
    }
  }

  onPasswordKeyUp(event: KeyboardEvent): void {
    if ((event.target as HTMLElement).tagName !== 'input') return // toggle button can trigger this handler
    this.newPassword = (event.target as HTMLInputElement)?.value
    this.onPasswordChange()
  }

  onPasswordConfirmKeyUp(event: KeyboardEvent): void {
    this.passwordConfirm = (event.target as HTMLInputElement)?.value
    this.onPasswordChange()
  }

  onPasswordChange(): void {
    this.showPasswordConfirm.set(this.currentPassword !== this.newPassword)

    if (this.showPasswordConfirm()) {
      this.form.get('password_confirm').enable()
      if (this.newPassword !== this.passwordConfirm) {
        this.setFieldError('password_confirm', $localize`Passwords must match`)
      } else {
        this.clearFieldError('password_confirm')
      }
    } else {
      this.form.get('password_confirm').disable()
      this.clearFieldError('password_confirm')
    }
  }

  private setFieldError(fieldName: string, message: string): void {
    this.error.set({
      ...this.error(),
      [fieldName]: message,
    })
  }

  private clearFieldError(fieldName: string): void {
    if (!this.error()) return
    const error = { ...this.error() }
    delete error[fieldName]
    this.error.set(Object.keys(error).length ? error : undefined)
  }

  save(): void {
    const passwordChanged =
      this.newPassword && this.currentPassword !== this.newPassword
    const profile = Object.assign({}, this.form.value)
    delete profile.totp_code
    this.error.set(null)
    this.networkActive.set(true)
    this.profileService
      .update(profile)
      .pipe(takeUntil(this.unsubscribeNotifier))
      .subscribe({
        next: () => {
          this.toastService.showInfo($localize`Profile updated successfully`)
          if (passwordChanged) {
            this.toastService.showInfo(
              $localize`Password has been changed, you will be logged out momentarily.`
            )
            setTimeout(() => {
              setLocationHref(
                `${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/`
              )
            }, 2500)
          }
          this.activeModal.close()
        },
        error: (error) => {
          this.toastService.showError($localize`Error saving profile`, error)
          this.error.set(error?.error)
          this.networkActive.set(false)
        },
      })
  }

  cancel(): void {
    this.activeModal.close()
  }

  generateAuthToken(): void {
    this.profileService.generateAuthToken().subscribe({
      next: (token: string) => {
        this.form.patchValue({ auth_token: token })
      },
      error: (error) => {
        this.toastService.showError(
          $localize`Error generating auth token`,
          error
        )
      },
    })
  }

  copyAuthToken(): void {
    this.clipboard.copy(this.form.get('auth_token').value)
    this.copied.set(true)
    setTimeout(() => {
      this.copied.set(false)
    }, 3000)
  }

  disconnectSocialAccount(id: number): void {
    this.profileService
      .disconnectSocialAccount(id)
      .pipe(takeUntil(this.unsubscribeNotifier))
      .subscribe({
        next: (id: number) => {
          this.socialAccounts.update((accounts) =>
            accounts.filter((account) => account.id != id)
          )
        },
        error: (error) => {
          this.toastService.showError(
            $localize`Error disconnecting social account`,
            error
          )
        },
      })
  }

  public gettotpSettings(): void {
    this.totpSettingsLoading.set(true)
    this.profileService
      .getTotpSettings()
      .pipe(takeUntil(this.unsubscribeNotifier))
      .subscribe({
        next: (totpSettings) => {
          this.totpSettingsLoading.set(false)
          this.totpSettings.set(totpSettings)
        },
        error: (error) => {
          this.toastService.showError(
            $localize`Error fetching TOTP settings`,
            error
          )
          this.totpSettingsLoading.set(false)
        },
      })
  }

  public activateTotp(): void {
    this.totpLoading.set(true)
    this.form.get('totp_code').disable()
    this.profileService
      .activateTotp(
        this.totpSettings().secret,
        this.form.get('totp_code').value
      )
      .pipe(takeUntil(this.unsubscribeNotifier))
      .subscribe({
        next: (activationResponse) => {
          this.totpLoading.set(false)
          this.isTotpEnabled.set(activationResponse.success)
          this.recoveryCodes.set(activationResponse.recovery_codes)
          this.form.get('totp_code').enable()
          if (activationResponse.success) {
            this.toastService.showInfo($localize`TOTP activated successfully`)
          } else {
            this.toastService.showError($localize`Error activating TOTP`)
          }
        },
        error: (error) => {
          this.totpLoading.set(false)
          this.form.get('totp_code').enable()
          this.toastService.showError($localize`Error activating TOTP`, error)
        },
      })
  }

  public deactivateTotp(): void {
    this.totpLoading.set(true)
    this.profileService
      .deactivateTotp()
      .pipe(takeUntil(this.unsubscribeNotifier))
      .subscribe({
        next: (success) => {
          this.totpLoading.set(false)
          this.isTotpEnabled.set(!success)
          this.recoveryCodes.set(null)
          if (success) {
            this.toastService.showInfo($localize`TOTP deactivated successfully`)
          } else {
            this.toastService.showError($localize`Error deactivating TOTP`)
          }
        },
        error: (error) => {
          this.totpLoading.set(false)
          this.toastService.showError($localize`Error deactivating TOTP`, error)
        },
      })
  }

  public copyRecoveryCodes(): void {
    this.clipboard.copy(this.recoveryCodes().join('\n'))
    this.codesCopied.set(true)
    setTimeout(() => {
      this.codesCopied.set(false)
    }, 3000)
  }
}
