/**
 * (interceptors: auth headers, 401/retry, permission-denied, response shape).
 */
jest.mock('jsdom/lib/jsdom/browser/not-implemented.js', () => {
  return function notImplemented() {
    // no-op in tests; prevents noisy jsdom "navigation not implemented" logs
  }
})

import {
  AUTH_INFO_KEY,
  ERROR_CODES,
  LOCALE_SELECTED_KEY,
} from '@/constants/system'

jest.mock('@/util/token-refresh-queue', () => ({
  refreshTokenAndRetry: jest.fn(),
}))

import axiosClient from '@/libs/axiosClient'

const mockGetItem = jest.fn()
const mockSetItem = jest.fn()
const mockRemoveItem = jest.fn()

beforeAll(() => {
  Object.defineProperty(window, 'localStorage', {
    value: {
      getItem: mockGetItem,
      setItem: mockSetItem,
      removeItem: mockRemoveItem,
      clear: jest.fn(),
    },
    writable: true,
  })
})

describe('[libs/axiosClient]', () => {
  beforeEach(() => {
    mockGetItem.mockClear()
    mockSetItem.mockClear()
    mockRemoveItem.mockClear()
  })

  describe('client configuration', () => {
    it('registers request and response interceptors', () => {
      expect(axiosClient.interceptors.request.handlers).toBeDefined()
      expect(axiosClient.interceptors.request.handlers.length).toBeGreaterThan(0)
      expect(axiosClient.interceptors.response.handlers).toBeDefined()
      expect(axiosClient.interceptors.response.handlers.length).toBeGreaterThan(0)
    })

    it('sets a baseURL', () => {
      expect(axiosClient.defaults.baseURL).toBeDefined()
    })
  })

  describe('request interceptor — fulfilled', () => {
    it('adds Accept-Language when locale exists', () => {
      mockGetItem.mockImplementation((key: string) => {
        if (key === LOCALE_SELECTED_KEY) return 'vi-VN'
        return null
      })
      const config = {
        headers: {} as Record<string, string>,
        url: '/some-api',
      }
      const out = axiosClient.interceptors.request.handlers[0].fulfilled?.(
        config as any,
      )
      expect(out?.headers?.Authorization).toBeUndefined()
      expect(out?.headers?.['Accept-Language']).toBe('vi-VN')
    })

    it('leaves headers empty when neither token nor locale', () => {
      mockGetItem.mockReturnValue(null)
      const config = {
        headers: {} as Record<string, string>,
        url: '/some-api',
      }
      const out = axiosClient.interceptors.request.handlers[0].fulfilled?.(
        config as any,
      )
      expect(out?.headers).toEqual({})
    })

    it('adds Accept-Language when only locale is stored', () => {
      mockGetItem.mockImplementation((key: string) => {
        if (key === LOCALE_SELECTED_KEY) return 'en-US'
        return null
      })
      const config = { headers: {} as Record<string, string>, url: '/api' }
      const out = axiosClient.interceptors.request.handlers[0].fulfilled?.(
        config as any,
      )
      expect(out?.headers?.['Accept-Language']).toBe('en-US')
    })
  })

  describe('request interceptor — rejected', () => {
    it('rejects with the original error', async () => {
      const err = new Error('req err')
      await expect(
        axiosClient.interceptors.request.handlers[0].rejected?.(err),
      ).rejects.toThrow('req err')
    })
  })

  describe('response interceptor — fulfilled', () => {
    it('unwraps response.data', () => {
      const handler = axiosClient.interceptors.response.handlers[0]
      const res = { data: { foo: 1 }, status: 200 }
      const out = handler.fulfilled?.(res as any)
      expect(out).toEqual({ foo: 1 })
    })
  })

  describe('response interceptor — rejected', () => {
    it('rejects passthrough for non-retryable errors', async () => {
      const handler = axiosClient.interceptors.response.handlers[0]
      const { refreshTokenAndRetry } = require('@/util/token-refresh-queue')
      const nonRetryError = {
        config: { headers: {}, url: '/retry', _retry: true },
        response: { status: 401, data: { code: 'OTHER' } },
      }

      await expect(
        handler.rejected?.(nonRetryError as any),
      ).rejects.toBe(nonRetryError)
      expect(refreshTokenAndRetry).not.toHaveBeenCalled()

      const not401Error = {
        config: { headers: {}, url: '/retry' },
        response: { status: 500, data: { code: 'OTHER' } },
      }
      await expect(
        handler.rejected?.(not401Error as any),
      ).rejects.toBe(not401Error)
    })

    it('clears session and does not refresh on PERMISSION_DENIED', async () => {
      const handler = axiosClient.interceptors.response.handlers[0]
      const { refreshTokenAndRetry } = require('@/util/token-refresh-queue')

      mockRemoveItem.mockClear()
      refreshTokenAndRetry.mockClear()

      const permissionDeniedError = {
        config: { headers: {}, url: '/protected' },
        response: {
          status: 401,
          data: { code: ERROR_CODES['PERMISSION_DENIED'] },
        },
      }

      const originalConsoleError = console.error
      ;(console as any).error = jest.fn()
      try {
        await expect(
          handler.rejected?.(permissionDeniedError as any),
        ).rejects.toBe(permissionDeniedError)
      } finally {
        console.error = originalConsoleError
      }
      // Token nằm trong HttpOnly cookie do BE quản lý — FE chỉ xoá AUTH_INFO_KEY
      // và KHÔNG được phép remove TOKEN_KEY (constant đã bị xoá khỏi system.ts).
      expect(mockRemoveItem).toHaveBeenCalledWith(AUTH_INFO_KEY)
      expect(mockRemoveItem).toHaveBeenCalledTimes(1)
      expect(refreshTokenAndRetry).not.toHaveBeenCalled()
    })

    it('retries via refreshTokenAndRetry and returns new data on success', async () => {
      const handler = axiosClient.interceptors.response.handlers[0]
      const { refreshTokenAndRetry } = require('@/util/token-refresh-queue')

      const refreshResult = { url: '/protected', headers: {} }
      refreshTokenAndRetry.mockResolvedValue(refreshResult)

      ;(axiosClient.defaults as any).adapter = () =>
        Promise.resolve({
          data: { ok: true },
          status: 200,
          statusText: 'OK',
          headers: {},
          config: {},
          request: {},
        })

      const retryError = {
        config: { headers: {}, url: '/protected' },
        response: {
          status: 401,
          data: { code: ERROR_CODES['INVALID_AUTH_TOKEN'] },
        },
      }

      const out = await handler.rejected?.(retryError as any)
      expect(refreshTokenAndRetry).toHaveBeenCalled()
      expect(out).toEqual({ ok: true })
    })

    it('propagates error when refreshTokenAndRetry fails', async () => {
      const handler = axiosClient.interceptors.response.handlers[0]
      const { refreshTokenAndRetry } = require('@/util/token-refresh-queue')

      refreshTokenAndRetry.mockRejectedValue('refresh-fail')

      const retryError = {
        config: { headers: {}, url: '/protected' },
        response: {
          status: 401,
          data: { code: ERROR_CODES['INVALID_AUTH_TOKEN'] },
        },
      }

      await expect(
        handler.rejected?.(retryError as any),
      ).rejects.toThrow('refresh-fail')
    })

    it('rejects with original error from fulfilled rejection path', async () => {
      const handler = axiosClient.interceptors.response.handlers[0]
      const err = new Error('fail')
      await expect(handler.rejected?.(err)).rejects.toThrow('fail')
    })
  })
})
