import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import {
  ComponentFixture,
  TestBed,
  fakeAsync,
  tick,
} from '@angular/core/testing'
import { Router, RouterModule } from '@angular/router'
import { NgbModalModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxFileDropModule } from 'ngx-file-drop'
import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap'
import { Subject } from 'rxjs'
import { routes } from './app-routing.module'
import { AppComponent } from './app.component'
import { ToastsComponent } from './components/common/toasts/toasts.component'
import { FileDropComponent } from './components/file-drop/file-drop.component'
import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
import { PermissionsGuard } from './guards/permissions.guard'
import {
  ConsumerStatusService,
  FileStatus,
} from './services/consumer-status.service'
import { HotKeyService } from './services/hot-key.service'
import { PermissionsService } from './services/permissions.service'
import { SettingsService } from './services/settings.service'
import { Toast, ToastService } from './services/toast.service'

describe('AppComponent', () => {
  let component: AppComponent
  let fixture: ComponentFixture<AppComponent>
  let tourService: TourService
  let consumerStatusService: ConsumerStatusService
  let permissionsService: PermissionsService
  let toastService: ToastService
  let router: Router
  let settingsService: SettingsService
  let hotKeyService: HotKeyService

  beforeEach(async () => {
    TestBed.configureTestingModule({
      declarations: [AppComponent, ToastsComponent, FileDropComponent],
      imports: [
        TourNgBootstrapModule,
        RouterModule.forRoot(routes),
        NgxFileDropModule,
        NgbModalModule,
      ],
      providers: [
        PermissionsGuard,
        DirtySavedViewGuard,
        provideHttpClient(withInterceptorsFromDi()),
        provideHttpClientTesting(),
      ],
    }).compileComponents()

    tourService = TestBed.inject(TourService)
    consumerStatusService = TestBed.inject(ConsumerStatusService)
    permissionsService = TestBed.inject(PermissionsService)
    settingsService = TestBed.inject(SettingsService)
    toastService = TestBed.inject(ToastService)
    router = TestBed.inject(Router)
    hotKeyService = TestBed.inject(HotKeyService)
    fixture = TestBed.createComponent(AppComponent)
    component = fixture.componentInstance
  })

  it('should initialize the tour service & toggle class on body for styling', fakeAsync(() => {
    jest.spyOn(console, 'warn').mockImplementation(() => {})
    fixture.detectChanges()
    const tourSpy = jest.spyOn(tourService, 'initialize')
    component.ngOnInit()
    expect(tourSpy).toHaveBeenCalled()
    tourService.start()
    expect(document.body.classList).toContain('tour-active')
    tourService.end()
    tick(500)
    expect(document.body.classList).not.toContain('tour-active')
  }))

  it('should display toast on document consumed with link if user has access', () => {
    const navigateSpy = jest.spyOn(router, 'navigate')
    jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
    let toast: Toast
    toastService.getToasts().subscribe((toasts) => (toast = toasts[0]))
    const toastSpy = jest.spyOn(toastService, 'show')
    const fileStatusSubject = new Subject<FileStatus>()
    jest
      .spyOn(consumerStatusService, 'onDocumentConsumptionFinished')
      .mockReturnValue(fileStatusSubject)
    component.ngOnInit()
    const status = new FileStatus()
    status.documentId = 1
    fileStatusSubject.next(status)
    expect(toastSpy).toHaveBeenCalled()
    expect(toast.action).not.toBeUndefined()
    toast.action()
    expect(navigateSpy).toHaveBeenCalledWith(['documents', status.documentId])
  })

  it('should display toast on document consumed without link if user does not have access', () => {
    jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(false)
    let toast: Toast
    toastService.getToasts().subscribe((toasts) => (toast = toasts[0]))
    const toastSpy = jest.spyOn(toastService, 'show')
    const fileStatusSubject = new Subject<FileStatus>()
    jest
      .spyOn(consumerStatusService, 'onDocumentConsumptionFinished')
      .mockReturnValue(fileStatusSubject)
    component.ngOnInit()
    fileStatusSubject.next(new FileStatus())
    expect(toastSpy).toHaveBeenCalled()
    expect(toast.action).toBeUndefined()
  })

  it('should display toast on document added', () => {
    jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
    const toastSpy = jest.spyOn(toastService, 'show')
    const fileStatusSubject = new Subject<FileStatus>()
    jest
      .spyOn(consumerStatusService, 'onDocumentDetected')
      .mockReturnValue(fileStatusSubject)
    component.ngOnInit()
    fileStatusSubject.next(new FileStatus())
    expect(toastSpy).toHaveBeenCalled()
  })

  it('should suppress dashboard notifications if set', () => {
    jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
    jest.spyOn(settingsService, 'get').mockReturnValue(true)
    jest.spyOn(router, 'url', 'get').mockReturnValue('/dashboard')
    const toastSpy = jest.spyOn(toastService, 'show')
    const fileStatusSubject = new Subject<FileStatus>()
    jest
      .spyOn(consumerStatusService, 'onDocumentDetected')
      .mockReturnValue(fileStatusSubject)
    component.ngOnInit()
    fileStatusSubject.next(new FileStatus())
    expect(toastSpy).not.toHaveBeenCalled()
  })

  it('should display toast on document failed', () => {
    jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
    const toastSpy = jest.spyOn(toastService, 'showError')
    const fileStatusSubject = new Subject<FileStatus>()
    jest
      .spyOn(consumerStatusService, 'onDocumentConsumptionFailed')
      .mockReturnValue(fileStatusSubject)
    component.ngOnInit()
    fileStatusSubject.next(new FileStatus())
    expect(toastSpy).toHaveBeenCalled()
  })

  it('should support hotkeys', () => {
    const addShortcutSpy = jest.spyOn(hotKeyService, 'addShortcut')
    const routerSpy = jest.spyOn(router, 'navigate')
    // prevent actual navigation
    routerSpy.mockReturnValue(new Promise(() => {}))
    jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
    component.ngOnInit()
    expect(addShortcutSpy).toHaveBeenCalled()
    document.dispatchEvent(new KeyboardEvent('keydown', { key: 'h' }))
    expect(routerSpy).toHaveBeenCalledWith(['/dashboard'])
    document.dispatchEvent(new KeyboardEvent('keydown', { key: 'd' }))
    expect(routerSpy).toHaveBeenCalledWith(['/documents'])
    document.dispatchEvent(new KeyboardEvent('keydown', { key: 's' }))
    expect(routerSpy).toHaveBeenCalledWith(['/settings'])
  })
})