import { describe, test, expect } from 'vitest'
import { MatchingStrategies } from 'meilisearch'
import { adaptSearchParams } from '../search-params-adapter.js'
import type {
  OverridableMeiliSearchSearchParameters,
  SearchContext,
} from '../../../types/index.js'

const DEFAULT_CONTEXT: SearchContext = {
  indexUid: 'test',
  pagination: { page: 0, hitsPerPage: 6, finite: false },
  placeholderSearch: true,
  keepZeroFacets: false,
  attributesToHighlight: ['*'],
  highlightPreTag: '<mark>',
  highlightPostTag: '</mark>',
}

describe('Parameters adapter', () => {
  test('adapting a basic searchContext ', () => {
    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
    })

    expect(searchParams.attributesToHighlight).toContain('*')
    expect(searchParams.attributesToHighlight?.length).toBe(1)
  })

  test('adapting a searchContext with filters and sort', () => {
    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      facetFilters: [['genres:Drama', 'genres:Thriller'], ['title:Ariel']],
      sort: 'id < 1',
    })

    expect(searchParams.filter).toStrictEqual([
      ['"genres"="Drama"', '"genres"="Thriller"'],
      ['"title"="Ariel"'],
    ])
    expect(searchParams.sort).toStrictEqual(['id < 1'])
    expect(searchParams.attributesToHighlight).toContain('*')
    expect(searchParams.attributesToHighlight?.length).toBe(1)
  })

  test('adapting multi-word filters', () => {
    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      facetFilters: [['My Genre:Science Fiction']],
    })

    expect(searchParams.filter).toStrictEqual([
      ['"My Genre"="Science Fiction"'],
    ])
  })

  test('adapting a searchContext with overridden Meilisearch parameters', () => {
    const meiliSearchParams: OverridableMeiliSearchSearchParameters = {
      attributesToHighlight: ['movies', 'genres'],
      highlightPreTag: '<em>',
      highlightPostTag: '</em>',
      matchingStrategy: MatchingStrategies.ALL,
    }

    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      meiliSearchParams,
    })

    expect(searchParams.attributesToHighlight).toEqual(
      meiliSearchParams.attributesToHighlight
    )
    expect(searchParams.highlightPreTag).toEqual(
      meiliSearchParams.highlightPreTag
    )
    expect(searchParams.highlightPostTag).toEqual(
      meiliSearchParams.highlightPostTag
    )
    expect(searchParams.matchingStrategy).toEqual(
      meiliSearchParams.matchingStrategy
    )
  })

  test('adapting a searchContext with overridden Meilisearch parameters for a specific index', () => {
    const meiliSearchParams: OverridableMeiliSearchSearchParameters = {
      attributesToHighlight: ['movies', 'genres'],
      highlightPreTag: '<em>',
      highlightPostTag: '</em>',
      matchingStrategy: MatchingStrategies.ALL,
      indexesOverrides: {
        test: {
          attributesToHighlight: ['release_date'],
          highlightPreTag: '<span>',
          highlightPostTag: '</span>',
          matchingStrategy: MatchingStrategies.LAST,
        },
      },
    }

    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      meiliSearchParams,
    })

    expect(searchParams.attributesToHighlight).toEqual(
      meiliSearchParams.indexesOverrides?.test?.attributesToHighlight
    )
    expect(searchParams.highlightPreTag).toEqual(
      meiliSearchParams.indexesOverrides?.test?.highlightPreTag
    )
    expect(searchParams.highlightPostTag).toEqual(
      meiliSearchParams.indexesOverrides?.test?.highlightPostTag
    )
    expect(searchParams.matchingStrategy).toEqual(
      meiliSearchParams.indexesOverrides?.test?.matchingStrategy
    )
  })

  test('hybrid search configuration can be set via search parameters', () => {
    const hybridSearchConfig = {
      semanticRatio: 0,
      embedder: 'default',
    }

    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      meiliSearchParams: {
        hybrid: hybridSearchConfig,
      },
    })

    expect(searchParams.hybrid).toBe(hybridSearchConfig)
  })

  test('hybrid search configuration can be set via search parameters for a specific index', () => {
    const hybridSearchConfig = {
      semanticRatio: 0,
      embedder: 'default',
    }
    const specificHybridSearchConfig = {
      semanticRatio: 10,
      embedder: 'default',
    }

    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      meiliSearchParams: {
        hybrid: hybridSearchConfig,
        indexesOverrides: {
          test: { hybrid: specificHybridSearchConfig },
        },
      },
    })

    expect(searchParams.hybrid).toBe(specificHybridSearchConfig)
  })

  test('vector can be set via search parameters', () => {
    const vector = [0, 1, 2]

    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      meiliSearchParams: {
        vector,
      },
    })

    expect(searchParams.vector).toBe(vector)
  })
  test('vector can be set via search parameters for a specific index', () => {
    const vector = [0, 1, 2]
    const indexVector = [3, 4, 5]

    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      meiliSearchParams: {
        vector,
        indexesOverrides: { test: { vector: indexVector } },
      },
    })

    expect(searchParams.vector).toBe(indexVector)
  })

  test('ranking score threshold can be set via search parameters', () => {
    const rankingScoreThreshold = 0.974

    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      meiliSearchParams: {
        rankingScoreThreshold,
      },
    })

    expect(searchParams.rankingScoreThreshold).toBe(rankingScoreThreshold)
  })

  test('ranking score threshold can be set via search parameters for a specific index', () => {
    const rankingScoreThreshold = 0.974
    const indexRankingScoreThreshold = 0.567

    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      meiliSearchParams: {
        rankingScoreThreshold,
        indexesOverrides: {
          test: { rankingScoreThreshold: indexRankingScoreThreshold },
        },
      },
    })

    expect(searchParams.rankingScoreThreshold).toBe(indexRankingScoreThreshold)
  })

  test('distinct search configuration can be set via search parameters', () => {
    const distinctSearchConfig = 'title'

    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      meiliSearchParams: {
        distinct: distinctSearchConfig,
      },
    })

    expect(searchParams.distinct).toBe(distinctSearchConfig)
  })

  test('distinct search configuration can be set via search parameters for a specific', () => {
    const distinctSearchConfig = 'title'
    const indexDistinctSearchConfig = 'name'

    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      meiliSearchParams: {
        distinct: distinctSearchConfig,
        indexesOverrides: {
          test: { distinct: indexDistinctSearchConfig },
        },
      },
    })

    expect(searchParams.distinct).toBe(indexDistinctSearchConfig)
  })

  test('filter can be set via global override when IS filters are empty', () => {
    const globalFilter = 'category = "books"'

    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      // No facetFilters, filters, or numericFilters - empty IS filters
      meiliSearchParams: {
        filter: globalFilter,
      },
    })

    expect(searchParams.filter).toBe(globalFilter)
  })

  test('filter can be set via per-index override when IS filters are empty', () => {
    const globalFilter = 'category = "books"'
    const indexFilter = 'status = "active"'

    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      // No facetFilters, filters, or numericFilters - empty IS filters
      meiliSearchParams: {
        filter: globalFilter,
        indexesOverrides: {
          test: { filter: indexFilter },
        },
      },
    })

    // Per-index override should take precedence over global override
    expect(searchParams.filter).toBe(indexFilter)
  })
})

