import { describe, it, expect, vi, beforeEach } from 'vitest'
import { getQueryStats } from './get-query-stats'
import type { NumberStatsResponse, StringStatsResponse } from './types'
import {
  DEFAULT_CARTO_API_URL,
  DEFAULT_CARTO_API_VERSION,
} from '@carto/ps-services'
import * as services from '@carto/ps-services'

vi.mock('@carto/ps-services', async () => {
  const actual = await vi.importActual<typeof services>('@carto/ps-services')
  return {
    ...actual,
    executeFetch: vi.fn(),
  }
})

const accessToken = 'test-token'
const connection = 'test-connection'
const attribute = 'test-attribute'
const query = 'SELECT * FROM table'

const numberStatsResponse: NumberStatsResponse = {
  attribute: 'test-attribute',
  type: 'number',
  avg: 50.5,
  sum: 1000,
  min: 10,
  max: 100,
  quantiles: {
    25: [25],
    50: [50],
    75: [75],
  },
}

const stringStatsResponse: StringStatsResponse = {
  attribute: 'test-attribute',
  type: 'string',
  categories: [
    { category: 'Category A', frequency: 100 },
    { category: 'Category B', frequency: 75 },
    { category: 'Category C', frequency: 50 },
  ],
}

const mockExecuteFetch = vi.mocked(services.executeFetch)

beforeEach(() => {
  mockExecuteFetch.mockClear()
})

describe('getQueryStats', () => {
  it('should fetch number statistics for a query', async () => {
    mockExecuteFetch.mockResolvedValue(numberStatsResponse)

    const result = await getQueryStats<number>({
      accessToken,
      connection,
      attribute,
      query,
    })

    expect(result).toEqual(numberStatsResponse)
    expect(result.type).toBe('number')
    expect(result.avg).toBe(50.5)
    expect(result.sum).toBe(1000)
    expect(result.min).toBe(10)
    expect(result.max).toBe(100)

    expect(mockExecuteFetch).toHaveBeenCalledWith({
      accessToken,
      url: `${DEFAULT_CARTO_API_URL}/${DEFAULT_CARTO_API_VERSION}/stats/${connection}/${attribute}`,
      method: 'GET',
      params: { q: query },
    })
  })

  it('should fetch string statistics for a query', async () => {
    mockExecuteFetch.mockResolvedValue(stringStatsResponse)

    const result = await getQueryStats<string>({
      accessToken,
      connection,
      attribute,
      query: 'SELECT name FROM users',
    })

    expect(result).toEqual(stringStatsResponse)
    expect(result.type).toBe('string')
    expect(result.categories).toHaveLength(3)
    expect(result.categories[0]).toEqual({
      category: 'Category A',
      frequency: 100,
    })

    expect(mockExecuteFetch).toHaveBeenCalledWith({
      accessToken,
      url: `${DEFAULT_CARTO_API_URL}/${DEFAULT_CARTO_API_VERSION}/stats/${connection}/${attribute}`,
      method: 'GET',
      params: { q: 'SELECT name FROM users' },
    })
  })

  it('should use default baseUrl and apiVersion', async () => {
    mockExecuteFetch.mockResolvedValue(numberStatsResponse)

    const result = await getQueryStats<number>({
      accessToken,
      connection,
      attribute,
      query,
    })

    expect(result).toEqual(numberStatsResponse)
    expect(mockExecuteFetch).toHaveBeenCalledWith(
      expect.objectContaining({
        url: `${DEFAULT_CARTO_API_URL}/${DEFAULT_CARTO_API_VERSION}/stats/${connection}/${attribute}`,
      }),
    )
  })

  it('should use custom baseUrl when provided', async () => {
    mockExecuteFetch.mockResolvedValue(numberStatsResponse)
    const customBaseUrl = 'https://custom.api.com'

    const result = await getQueryStats<number>({
      accessToken,
      connection,
      attribute,
      query,
      baseUrl: customBaseUrl,
    })

    expect(result).toEqual(numberStatsResponse)
    expect(mockExecuteFetch).toHaveBeenCalledWith(
      expect.objectContaining({
        url: `${customBaseUrl}/${DEFAULT_CARTO_API_VERSION}/stats/${connection}/${attribute}`,
      }),
    )
  })

  it('should use custom apiVersion when provided', async () => {
    mockExecuteFetch.mockResolvedValue(numberStatsResponse)
    const customApiVersion = 'v4'

    const result = await getQueryStats<number>({
      accessToken,
      connection,
      attribute,
      query,
      apiVersion: customApiVersion,
    })

    expect(result).toEqual(numberStatsResponse)
    expect(mockExecuteFetch).toHaveBeenCalledWith(
      expect.objectContaining({
        url: `${DEFAULT_CARTO_API_URL}/${customApiVersion}/stats/${connection}/${attribute}`,
      }),
    )
  })

  it('should pass queryParameters to the request', async () => {
    mockExecuteFetch.mockResolvedValue(numberStatsResponse)

    const result = await getQueryStats<number>({
      accessToken,
      connection,
      attribute,
      query,
      queryParameters: { region: 'US' },
    })

    expect(result).toEqual(numberStatsResponse)
    expect(mockExecuteFetch).toHaveBeenCalledWith(
      expect.objectContaining({
        params: {
          q: query,
          queryParameters: { region: 'US' },
        },
      }),
    )
  })

  it('should pass additional params to the request', async () => {
    mockExecuteFetch.mockResolvedValue(numberStatsResponse)

    const result = await getQueryStats<number>({
      accessToken,
      connection,
      attribute,
      query,
      params: {
        categoriesOrderBy: 'alphabetical_asc',
      },
    })

    expect(result).toEqual(numberStatsResponse)
    expect(mockExecuteFetch).toHaveBeenCalledWith(
      expect.objectContaining({
        params: {
          q: query,
          categoriesOrderBy: 'alphabetical_asc',
        },
      }),
    )
  })

  it('should handle abort signal', async () => {
    const controller = new AbortController()
    mockExecuteFetch.mockRejectedValue(new Error('The operation was aborted'))

    const promise = getQueryStats<number>({
      accessToken,
      connection,
      attribute,
      query,
      fetcherOptions: {
        signal: controller.signal,
      },
    })

    await expect(promise).rejects.toThrow()

    expect(mockExecuteFetch).toHaveBeenCalledWith(
      expect.objectContaining({
        signal: controller.signal,
      }),
    )
  })

  it('should pass additional fetcher options', async () => {
    mockExecuteFetch.mockResolvedValue(numberStatsResponse)

    const result = await getQueryStats<number>({
      accessToken,
      connection,
      attribute,
      query,
      fetcherOptions: {
        headers: {
          'Custom-Header': 'value',
        },
      },
    })

    expect(result).toEqual(numberStatsResponse)
    expect(mockExecuteFetch).toHaveBeenCalledWith(
      expect.objectContaining({
        headers: {
          'Custom-Header': 'value',
        },
      }),
    )
  })
})
