import { DatePipe } from '@angular/common'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import {
  HttpTestingController,
  provideHttpClientTesting,
} from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule } from '@angular/forms'
import { By } from '@angular/platform-browser'
import { Router } from '@angular/router'
import { RouterTestingModule } from '@angular/router/testing'
import { NgbModal, NgbModalRef, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { allIcons, NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { of, throwError } from 'rxjs'
import { routes } from 'src/app/app-routing.module'
import {
  PaperlessTask,
  PaperlessTaskStatus,
  PaperlessTaskTriggerSource,
  PaperlessTaskType,
} from 'src/app/data/paperless-task'
import { Results } from 'src/app/data/results'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { PermissionsService } from 'src/app/services/permissions.service'
import { TasksService } from 'src/app/services/tasks.service'
import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import {
  TaskFilterTargetID,
  TasksComponent,
  TaskSection,
} from './tasks.component'

const tasks: PaperlessTask[] = [
  {
    id: 467,
    task_id: '11ca1a5b-9f81-442c-b2c8-7e4ae53657f1',
    input_data: { filename: 'test.pdf' },
    date_created: new Date('2023-03-01T10:26:03.093116Z'),
    date_done: new Date('2023-03-01T10:26:07.223048Z'),
    task_type: PaperlessTaskType.ConsumeFile,
    task_type_display: 'Consume File',
    trigger_source: PaperlessTaskTriggerSource.FolderConsume,
    trigger_source_display: 'Folder Consume',
    status: PaperlessTaskStatus.Failure,
    status_display: 'Failure',
    result_data: {
      error_message:
        'test.pd: Not consuming test.pdf: It is a duplicate of test (#100)',
    },
    acknowledged: false,
    related_document_ids: [],
  },
  {
    id: 466,
    task_id: '10ca1a5b-3c08-442c-b2c8-7e4ae53657f1',
    input_data: { filename: '191092.pdf' },
    date_created: new Date('2023-03-01T09:26:03.093116Z'),
    date_done: new Date('2023-03-01T09:26:07.223048Z'),
    task_type: PaperlessTaskType.ConsumeFile,
    task_type_display: 'Consume File',
    trigger_source: PaperlessTaskTriggerSource.FolderConsume,
    trigger_source_display: 'Folder Consume',
    status: PaperlessTaskStatus.Failure,
    status_display: 'Failure',
    result_data: { duplicate_of: 311 },
    acknowledged: false,
    related_document_ids: [],
  },
  {
    id: 465,
    task_id: '3612d477-bb04-44e3-985b-ac580dd496d8',
    input_data: { filename: 'Scan Jun 6, 2023 at 3.19 PM.pdf' },
    date_created: new Date('2023-06-06T15:22:05.722323-07:00'),
    date_done: new Date('2023-06-06T15:22:14.564305-07:00'),
    task_type: PaperlessTaskType.ConsumeFile,
    task_type_display: 'Consume File',
    trigger_source: PaperlessTaskTriggerSource.FolderConsume,
    trigger_source_display: 'Folder Consume',
    status: PaperlessTaskStatus.Pending,
    status_display: 'Pending',
    result_data: null,
    acknowledged: false,
    related_document_ids: [],
  },
  {
    id: 464,
    task_id: '2eac4716-2aa6-4dcd-9953-264e11656d7e',
    input_data: { filename: 'paperless-mail-l4dkg8ir' },
    date_created: new Date('2023-06-04T11:24:32.898089-07:00'),
    date_done: new Date('2023-06-04T11:24:44.678605-07:00'),
    task_type: PaperlessTaskType.ConsumeFile,
    task_type_display: 'Consume File',
    trigger_source: PaperlessTaskTriggerSource.EmailConsume,
    trigger_source_display: 'Email Consume',
    status: PaperlessTaskStatus.Success,
    status_display: 'Success',
    result_data: { document_id: 422, duplicate_of: 99 },
    acknowledged: false,
    related_document_ids: [422],
  },
  {
    id: 463,
    task_id: '28125528-1575-4d6b-99e6-168906e8fa5c',
    input_data: { filename: 'onlinePaymentSummary.pdf' },
    date_created: new Date('2023-06-01T13:49:51.631305-07:00'),
    date_done: new Date('2023-06-01T13:49:54.190220-07:00'),
    task_type: PaperlessTaskType.ConsumeFile,
    task_type_display: 'Consume File',
    trigger_source: PaperlessTaskTriggerSource.FolderConsume,
    trigger_source_display: 'Folder Consume',
    status: PaperlessTaskStatus.Success,
    status_display: 'Success',
    result_data: { document_id: 421 },
    acknowledged: false,
    related_document_ids: [421],
  },
  {
    id: 462,
    task_id: 'a5b9ca47-0c8e-490f-a04c-6db5d5fc09e5',
    input_data: { filename: 'paperless-mail-_rrpmqk6' },
    date_created: new Date('2023-06-07T02:54:35.694916Z'),
    date_done: null,
    task_type: PaperlessTaskType.ConsumeFile,
    task_type_display: 'Consume File',
    trigger_source: PaperlessTaskTriggerSource.EmailConsume,
    trigger_source_display: 'Email Consume',
    status: PaperlessTaskStatus.Started,
    status_display: 'Started',
    result_data: null,
    acknowledged: false,
    related_document_ids: [],
  },
  {
    id: 461,
    task_id: 'bb79efb3-1e78-4f31-b4be-0966620b0ce1',
    input_data: { dry_run: false, scope: 'global' },
    date_created: new Date('2023-06-07T03:54:35.694916Z'),
    date_done: null,
    task_type: PaperlessTaskType.SanityCheck,
    task_type_display: 'Sanity Check',
    trigger_source: PaperlessTaskTriggerSource.System,
    trigger_source_display: 'System',
    status: PaperlessTaskStatus.Started,
    status_display: 'Started',
    result_data: { issues_found: 0 },
    acknowledged: false,
    related_document_ids: [],
  },
]

const paginatedTasks: Results<PaperlessTask> = {
  count: tasks.length,
  results: tasks,
}

const sectionCountResponse = {
  all: 7,
  needs_attention: 2,
  in_progress: 3,
  completed: 2,
}

describe('TasksComponent', () => {
  let component: TasksComponent
  let fixture: ComponentFixture<TasksComponent>
  let tasksService: TasksService
  let modalService: NgbModal
  let router: Router
  let httpTestingController: HttpTestingController
  let reloadSpy
  let toastService: ToastService

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [
        NgbModule,
        RouterTestingModule.withRoutes(routes),
        NgxBootstrapIconsModule.pick(allIcons),
        FormsModule,
        TasksComponent,
        PageHeaderComponent,
        IfPermissionsDirective,
        CustomDatePipe,
        ConfirmDialogComponent,
      ],
      providers: [
        {
          provide: PermissionsService,
          useValue: {
            currentUserCan: () => true,
          },
        },
        CustomDatePipe,
        DatePipe,
        PermissionsGuard,
        provideHttpClient(withInterceptorsFromDi()),
        provideHttpClientTesting(),
      ],
    }).compileComponents()

    tasksService = TestBed.inject(TasksService)
    reloadSpy = jest.spyOn(tasksService, 'reload')
    httpTestingController = TestBed.inject(HttpTestingController)
    modalService = TestBed.inject(NgbModal)
    router = TestBed.inject(Router)
    toastService = TestBed.inject(ToastService)
    fixture = TestBed.createComponent(TasksComponent)
    component = fixture.componentInstance
    jest.useFakeTimers()
    fixture.detectChanges()

    httpTestingController
      .expectOne(
        (req) =>
          req.url === `${environment.apiBaseUrl}tasks/` &&
          req.params.get('acknowledged') === 'false' &&
          req.params.get('page_size') === '1000'
      )
      .flush(paginatedTasks)

    httpTestingController
      .expectOne(
        (req) =>
          req.url === `${environment.apiBaseUrl}tasks/` &&
          req.params.get('acknowledged') === 'false' &&
          req.params.get('page_size') === '25' &&
          req.params.get('page') === '1'
      )
      .flush(paginatedTasks)

    httpTestingController
      .expectOne(
        (req) =>
          req.url === `${environment.apiBaseUrl}tasks/status_counts/` &&
          req.params.get('acknowledged') === 'false' &&
          !req.params.has('status')
      )
      .flush(sectionCountResponse)
  })

  it('should display task sections with counts', () => {
    expect(component.selectedSection).toBe(TaskSection.All)
    expect(component.selectedTaskType).toBeNull()
    expect(component.selectedTriggerSource).toBeNull()

    fixture.detectChanges()

    const viewScope = fixture.debugElement.query(By.css('.task-view-scope'))
    const text = viewScope.nativeElement.textContent

    expect(text).toContain('All')
    expect(text).toContain('Needs attention')
    expect(text).toContain('2')
    expect(text).toContain('In progress')
    expect(text).toContain('3')
    expect(text).toContain('Recently completed')
  })

  it('should filter visible sections by selected status', () => {
    component.setSection(TaskSection.InProgress)
    fixture.detectChanges()

    expect(component.visibleSections).toEqual([TaskSection.InProgress])
    expect(fixture.nativeElement.textContent).toContain('In progress')
    expect(fixture.nativeElement.textContent).not.toContain('Recent completed')
  })

  it('should filter tasks by task type', () => {
    component.setSection(TaskSection.InProgress)
    component.setTaskType(PaperlessTaskType.SanityCheck)

    expect(component.tasksForSection(TaskSection.InProgress)).toHaveLength(1)
    expect(component.tasksForSection(TaskSection.InProgress)[0].task_type).toBe(
      PaperlessTaskType.SanityCheck
    )
  })

  it('should filter tasks by trigger source', () => {
    component.setSection(TaskSection.InProgress)
    component.setTriggerSource(PaperlessTaskTriggerSource.EmailConsume)

    expect(component.tasksForSection(TaskSection.InProgress)).toHaveLength(1)
    expect(
      component.tasksForSection(TaskSection.InProgress)[0].trigger_source
    ).toBe(PaperlessTaskTriggerSource.EmailConsume)
  })

  it('should reset all active filters together', () => {
    component.setSection(TaskSection.InProgress)
    component.setTaskType(PaperlessTaskType.SanityCheck)
    component.setTriggerSource(PaperlessTaskTriggerSource.System)
    component.filterText = 'system'
    jest.advanceTimersByTime(150)

    expect(component.isFiltered).toBe(true)

    component.resetFilters()

    expect(component.selectedSection).toBe(TaskSection.InProgress)
    expect(component.selectedTaskType).toBeNull()
    expect(component.selectedTriggerSource).toBeNull()
    expect(component.filterText).toBe('')
    expect(component.isFiltered).toBe(false)
  })

  it('should keep header controls focused on actions and auto refresh', () => {
    fixture.detectChanges()

    const header = fixture.debugElement.query(By.css('pngx-page-header'))
    const headerText = header.nativeElement.textContent

    expect(headerText).toContain('Dismiss visible')
    expect(headerText).toContain('Dismiss all')
    expect(headerText).toContain('Auto refresh')
    expect(headerText).not.toContain('All types')
    expect(headerText).not.toContain('All sources')
    expect(headerText).not.toContain('Reset filters')
  })

  it('should render the view scope row above the filter bar', () => {
    fixture.detectChanges()

    const controls = fixture.debugElement.query(By.css('.task-controls'))
    const viewScope = controls.query(By.css('.task-view-scope'))
    const search = controls.query(By.css('.task-search'))

    expect(viewScope).not.toBeNull()
    expect(search).not.toBeNull()
    expect(
      viewScope.nativeElement.compareDocumentPosition(search.nativeElement) &
        Node.DOCUMENT_POSITION_FOLLOWING
    ).toBeTruthy()
  })

  it('should render pagination controls next to the task filter', () => {
    fixture.detectChanges()

    const controls = fixture.debugElement.query(By.css('.task-controls'))
    const search = controls.query(By.css('.task-search'))
    const pagination = controls.query(By.css('ngb-pagination'))

    expect(search).not.toBeNull()
    expect(pagination).not.toBeNull()
  })

  it('should apply the selected section to the server-side task query', () => {
    component.setSection(TaskSection.NeedsAttention)

    const req = httpTestingController.expectOne(
      (request) =>
        request.url === `${environment.apiBaseUrl}tasks/` &&
        request.params.get('page') === '1' &&
        request.params.get('page_size') === '25' &&
        request.params.get('acknowledged') === 'false' &&
        request.params.getAll('status').includes(PaperlessTaskStatus.Failure) &&
        request.params.getAll('status').includes(PaperlessTaskStatus.Revoked)
    )

    req.flush({ count: 2, results: [tasks[0], tasks[1]] })
    expect(component.totalTasks()).toBe(2)
  })

  it('should apply task type and trigger source filters to the server-side task query', () => {
    component.setTaskType(PaperlessTaskType.SanityCheck)

    httpTestingController
      .expectOne(
        (request) =>
          request.url === `${environment.apiBaseUrl}tasks/` &&
          request.params.get('page_size') === '25' &&
          request.params.get('task_type') === PaperlessTaskType.SanityCheck
      )
      .flush({ count: 1, results: [tasks[6]] })

    component.setTriggerSource(PaperlessTaskTriggerSource.System)

    httpTestingController
      .expectOne(
        (request) =>
          request.url === `${environment.apiBaseUrl}tasks/` &&
          request.params.get('page_size') === '25' &&
          request.params.get('task_type') === PaperlessTaskType.SanityCheck &&
          request.params.get('trigger_source') ===
            PaperlessTaskTriggerSource.System
      )
      .flush({ count: 1, results: [tasks[6]] })
  })

  it('should apply text filters to the server-side task query', () => {
    component.filterText = 'invoice'
    jest.advanceTimersByTime(150)

    httpTestingController
      .expectOne(
        (request) =>
          request.url === `${environment.apiBaseUrl}tasks/` &&
          request.params.get('page_size') === '25' &&
          request.params.get('name') === 'invoice'
      )
      .flush({ count: 1, results: [tasks[0]] })

    component.setFilterTarget(TaskFilterTargetID.Result)

    httpTestingController
      .expectOne(
        (request) =>
          request.url === `${environment.apiBaseUrl}tasks/` &&
          request.params.get('page_size') === '25' &&
          request.params.get('result') === 'invoice'
      )
      .flush({ count: 0, results: [] })
  })

  it('should load a different task page when pagination changes', () => {
    component.setPage(2)

    const pageTwoTasks = {
      count: 30,
      results: [tasks[0]],
    }

    httpTestingController
      .expectOne(
        (req) =>
          req.url === `${environment.apiBaseUrl}tasks/` &&
          req.params.get('acknowledged') === 'false' &&
          req.params.get('page_size') === '25' &&
          req.params.get('page') === '2'
      )
      .flush(pageTwoTasks)

    expect(component.page).toBe(2)
    expect(component.totalTasks()).toBe(30)
    expect(component.pagedTasks()).toEqual([tasks[0]])
  })

  it('should not replace section counts with current-page counts', () => {
    component.setPage(2)

    httpTestingController
      .expectOne(
        (req) =>
          req.url === `${environment.apiBaseUrl}tasks/` &&
          req.params.get('acknowledged') === 'false' &&
          req.params.get('page_size') === '25' &&
          req.params.get('page') === '2'
      )
      .flush({
        count: 30,
        results: [tasks[0]],
      })

    expect(component.sectionCount(TaskSection.NeedsAttention)).toBe(2)
    expect(component.sectionCount(TaskSection.InProgress)).toBe(3)
    expect(component.sectionCount(TaskSection.Completed)).toBe(2)
  })

  it('should expose stable task type options and disable empty ones', () => {
    expect(component.taskTypeOptions.map((option) => option.value)).toContain(
      PaperlessTaskType.TrainClassifier
    )
    expect(
      component.isTaskTypeOptionDisabled(PaperlessTaskType.TrainClassifier)
    ).toBe(true)
    expect(
      component.isTaskTypeOptionDisabled(PaperlessTaskType.ConsumeFile)
    ).toBe(false)
  })

  it('should fall back to the raw selected task type label when no option matches', () => {
    component.selectedTaskType = 'unknown_task_type' as PaperlessTaskType

    expect(component.selectedTaskTypeLabel).toBe('unknown_task_type')
  })

  it('should expose stable trigger source options and disable empty ones', () => {
    expect(
      component.triggerSourceOptions.map((option) => option.value)
    ).toContain(PaperlessTaskTriggerSource.ApiUpload)
    expect(
      component.isTriggerSourceOptionDisabled(
        PaperlessTaskTriggerSource.ApiUpload
      )
    ).toBe(true)
    expect(
      component.isTriggerSourceOptionDisabled(
        PaperlessTaskTriggerSource.EmailConsume
      )
    ).toBe(false)
  })

  it('should fall back to the raw selected trigger source label when no option matches', () => {
    component.selectedTriggerSource =
      'unknown_trigger_source' as PaperlessTaskTriggerSource

    expect(component.selectedTriggerSourceLabel).toBe('unknown_trigger_source')
  })

  it('should support expanding / collapsing one task at a time', () => {
    component.expandTask(tasks[0])
    expect(component.expandedTask).toEqual(tasks[0].id)
    component.expandTask(tasks[1])
    expect(component.expandedTask).toEqual(tasks[1].id)
    component.expandTask(tasks[1])
    expect(component.expandedTask).toBeUndefined()
  })

  it('should show structured task details when expanded', () => {
    component.setSection(TaskSection.InProgress)
    component.expandTask(tasks[6])
    fixture.detectChanges()

    const detailText = fixture.nativeElement.textContent

    expect(detailText).toContain('Input data')
    expect(detailText).toContain('Result data')
    expect(detailText).toContain('"scope": "global"')
    expect(detailText).toContain('"issues_found": 0')
  })

  it('should show duplicate warnings and duplicate details when present', () => {
    component.setSection(TaskSection.Completed)
    component.expandTask(tasks[3])
    fixture.detectChanges()

    const content = fixture.nativeElement.textContent

    expect(content).toContain('Duplicate of document #99')
    expect(content).toContain('Duplicate')
    expect(content).toContain('Open')
  })

  it('should support dismiss single task', () => {
    const dismissSpy = jest.spyOn(tasksService, 'dismissTasks')
    component.dismissTask(tasks[0])
    expect(dismissSpy).toHaveBeenCalledWith(new Set([tasks[0].id]))
  })

  it('should support dismiss specific checked tasks', () => {
    component.toggleSelected(tasks[0])
    component.toggleSelected(tasks[1])
    component.toggleSelected(tasks[3])
    component.toggleSelected(tasks[3])
    const selected = new Set([tasks[0].id, tasks[1].id])
    expect(component.selectedTasks).toEqual(selected)
    let modal: NgbModalRef
    modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
    const dismissSpy = jest.spyOn(tasksService, 'dismissTasks')
    fixture.detectChanges()
    component.dismissTasks()
    expect(modal).not.toBeUndefined()
    modal.componentInstance.confirmClicked.emit()
    expect(dismissSpy).toHaveBeenCalledWith(selected)
  })

  it('should show an error and re-enable modal buttons when dismissing multiple tasks fails', () => {
    component.selectedTasks = new Set([tasks[0].id, tasks[1].id])
    const error = new Error('dismiss failed')
    const toastSpy = jest.spyOn(toastService, 'showError')
    const dismissSpy = jest
      .spyOn(tasksService, 'dismissTasks')
      .mockReturnValue(throwError(() => error))

    let modal: NgbModalRef
    modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))

    component.dismissTasks()
    expect(modal).not.toBeUndefined()

    modal.componentInstance.confirmClicked.emit()

    expect(dismissSpy).toHaveBeenCalledWith(new Set([tasks[0].id, tasks[1].id]))
    expect(toastSpy).toHaveBeenCalledWith('Error dismissing tasks', error)
    expect(modal.componentInstance.buttonsEnabled()).toBe(true)
    expect(component.selectedTasks.size).toBe(0)
  })

  it('should show an error when dismissing a single task fails', () => {
    const error = new Error('dismiss failed')
    const toastSpy = jest.spyOn(toastService, 'showError')
    const dismissSpy = jest
      .spyOn(tasksService, 'dismissTasks')
      .mockReturnValue(throwError(() => error))

    component.dismissTask(tasks[0])

    expect(dismissSpy).toHaveBeenCalledWith(new Set([tasks[0].id]))
    expect(toastSpy).toHaveBeenCalledWith('Error dismissing task', error)
    expect(component.selectedTasks.size).toBe(0)
  })

  it('should support dismiss visible tasks', () => {
    component.setSection(TaskSection.NeedsAttention)
    let modal: NgbModalRef
    modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
    const dismissSpy = jest.spyOn(tasksService, 'dismissTasks')
    component.dismissTasks()
    expect(modal).not.toBeUndefined()
    modal.componentInstance.confirmClicked.emit()
    expect(dismissSpy).toHaveBeenCalledWith(new Set([467, 466]))
  })

  it('should support dismiss all tasks', () => {
    let modal: NgbModalRef
    modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
    const dismissSpy = jest
      .spyOn(tasksService, 'dismissAllTasks')
      .mockReturnValue(of({}))
    const reloadPageSpy = jest
      .spyOn(component as any, 'reloadPage')
      .mockImplementation(() => undefined)

    component.dismissAllTasks()

    expect(modal).not.toBeUndefined()
    expect(modal.componentInstance.messageBold).toBe('Dismiss all 7 tasks?')
    modal.componentInstance.confirmClicked.emit()
    expect(dismissSpy).toHaveBeenCalled()
    expect(reloadPageSpy).toHaveBeenCalledWith(false)
    expect(component.selectedTasks.size).toBe(0)
  })

  it('should show an error and re-enable modal buttons when dismissing all tasks fails', () => {
    const error = new Error('dismiss all failed')
    const toastSpy = jest.spyOn(toastService, 'showError')
    const dismissSpy = jest
      .spyOn(tasksService, 'dismissAllTasks')
      .mockReturnValue(throwError(() => error))

    let modal: NgbModalRef
    modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))

    component.dismissAllTasks()
    expect(modal).not.toBeUndefined()

    modal.componentInstance.confirmClicked.emit()

    expect(dismissSpy).toHaveBeenCalled()
    expect(toastSpy).toHaveBeenCalledWith('Error dismissing tasks', error)
    expect(modal.componentInstance.buttonsEnabled()).toBe(true)
  })

  it('should dismiss the currently visible scoped and filtered tasks', () => {
    component.setSection(TaskSection.InProgress)
    component.setTaskType(PaperlessTaskType.SanityCheck)
    component.setTriggerSource(PaperlessTaskTriggerSource.System)

    const dismissSpy = jest.spyOn(tasksService, 'dismissTasks')

    component.dismissTasks()

    expect(dismissSpy).toHaveBeenCalledWith(new Set([461]))
  })

  it('should support toggling a full section', () => {
    component.setSection(TaskSection.NeedsAttention)
    fixture.detectChanges()

    const toggleCheck = fixture.debugElement.query(
      By.css('#all-tasks-needs_attention')
    )
    expect(toggleCheck).not.toBeNull()
    toggleCheck.nativeElement.dispatchEvent(new MouseEvent('click'))
    fixture.detectChanges()
    expect(component.selectedTasks).toEqual(new Set([467, 466]))
  })

  it('should remove a full section from selection when toggled off', () => {
    component.setSection(TaskSection.NeedsAttention)
    component.selectedTasks = new Set([467, 466])

    component.toggleSection(TaskSection.NeedsAttention, {
      target: { checked: false },
    } as unknown as PointerEvent)

    expect(component.selectedTasks).toEqual(new Set())
  })

  it('should support dismiss and open a document', () => {
    const dismissSpy = jest.spyOn(component, 'dismissTask')

    fixture.detectChanges()

    const openDocumentLink = fixture.debugElement
      .queryAll(By.css('a'))
      .find((link) => link.nativeElement.textContent.includes('Open Document'))

    expect(openDocumentLink).not.toBeNull()

    openDocumentLink.triggerEventHandler(
      'click',
      new MouseEvent('click', { ctrlKey: true })
    )

    expect(dismissSpy).toHaveBeenCalledWith(tasks[3])
  })

  it('should auto refresh, allow toggle', () => {
    expect(reloadSpy).toHaveBeenCalledTimes(1)
    jest.advanceTimersByTime(5000)
    expect(reloadSpy).toHaveBeenCalledTimes(2)
    component.autoRefreshEnabled = false
    jest.advanceTimersByTime(6000)
    expect(reloadSpy).toHaveBeenCalledTimes(2)
  })

  it('should filter tasks by file name', () => {
    fixture.detectChanges()
    const input = fixture.debugElement.query(
      By.css('.task-search input[type=text]')
    )
    expect(input).not.toBeNull()
    input.nativeElement.value = '191092'
    input.nativeElement.dispatchEvent(new Event('input'))
    jest.advanceTimersByTime(150)
    fixture.detectChanges()
    expect(component.filterText).toEqual('191092')
    expect(component.tasksForSection(TaskSection.NeedsAttention)).toHaveLength(
      1
    )
  })

  it('should match task type and source in name filtering', () => {
    component.setSection(TaskSection.InProgress)
    component.filterText = 'system'
    jest.advanceTimersByTime(150)

    expect(component.tasksForSection(TaskSection.InProgress)).toHaveLength(1)
    expect(component.tasksForSection(TaskSection.InProgress)[0].task_type).toBe(
      PaperlessTaskType.SanityCheck
    )
  })

  it('should fall back to task type when filename is unavailable', () => {
    component.setSection(TaskSection.InProgress)
    fixture.detectChanges()

    const nameColumn = fixture.debugElement.queryAll(
      By.css('tbody td.name-col')
    )
    const sanityTaskRow = nameColumn.find((cell) =>
      cell.nativeElement.textContent.includes('Sanity Check')
    )

    expect(sanityTaskRow.nativeElement.textContent).toContain('Sanity Check')
    expect(sanityTaskRow.nativeElement.textContent).toContain('System')
  })

  it('should filter tasks by result', () => {
    component.setSection(TaskSection.NeedsAttention)
    component.filterTargetID = 1
    fixture.detectChanges()
    const input = fixture.debugElement.query(
      By.css('.task-search input[type=text]')
    )
    expect(input).not.toBeNull()
    input.nativeElement.value = 'duplicate'
    input.nativeElement.dispatchEvent(new Event('input'))
    jest.advanceTimersByTime(150)
    fixture.detectChanges()
    expect(component.filterText).toEqual('duplicate')
    expect(component.tasksForSection(TaskSection.NeedsAttention)).toHaveLength(
      2
    )
  })

  it('should prefer explicit reason in the result message', () => {
    expect(
      component.taskResultMessage({
        ...tasks[0],
        result_data: { reason: 'Manual review required', duplicate_of: 311 },
      })
    ).toBe('Manual review required')
  })

  it('should return null preview and popover text when there is no result message', () => {
    expect(component.taskResultPreview(tasks[2])).toBeNull()
    expect(component.taskResultPopoverMessage(tasks[2])).toBe('')
    expect(component.taskResultMessageOverflowsPopover(tasks[2])).toBe(false)
  })

  it('should navigate to a duplicate document details page', () => {
    const routerSpy = jest.spyOn(router, 'navigate')

    component.openDuplicateDocument(99)

    expect(routerSpy).toHaveBeenCalledWith(['documents', 99, 'details'])
  })

  it('should report when a result message overflows the popover limit', () => {
    const longMessage = 'x'.repeat(350)
    const task = {
      ...tasks[0],
      result_data: { error_message: longMessage },
    }

    expect(component.taskResultPopoverMessage(task)).toBe(
      longMessage.slice(0, 300)
    )
    expect(component.taskResultMessageOverflowsPopover(task)).toBe(true)
  })

  it('should support keyboard events for filtering', () => {
    fixture.detectChanges()
    const input = fixture.debugElement.query(
      By.css('.task-search input[type=text]')
    )
    expect(input).not.toBeNull()
    input.nativeElement.value = '191092'
    input.nativeElement.dispatchEvent(
      new KeyboardEvent('keyup', { key: 'Enter' })
    )
    expect(component.filterText).toEqual('191092')
    input.nativeElement.dispatchEvent(
      new KeyboardEvent('keyup', { key: 'Escape' })
    )
    expect(component.filterText).toEqual('')
  })

  it('should keep clearing selection independent from resetting filters', () => {
    component.resetFilter()
    expect(component.filterText).toBe('')

    component.setTaskType(PaperlessTaskType.ConsumeFile)
    component.toggleSelected(tasks[0])
    expect(component.selectedTasks.size).toBe(1)

    component.clearSelection()

    expect(component.selectedTasks.size).toBe(0)
    expect(component.selectedTaskType).toBe(PaperlessTaskType.ConsumeFile)
    expect(component.isFiltered).toBe(true)
  })
})
