// __tests__/auth.test.ts
import {
  Auth,
  Callback,
  clientConfig,
  getClientConfig,
  Logout
} from '@/features/User/oidcAuth'
import * as apiUtils from '@/utils/apiUtils'
import * as client from 'openid-client'

clientConfig.url = 'https://example.com'
const localAdress = 'https://local.exemple.com'

describe('OpenID Client Auth Module', () => {
  beforeEach(() => {
    jest.clearAllMocks()
    jest.spyOn(apiUtils, 'getLocation').mockImplementation(() => localAdress)
  })

  describe('getClientConfig', () => {
    it('should call discovery with clientConfig.url', async () => {
      const discoveryMock = client.discovery as jest.Mock
      discoveryMock.mockResolvedValue('discoveredClient')

      const result = await getClientConfig()

      expect(discoveryMock).toHaveBeenCalledWith(
        new URL(clientConfig.url),
        clientConfig.client_id
      )
      expect(result).toBe('discoveredClient')
    })
  })

  describe('Auth', () => {
    it('should generate PKCE and build authorization URL with PKCE', async () => {
      ;(client.randomPKCECodeVerifier as jest.Mock).mockReturnValue(
        'verifier123'
      )
      ;(client.calculatePKCECodeChallenge as jest.Mock).mockResolvedValue(
        'challenge123'
      )

      // Mock discovery returning an object with serverMetadata()
      const discoveredClient = {
        serverMetadata: jest.fn(() => ({
          supportsPKCE: () => true
        }))
      }
      ;(client.discovery as jest.Mock).mockResolvedValue(discoveredClient)
      ;(client.buildAuthorizationUrl as jest.Mock).mockReturnValue(
        'https://auth.url'
      )

      const result = await Auth()

      expect(client.randomPKCECodeVerifier).toHaveBeenCalled()
      expect(client.calculatePKCECodeChallenge).toHaveBeenCalledWith(
        'verifier123'
      )
      expect(client.buildAuthorizationUrl).toHaveBeenCalledWith(
        discoveredClient,
        expect.objectContaining({
          code_challenge: 'challenge123',
          code_challenge_method: clientConfig.code_challenge_method,
          redirect_uri: clientConfig.redirect_uri,
          scope: clientConfig.scope
        })
      )
      expect(result).toEqual({
        redirectTo: 'https://auth.url',
        code_verifier: 'verifier123',
        state: undefined
      })
    })

    it('should generate state when PKCE not supported', async () => {
      ;(client.randomPKCECodeVerifier as jest.Mock).mockReturnValue(
        'verifier123'
      )
      ;(client.calculatePKCECodeChallenge as jest.Mock).mockResolvedValue(
        'challenge123'
      )

      const discoveredClient = {
        serverMetadata: jest.fn(() => ({
          supportsPKCE: () => false
        }))
      }
      ;(client.discovery as jest.Mock).mockResolvedValue(discoveredClient)
      ;(client.randomState as jest.Mock).mockReturnValue('state123')
      ;(client.buildAuthorizationUrl as jest.Mock).mockReturnValue(
        'https://auth.url'
      )

      const result = await Auth()

      expect(client.randomState).toHaveBeenCalled()
      expect(result.state).toBe('state123')
      expect(result.redirectTo).toBe('https://auth.url')
    })
  })

  describe('Logout', () => {
    it('should build end session URL', async () => {
      const discoveredClient = {}
      ;(client.discovery as jest.Mock).mockResolvedValue(discoveredClient)
      ;(client.buildEndSessionUrl as jest.Mock).mockReturnValue(
        'https://logout.url'
      )

      const result = await Logout()

      expect(client.buildEndSessionUrl).toHaveBeenCalledWith(discoveredClient, {
        post_logout_redirect_uri: clientConfig.post_logout_redirect_uri
      })
      expect(result).toBe('https://logout.url')
    })
  })

  describe('Callback', () => {
    it('should perform authorization code grant and fetch user info', async () => {
      const discoveredClient = {}
      ;(client.discovery as jest.Mock).mockResolvedValue(discoveredClient)

      const mockTokenSet = {
        access_token: 'access123',
        claims: jest.fn(() => ({ sub: 'user123' }))
      }
      ;(client.authorizationCodeGrant as jest.Mock).mockResolvedValue(
        mockTokenSet
      )
      ;(client.fetchUserInfo as jest.Mock).mockResolvedValue({ name: 'User' })
      const result = await Callback('verifier123', 'state123')

      expect(client.authorizationCodeGrant).toHaveBeenCalledWith(
        discoveredClient,
        new URL(localAdress),
        { pkceCodeVerifier: 'verifier123', expectedState: 'state123' }
      )

      expect(client.fetchUserInfo).toHaveBeenCalledWith(
        discoveredClient,
        'access123',
        'user123'
      )

      expect(result).toEqual({
        tokenSet: mockTokenSet,
        userinfo: { name: 'User' }
      })
    })

    it('should catch and log errors', async () => {
      const error = new Error('fail')
      ;(client.discovery as jest.Mock).mockResolvedValue({})
      ;(client.authorizationCodeGrant as jest.Mock).mockRejectedValue(error)
      const consoleErrorSpy = jest
        .spyOn(console, 'error')
        .mockImplementation(() => {})

      const result = await Callback('verifier', 'state')

      expect(consoleErrorSpy).toHaveBeenCalledWith('Token grant error:', error)
      expect(result).toBeUndefined()

      consoleErrorSpy.mockRestore()
    })
  })
})
