import { describe, expect, test, vi } from 'vitest'
import type { Query as PiniaOrmQuery } from 'pinia-orm'
import type { Ref } from 'vue'
import { ref } from 'vue'
import EqualFilter from '~/services/data/Filters/EqualFilter'
import type ApiResource from '~/models/ApiResource'
import SearchFilter from '~/services/data/Filters/SearchFilter'
import { SEARCH_STRATEGY } from '~/types/enum/data'

class TestableSearchFilter extends SearchFilter {
  public search(value: string, filterValue: Ref<string | null>): boolean {
    return super.search(value, filterValue)
  }
}

describe('constructor', () => {
  test('simple call', () => {
    const filter = new SearchFilter('foo', ref('abc'))

    expect(filter.field).toEqual('foo')
    expect(filter.filterValue.value).toEqual('abc')
    expect(filter.reactiveFilter).toEqual(false)
  })

  test('simple call with reactive filters and mode', () => {
    const filter = new SearchFilter(
      'foo',
      ref('abc'),
      SEARCH_STRATEGY.PARTIAL,
      true,
    )

    expect(filter.field).toEqual('foo')
    expect(filter.filterValue.value).toEqual('abc')
    expect(filter.mode).toEqual(SEARCH_STRATEGY.PARTIAL)
    expect(filter.reactiveFilter).toEqual(true)
  })
})

describe('search', () => {
  test('exact mode (default)', () => {
    const filter = new TestableSearchFilter('foo', ref('abc'))

    expect(filter.search('azerty', ref('azerty'))).toBeTruthy()
    expect(filter.search('azerty', ref('AZERTY'))).toBeFalsy()
    expect(filter.search('azerty', ref('foo'))).toBeFalsy()
  })

  test('iexact mode', () => {
    const filter = new TestableSearchFilter(
      'foo',
      ref('abc'),
      SEARCH_STRATEGY.IEXACT,
    )

    expect(filter.search('azerty', ref('azerty'))).toBeTruthy()
    expect(filter.search('azerty', ref('AZERTY'))).toBeTruthy()
    expect(filter.search('azerty', ref('foo'))).toBeFalsy()
  })

  test('partial mode', () => {
    const filter = new TestableSearchFilter(
      'foo',
      ref('abc'),
      SEARCH_STRATEGY.PARTIAL,
    )

    expect(filter.search('azerty', ref('azerty'))).toBeTruthy()
    expect(filter.search('azerty', ref('azer'))).toBeTruthy()
    expect(filter.search('azerty', ref('zer'))).toBeTruthy()
    expect(filter.search('azerty', ref('AZER'))).toBeFalsy()
    expect(filter.search('azerty', ref('foo'))).toBeFalsy()
  })

  test('ipartial mode', () => {
    const filter = new TestableSearchFilter(
      'foo',
      ref('abc'),
      SEARCH_STRATEGY.IPARTIAL,
    )

    expect(filter.search('azerty', ref('azerty'))).toBeTruthy()
    expect(filter.search('azerty', ref('azer'))).toBeTruthy()
    expect(filter.search('azerty', ref('zer'))).toBeTruthy()
    expect(filter.search('azerty', ref('AZER'))).toBeTruthy()
    expect(filter.search('azerty', ref('foo'))).toBeFalsy()
  })

  test('start mode', () => {
    const filter = new TestableSearchFilter(
      'foo',
      ref('abc'),
      SEARCH_STRATEGY.START,
    )

    expect(filter.search('azerty', ref('azerty'))).toBeTruthy()
    expect(filter.search('azerty', ref('azer'))).toBeTruthy()
    expect(filter.search('azerty', ref('zer'))).toBeFalsy()
    expect(filter.search('azerty', ref('AZER'))).toBeFalsy()
    expect(filter.search('azerty', ref('foo'))).toBeFalsy()
  })

  test('end mode', () => {
    const filter = new TestableSearchFilter(
      'foo',
      ref('abc'),
      SEARCH_STRATEGY.END,
    )

    expect(filter.search('azerty', ref('azerty'))).toBeTruthy()
    expect(filter.search('azerty', ref('rty'))).toBeTruthy()
    expect(filter.search('azerty', ref('zer'))).toBeFalsy()
    expect(filter.search('azerty', ref('AZER'))).toBeFalsy()
    expect(filter.search('azerty', ref('foo'))).toBeFalsy()
  })

  test('word-start mode', () => {
    const filter = new TestableSearchFilter(
      'foo',
      ref('abc'),
      SEARCH_STRATEGY.WORD_START,
    )

    expect(filter.search('Once upon a time', ref('tim'))).toBeTruthy()
    expect(filter.search('Once upon a time', ref('onc'))).toBeTruthy()
    expect(filter.search('Once upon a time', ref('TIM'))).toBeTruthy()
    expect(filter.search('Once upon a time', ref('ime'))).toBeFalsy()
    expect(filter.search('Once upon a time', ref('foo'))).toBeFalsy()
  })

  test('unknown mode', () => {
    // @ts-ignore
    const filter = new TestableSearchFilter('foo', ref('abc'), 'other')

    expect(() => filter.search('azerty', ref('azerty'))).toThrowError()
  })

  test('null filter', () => {
    // @ts-ignore
    const filter = new TestableSearchFilter('foo', ref(null))

    expect(filter.search('azerty', ref(null))).toBeFalsy()
  })
})

describe('applyToPiniaOrmQuery', () => {
  test('simple call', () => {
    const filterValue = ref('abc')
    const filter = new TestableSearchFilter('foo', filterValue)

    // @ts-ignore
    const piniaOrmQuery1 = vi.fn() as PiniaOrmQuery<ApiResource>

    // @ts-ignore
    const piniaOrmQuery2 = vi.fn() as PiniaOrmQuery<ApiResource>

    piniaOrmQuery1.where = vi.fn((field, callback) => {
      // eslint-disable-next-line n/no-callback-literal
      callback('test')
      return piniaOrmQuery2
    })

    filter.search = vi.fn((value, filterValue) => true)

    const result = filter.applyToPiniaOrmQuery(piniaOrmQuery1)

    expect(result).toEqual(piniaOrmQuery2)
    expect(filter.search).toHaveBeenCalledWith('test', filterValue)
  })

  test('empty filter value', () => {
    const filterValue = ref(null)
    const filter = new TestableSearchFilter('foo', filterValue)

    // @ts-ignore
    const piniaOrmQuery1 = vi.fn() as PiniaOrmQuery<ApiResource>

    const result = filter.applyToPiniaOrmQuery(piniaOrmQuery1)

    expect(result).toEqual(piniaOrmQuery1)
  })
})

describe('getApiQueryPart', () => {
  test('simple call', () => {
    const filter = new TestableSearchFilter('foo', ref('abc'))

    const result = filter.getApiQueryPart()

    expect(result).toEqual('foo[]=abc')
  })

  test('empty filter value', () => {
    const filter = new TestableSearchFilter('foo', ref(''))

    const result = filter.getApiQueryPart()

    expect(result).toEqual('')
  })

  test('null filter value', () => {
    const filter = new TestableSearchFilter('foo', ref(null))

    const result = filter.getApiQueryPart()

    expect(result).toEqual('')
  })
})
