import { TestBed } from '@angular/core/testing'
import { DocumentListViewService } from './document-list-view.service'
import {
  HttpClientTestingModule,
  HttpTestingController,
} from '@angular/common/http/testing'
import { environment } from 'src/environments/environment'
import { Subscription } from 'rxjs'
import { ConfirmDialogComponent } from '../components/common/confirm-dialog/confirm-dialog.component'
import { Params, Router, convertToParamMap } from '@angular/router'
import {
  FILTER_HAS_TAGS_ALL,
  FILTER_HAS_TAGS_ANY,
} from '../data/filter-rule-type'
import { PaperlessSavedView } from '../data/paperless-saved-view'
import { FilterRule } from '../data/filter-rule'
import { RouterTestingModule } from '@angular/router/testing'
import { routes } from 'src/app/app-routing.module'
import { PermissionsGuard } from '../guards/permissions.guard'
import { SettingsService } from './settings.service'
import { SETTINGS_KEYS } from '../data/paperless-uisettings'

const documents = [
  {
    id: 1,
    title: 'Doc 1',
    content: 'some content',
    tags: [1, 2, 3],
    correspondent: 11,
    document_type: 3,
    storage_path: 8,
  },
  {
    id: 2,
    title: 'Doc 2',
    content: 'some content',
  },
  {
    id: 3,
    title: 'Doc 3',
    content: 'some content',
  },
  {
    id: 4,
    title: 'Doc 4',
    content: 'some content',
  },
  {
    id: 5,
    title: 'Doc 5',
    content: 'some content',
  },
  {
    id: 6,
    title: 'Doc 6',
    content: 'some content',
  },
]
const full_results = {
  count: documents.length,
  results: documents,
}

const tags__id__all = '9'
const filterRules: FilterRule[] = [
  {
    rule_type: FILTER_HAS_TAGS_ALL,
    value: tags__id__all,
  },
]

const view: PaperlessSavedView = {
  id: 3,
  name: 'Saved View',
  sort_field: 'added',
  sort_reverse: true,
  filter_rules: filterRules,
}

