import { ComponentFixture, TestBed } from '@angular/core/testing'

import { DatePipe } from '@angular/common'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of } from 'rxjs'
import { AuditLogAction } from 'src/app/data/auditlog-entry'
import { DataType } from 'src/app/data/datatype'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { DocumentService } from 'src/app/services/rest/document.service'
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
import { UserService } from 'src/app/services/rest/user.service'
import { DocumentHistoryComponent } from './document-history.component'

describe('DocumentHistoryComponent', () => {
  let component: DocumentHistoryComponent
  let fixture: ComponentFixture<DocumentHistoryComponent>
  let documentService: DocumentService
  let correspondentService: CorrespondentService
  let documentTypeService: DocumentTypeService
  let storagePathService: StoragePathService
  let userService: UserService

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [
        DocumentHistoryComponent,
        NgbCollapseModule,
        NgxBootstrapIconsModule.pick(allIcons),
      ],
      providers: [
        DatePipe,
        provideHttpClient(withInterceptorsFromDi()),
        provideHttpClientTesting(),
      ],
    }).compileComponents()

    fixture = TestBed.createComponent(DocumentHistoryComponent)
    documentService = TestBed.inject(DocumentService)
    correspondentService = TestBed.inject(CorrespondentService)
    documentTypeService = TestBed.inject(DocumentTypeService)
    storagePathService = TestBed.inject(StoragePathService)
    userService = TestBed.inject(UserService)
    component = fixture.componentInstance
  })

  it('should get audit log entries on init', () => {
    const getHistorySpy = jest.spyOn(documentService, 'getHistory')
    getHistorySpy.mockReturnValue(
      of([
        {
          id: 1,
          actor: {
            id: 1,
            username: 'user1',
          },
          action: AuditLogAction.Create,
          timestamp: '2021-01-01T00:00:00Z',
          remote_addr: '1.2.3.4',
          changes: {
            title: ['old title', 'new title'],
          },
        },
      ])
    )
    component.documentId = 1
    fixture.detectChanges()
    expect(getHistorySpy).toHaveBeenCalledWith(1)
  })

  it('getPrettyName should return the correspondent name', () => {
    const correspondentId = '1'
    const correspondentName = 'John Doe'
    const getCachedSpy = jest
      .spyOn(correspondentService, 'getCached')
      .mockReturnValue(of({ name: correspondentName }))
    component
      .getPrettyName(DataType.Correspondent, correspondentId)
      .subscribe((result) => {
        expect(result).toBe(correspondentName)
      })
    expect(getCachedSpy).toHaveBeenCalledWith(parseInt(correspondentId))
  })

  it('getPrettyName should memoize results to avoid resubscribe loops', () => {
    const correspondentId = '1'
    const getCachedSpy = jest
      .spyOn(correspondentService, 'getCached')
      .mockReturnValue(of({ name: 'John Doe' }))
    const a = component.getPrettyName(DataType.Correspondent, correspondentId)
    const b = component.getPrettyName(DataType.Correspondent, correspondentId)
    expect(a).toBe(b)
    expect(getCachedSpy).toHaveBeenCalledTimes(1)
  })

  it('getPrettyName should fall back to the correspondent id when missing', () => {
    const correspondentId = '1'
    jest.spyOn(correspondentService, 'getCached').mockReturnValue(of(null))
    component
      .getPrettyName(DataType.Correspondent, correspondentId)
      .subscribe((result) => {
        expect(result).toBe(correspondentId)
      })
  })

  it('getPrettyName should return the document type name', () => {
    const documentTypeId = '1'
    const documentTypeName = 'Invoice'
    const getCachedSpy = jest
      .spyOn(documentTypeService, 'getCached')
      .mockReturnValue(of({ name: documentTypeName }))
    component
      .getPrettyName(DataType.DocumentType, documentTypeId)
      .subscribe((result) => {
        expect(result).toBe(documentTypeName)
      })
    expect(getCachedSpy).toHaveBeenCalledWith(parseInt(documentTypeId))
  })

  it('getPrettyName should fall back to the document type id when missing', () => {
    const documentTypeId = '1'
    jest.spyOn(documentTypeService, 'getCached').mockReturnValue(of(null))
    component
      .getPrettyName(DataType.DocumentType, documentTypeId)
      .subscribe((result) => {
        expect(result).toBe(documentTypeId)
      })
  })

  it('getPrettyName should return the storage path path', () => {
    const storagePathId = '1'
    const storagePath = '/path/to/storage'
    const getCachedSpy = jest
      .spyOn(storagePathService, 'getCached')
      .mockReturnValue(of({ path: storagePath }))
    component
      .getPrettyName(DataType.StoragePath, storagePathId)
      .subscribe((result) => {
        expect(result).toBe(storagePath)
      })
    expect(getCachedSpy).toHaveBeenCalledWith(parseInt(storagePathId))
  })

  it('getPrettyName should fall back to the storage path id when missing', () => {
    const storagePathId = '1'
    jest.spyOn(storagePathService, 'getCached').mockReturnValue(of(null))
    component
      .getPrettyName(DataType.StoragePath, storagePathId)
      .subscribe((result) => {
        expect(result).toBe(storagePathId)
      })
  })

  it('getPrettyName should return the owner username', () => {
    const ownerId = '1'
    const ownerUsername = 'user1'
    const getCachedSpy = jest
      .spyOn(userService, 'getCached')
      .mockReturnValue(of({ username: ownerUsername }))
    component.getPrettyName('owner', ownerId).subscribe((result) => {
      expect(result).toBe(ownerUsername)
    })
    expect(getCachedSpy).toHaveBeenCalledWith(parseInt(ownerId))
  })

  it('getPrettyName should fall back to the owner id when missing', () => {
    const ownerId = '1'
    jest.spyOn(userService, 'getCached').mockReturnValue(of(null))
    component.getPrettyName('owner', ownerId).subscribe((result) => {
      expect(result).toBe(ownerId)
    })
  })

  it('getPrettyName should return the value as is for other types', () => {
    const id = '123'
    component.getPrettyName('other', id).subscribe((result) => {
      expect(result).toBe(id)
    })
  })
})