describe('Geo filter adapter', () => {
  test('adapting a searchContext with filters, sort and geo filters ', () => {
    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      facetFilters: [['genres:Drama', 'genres:Thriller'], ['title:Ariel']],
      insideBoundingBox: '0,0,0,0',
      sort: 'id < 1',
    })

    expect(searchParams.filter).toStrictEqual([
      '_geoBoundingBox([0, 0], [0, 0])',
      ['"genres"="Drama"', '"genres"="Thriller"'],
      ['"title"="Ariel"'],
    ])
    expect(searchParams.sort).toStrictEqual(['id < 1'])
    expect(searchParams.attributesToHighlight).toContain('*')
    expect(searchParams.attributesToHighlight?.length).toBe(1)
  })

  test('adapting a searchContext with only facetFilters and geo filters ', () => {
    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      facetFilters: [['genres:Drama', 'genres:Thriller'], ['title:Ariel']],
      insideBoundingBox: '0,0,0,0',
    })

    expect(searchParams.filter).toEqual([
      '_geoBoundingBox([0, 0], [0, 0])',
      ['"genres"="Drama"', '"genres"="Thriller"'],
      ['"title"="Ariel"'],
    ])
    expect(searchParams.attributesToHighlight).toContain('*')
    expect(searchParams.attributesToHighlight?.length).toBe(1)
  })

  test('adapting a searchContext with only sort and geo filters ', () => {
    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      insideBoundingBox: '0,0,0,0',
      sort: 'id < 1',
    })

    expect(searchParams.filter).toEqual(['_geoBoundingBox([0, 0], [0, 0])'])
    expect(searchParams.sort).toStrictEqual(['id < 1'])
    expect(searchParams.attributesToHighlight).toContain('*')
    expect(searchParams.attributesToHighlight?.length).toBe(1)
  })

  test('adapting a searchContext with no sort and no filters and geo filters ', () => {
    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      insideBoundingBox: '0,0,0,0',
    })

    expect(searchParams.filter).toEqual(['_geoBoundingBox([0, 0], [0, 0])'])
    expect(searchParams.attributesToHighlight).toContain('*')
    expect(searchParams.attributesToHighlight?.length).toBe(1)
  })
})

