import { describe, it, expect, vi, beforeEach } from 'vitest'
import { getTableStats } from './get-table-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 table = 'test-table'
const attribute = 'test-attribute'

const numberStatsResponse: NumberStatsResponse = {
  attribute: 'test-attribute',
  type: 'number',
  avg: 42.8,
  sum: 2140,
  min: 5,
  max: 95,
  quantiles: {
    25: [20],
    50: [42],
    75: [65],
  },
}

const stringStatsResponse: StringStatsResponse = {
  attribute: 'test-attribute',
  type: 'string',
  categories: [
    { category: 'Active', frequency: 150 },
    { category: 'Inactive', frequency: 50 },
  ],
}

const mockExecuteFetch = vi.mocked(services.executeFetch)

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

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

    const result = await getTableStats<number>({
      accessToken,
      connection,
      table,
      attribute,
    })

    expect(result).toEqual(numberStatsResponse)
    expect(result.type).toBe('number')
    expect(result.avg).toBe(42.8)
    expect(result.sum).toBe(2140)
    expect(result.min).toBe(5)
    expect(result.max).toBe(95)

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

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

    const result = await getTableStats<string>({
      accessToken,
      connection,
      table: 'users',
      attribute: 'status',
    })

    expect(result).toEqual(stringStatsResponse)
    expect(result.type).toBe('string')
    expect(result.categories).toHaveLength(2)
    expect(result.categories[0]).toEqual({
      category: 'Active',
      frequency: 150,
    })

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

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

    const result = await getTableStats<number>({
      accessToken,
      connection,
      table,
      attribute,
    })

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

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

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

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

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

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

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

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

    const result = await getTableStats<number>({
      accessToken,
      connection,
      table,
      attribute,
      params: {
        maxNumberOfCategories: 5,
        categoriesOrderBy: 'alphabetical_asc' as const,
      },
    })

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

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

    const promise = getTableStats<number>({
      accessToken,
      connection,
      table,
      attribute,
      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 getTableStats<number>({
      accessToken,
      connection,
      table,
      attribute,
      fetcherOptions: {
        headers: {
          'Custom-Header': 'value',
        },
      },
    })

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

  it('should build correct URL with all parameters', async () => {
    mockExecuteFetch.mockResolvedValue(numberStatsResponse)
    const customConnection = 'prod-connection'
    const customTable = 'products'
    const customAttribute = 'price'

    const result = await getTableStats<number>({
      accessToken,
      connection: customConnection,
      table: customTable,
      attribute: customAttribute,
    })

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