describe('DocumentListViewService', () => {
  let httpTestingController: HttpTestingController
  let documentListViewService: DocumentListViewService
  let subscriptions: Subscription[] = []
  let router: Router
  let settingsService: SettingsService

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [DocumentListViewService, PermissionsGuard, SettingsService],
      imports: [
        HttpClientTestingModule,
        RouterTestingModule.withRoutes(routes),
      ],
      declarations: [ConfirmDialogComponent],
      teardown: { destroyAfterEach: true },
    })

    sessionStorage.clear()
    httpTestingController = TestBed.inject(HttpTestingController)
    documentListViewService = TestBed.inject(DocumentListViewService)
    settingsService = TestBed.inject(SettingsService)
    router = TestBed.inject(Router)
  })

  afterEach(() => {
    httpTestingController.verify()
    sessionStorage.clear()
  })

  afterAll(() => {
    subscriptions?.forEach((subscription) => {
      subscription.unsubscribe()
    })
  })

  it('should reload the list', () => {
    expect(documentListViewService.currentPage).toEqual(1)
    documentListViewService.reload()
    const req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
    )
    expect(req.request.method).toEqual('GET')
    req.flush(full_results)
    httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/selection_data/`
    )
    expect(req.request.method).toEqual('GET')
    expect(documentListViewService.isReloading).toBeFalsy()
    expect(documentListViewService.activeSavedViewId).toBeNull()
    expect(documentListViewService.activeSavedViewTitle).toBeNull()
    expect(documentListViewService.collectionSize).toEqual(documents.length)
    expect(documentListViewService.getLastPage()).toEqual(1)
  })

  it('should handle error on page request out of range', () => {
    documentListViewService.currentPage = 50
    let req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=50&page_size=50&ordering=-created&truncate_content=true`
    )
    expect(req.request.method).toEqual('GET')
    req.flush([], { status: 404, statusText: 'Unexpected error' })
    req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
    )
    expect(req.request.method).toEqual('GET')
    expect(documentListViewService.currentPage).toEqual(1)
  })

  it('should handle error on filtering request', () => {
    documentListViewService.currentPage = 1
    const tags__id__in = 'hello'
    const filterRulesAny = [
      {
        rule_type: FILTER_HAS_TAGS_ANY,
        value: tags__id__in,
      },
    ]
    documentListViewService.filterRules = filterRulesAny
    let req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true&tags__id__in=${tags__id__in}`
    )
    expect(req.request.method).toEqual('GET')
    req.flush(
      { archive_serial_number: 'hello' },
      { status: 404, statusText: 'Unexpected error' }
    )
    req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
    )
    expect(req.request.method).toEqual('GET')
    // reset the list
    documentListViewService.filterRules = []
    req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
    )
  })

  it('should support setting sort', () => {
    expect(documentListViewService.sortField).toEqual('created')
    expect(documentListViewService.sortReverse).toBeTruthy()
    documentListViewService.setSort('added', false)
    let req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=added&truncate_content=true`
    )
    expect(req.request.method).toEqual('GET')
    expect(documentListViewService.sortField).toEqual('added')
    expect(documentListViewService.sortReverse).toBeFalsy()

    documentListViewService.sortField = 'created'
    req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=created&truncate_content=true`
    )
    expect(documentListViewService.sortField).toEqual('created')
    documentListViewService.sortReverse = true
    req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
    )
    expect(req.request.method).toEqual('GET')
    expect(documentListViewService.sortReverse).toBeTruthy()
  })

  it('should load from query params', () => {
    expect(documentListViewService.currentPage).toEqual(1)
    const page = 2
    const sort = 'added'
    const reverse = true
    const params: Params = {
      page,
      sort,
      reverse,
    }
    documentListViewService.loadFromQueryParams(convertToParamMap(params))
    const req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=${page}&page_size=${
        documentListViewService.currentPageSize
      }&ordering=${reverse ? '-' : ''}${sort}&truncate_content=true`
    )
    expect(req.request.method).toEqual('GET')
    expect(documentListViewService.currentPage).toEqual(page)
    expect(documentListViewService.filterRules).toEqual([])
  })

  it('should load filter rules from query params', () => {
    const sort = 'added'
    const reverse = true
    const params: Params = {
      sort,
      reverse,
      tags__id__all,
    }
    documentListViewService.loadFromQueryParams(convertToParamMap(params))
    let req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.currentPageSize}&ordering=-added&truncate_content=true&tags__id__all=${tags__id__all}`
    )
    expect(req.request.method).toEqual('GET')
    expect(documentListViewService.filterRules).toEqual([
      {
        rule_type: FILTER_HAS_TAGS_ALL,
        value: tags__id__all,
      },
    ])
    req.flush(full_results)
    httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/selection_data/`
    )
  })

  it('should use filter rules to update query params', () => {
    documentListViewService.filterRules = filterRules
    const req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.currentPageSize}&ordering=-created&truncate_content=true&tags__id__all=${tags__id__all}`
    )
    expect(req.request.method).toEqual('GET')
  })

  it('should support quick filter', () => {
    documentListViewService.quickFilter(filterRules)
    const req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.currentPageSize}&ordering=-created&truncate_content=true&tags__id__all=${tags__id__all}`
    )
    expect(req.request.method).toEqual('GET')
  })

  it('should support loading saved view', () => {
    const routerSpy = jest.spyOn(router, 'navigate')
    documentListViewService.activateSavedView(view)
    expect(routerSpy).toHaveBeenCalledWith(['view', view.id])
    documentListViewService.activateSavedView(null)
  })

  it('should support loading saved view view query params', () => {
    const page = 2
    const params: Params = {
      view: view.id,
      page,
    }
    documentListViewService.activateSavedViewWithQueryParams(
      view,
      convertToParamMap(params)
    )
    let req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=${page}&page_size=${documentListViewService.currentPageSize}&ordering=-added&truncate_content=true&tags__id__all=${tags__id__all}`
    )
    expect(req.request.method).toEqual('GET')
    // reset the list
    documentListViewService.currentPage = 1
    req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-added&truncate_content=true&tags__id__all=9`
    )
    documentListViewService.filterRules = []
    req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-added&truncate_content=true`
    )
    documentListViewService.sortField = 'created'
    req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
    )
    documentListViewService.activateSavedView(null)
  })

  it('should support navigating next / previous', () => {
    documentListViewService.filterRules = []
    let req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
    )
    expect(documentListViewService.currentPage).toEqual(1)
    documentListViewService.currentPageSize = 3
    documentListViewService.reload()
    req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=3&ordering=-created&truncate_content=true`
    )
    expect(req.request.method).toEqual('GET')
    req.flush({
      count: 3,
      results: documents.slice(0, 3),
    })
    httpTestingController
      .expectOne(`${environment.apiBaseUrl}documents/selection_data/`)
      .flush([])
    expect(documentListViewService.hasNext(documents[0].id)).toBeTruthy()
    expect(documentListViewService.hasPrevious(documents[0].id)).toBeFalsy()
    documentListViewService.getNext(documents[0].id).subscribe((docId) => {
      expect(docId).toEqual(documents[1].id)
    })
    documentListViewService.getNext(documents[2].id).subscribe((docId) => {
      expect(docId).toEqual(documents[3].id)
      expect(documentListViewService.currentPage).toEqual(2)
    })
    documentListViewService.getPrevious(documents[3].id).subscribe((docId) => {
      expect(docId).toEqual(documents[2].id)
      expect(documentListViewService.currentPage).toEqual(1)
    })
  })

  it('should update page size from settings', () => {
    settingsService.set(SETTINGS_KEYS.DOCUMENT_LIST_SIZE, 10)
    documentListViewService.updatePageSize()
    expect(documentListViewService.currentPageSize).toEqual(10)
  })

  it('should support select a document', () => {
    documentListViewService.reload()
    const req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
    )
    expect(req.request.method).toEqual('GET')
    req.flush(full_results)
    httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/selection_data/`
    )
    documentListViewService.toggleSelected(documents[0])
    expect(documentListViewService.isSelected(documents[0])).toBeTruthy()
    documentListViewService.toggleSelected(documents[0])
    expect(documentListViewService.isSelected(documents[0])).toBeFalsy()
  })

  it('should support select all', () => {
    documentListViewService.selectAll()
    const req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
    )
    expect(req.request.method).toEqual('GET')
    req.flush(full_results)
    expect(documentListViewService.selected.size).toEqual(documents.length)
    expect(documentListViewService.isSelected(documents[0])).toBeTruthy()
    documentListViewService.selectNone()
  })

  it('should support select page', () => {
    documentListViewService.currentPageSize = 3
    documentListViewService.reload()
    const req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=3&ordering=-created&truncate_content=true`
    )
    expect(req.request.method).toEqual('GET')
    req.flush({
      count: 3,
      results: documents.slice(0, 3),
    })
    httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/selection_data/`
    )
    documentListViewService.selectPage()
    expect(documentListViewService.selected.size).toEqual(3)
    expect(documentListViewService.isSelected(documents[5])).toBeFalsy()
  })

  it('should support select range', () => {
    documentListViewService.reload()
    const req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
    )
    expect(req.request.method).toEqual('GET')
    req.flush(full_results)
    httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/selection_data/`
    )
    documentListViewService.toggleSelected(documents[0])
    expect(documentListViewService.isSelected(documents[0])).toBeTruthy()
    documentListViewService.selectRangeTo(documents[2])
    expect(documentListViewService.isSelected(documents[1])).toBeTruthy()
    documentListViewService.selectRangeTo(documents[4])
    expect(documentListViewService.isSelected(documents[3])).toBeTruthy()
  })

  it('should support selection range reduction', () => {
    documentListViewService.selectAll()
    let req = httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
    )
    expect(req.request.method).toEqual('GET')
    req.flush(full_results)
    expect(documentListViewService.selected.size).toEqual(6)

    documentListViewService.filterRules = filterRules
    httpTestingController.expectOne(
      `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true&tags__id__all=9`
    )
    const reqs = httpTestingController.match(
      `${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id&tags__id__all=9`
    )
    reqs[0].flush({
      count: 3,
      results: documents.slice(0, 3),
    })
    expect(documentListViewService.selected.size).toEqual(3)
  })
})