describe('Pagination adapter', () => {
  test('adapting a searchContext with finite pagination', () => {
    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      pagination: { page: 0, hitsPerPage: 6, finite: true },
    })

    expect(searchParams.page).toBe(1)
    expect(searchParams.hitsPerPage).toBe(6)
  })

  test('adapting a searchContext with finite pagination on a later page', () => {
    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      pagination: { page: 10, hitsPerPage: 6, finite: true },
    })

    expect(searchParams.page).toBe(11)
    expect(searchParams.hitsPerPage).toBe(6)
  })

  test('adapting a searchContext with no finite pagination on page 1', () => {
    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
    })

    expect(searchParams.limit).toBe(7)
    expect(searchParams.offset).toBe(0)
  })

  test('adapting a searchContext with no finite pagination on page 2', () => {
    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      pagination: { page: 1, hitsPerPage: 6, finite: false },
    })

    expect(searchParams.limit).toBe(7)
    expect(searchParams.offset).toBe(6)
  })

  test('adapting a finite pagination with no placeholderSearch', () => {
    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      query: '',
      pagination: { page: 4, hitsPerPage: 6, finite: true },
      placeholderSearch: false,
    })

    expect(searchParams.page).toBe(5)
    expect(searchParams.hitsPerPage).toBe(0)
  })

  test('adapting a finite pagination with no placeholderSearch and a query', () => {
    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      query: 'a',
      pagination: { page: 4, hitsPerPage: 6, finite: true },
      placeholderSearch: false,
    })

    expect(searchParams.page).toBe(5)
    expect(searchParams.hitsPerPage).toBeGreaterThan(0)
  })

  test('adapting a finite pagination with no placeholderSearch and a facetFilter', () => {
    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      query: '',
      pagination: { page: 4, hitsPerPage: 6, finite: true },
      placeholderSearch: false,
      facetFilters: ['genres:Action'],
    })

    expect(searchParams.page).toBe(5)
    expect(searchParams.hitsPerPage).toBeGreaterThan(0)
  })

  test('adapting a scroll pagination with no placeholderSearch', () => {
    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      query: '',
      pagination: { page: 4, hitsPerPage: 6, finite: false },
      placeholderSearch: false,
    })

    expect(searchParams.limit).toBe(0)
    expect(searchParams.offset).toBe(0)
  })

  test('adapting a scroll pagination with no placeholderSearch and a query', () => {
    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      query: 'a',
      pagination: { page: 4, hitsPerPage: 6, finite: false },
      placeholderSearch: false,
    })

    expect(searchParams.limit).toBeGreaterThan(0)
    expect(searchParams.offset).toBeGreaterThan(0)
  })

  test('adapting a scroll pagination with no placeholderSearch and a facetFilter', () => {
    const searchParams = adaptSearchParams({
      ...DEFAULT_CONTEXT,
      query: 'a',
      pagination: { page: 4, hitsPerPage: 6, finite: false },
      placeholderSearch: false,
      facetFilters: ['genres:Action'],
    })

    expect(searchParams.limit).toBeGreaterThan(0)
    expect(searchParams.offset).toBeGreaterThan(0)
  })
})
