import { describe, it, expect } from 'vitest'
import {
  constantTimeCompare,
  validateRedirectPath,
  createOidcParams,
  getCodeChallenge,
  getOidcRedirectUri,
  getOidcConfig,
  buildAuthorizeUrl,
  exchangeCode,
} from '../../server/utils/oidc.js'
import { withTemporaryEnv } from '../helpers/env.js'

describe('oidc', () => {
  describe('constantTimeCompare', () => {
    it.each([
      [['abc', 'abc'], true],
      [['abc', 'abd'], false],
      [['ab', 'abc'], false],
      [['a', 1], false],
    ])('compares %j -> %s', ([a, b], expected) => {
      expect(constantTimeCompare(a, b)).toBe(expected)
    })
  })

  describe('validateRedirectPath', () => {
    it.each([
      ['/', '/'],
      ['/feeds', '/feeds'],
      ['/feeds?foo=1', '/feeds?foo=1'],
      ['//evil.com', '/'],
      ['', '/'],
      [null, '/'],
      ['/foo//bar', '/'],
    ])('validates %s -> %s', (input, expected) => {
      expect(validateRedirectPath(input)).toBe(expected)
    })
  })

  describe('createOidcParams', () => {
    it('returns state, nonce, and codeVerifier', () => {
      const params = createOidcParams()
      expect(params).toMatchObject({
        state: expect.any(String),
        nonce: expect.any(String),
        codeVerifier: expect.any(String),
      })
    })
  })

  describe('getCodeChallenge', () => {
    it('returns a string for a verifier', async () => {
      const { codeVerifier } = createOidcParams()
      const challenge = await getCodeChallenge(codeVerifier)
      expect(challenge).toMatch(/^[\w-]+$/)
    })
  })

  describe('getOidcRedirectUri', () => {
    it('returns URL ending with callback path when env is default', () => {
      withTemporaryEnv(
        {
          OIDC_REDIRECT_URI: undefined,
          OPENID_REDIRECT_URI: undefined,
          NUXT_APP_URL: undefined,
          APP_URL: undefined,
        },
        () => {
          expect(getOidcRedirectUri()).toMatch(/\/api\/auth\/oidc\/callback$/)
        },
      )
    })

    it.each([
      [{ OIDC_REDIRECT_URI: '  https://app.example.com/oidc/cb  ' }, 'https://app.example.com/oidc/cb'],
      [
        { OIDC_REDIRECT_URI: undefined, OPENID_REDIRECT_URI: undefined, NUXT_APP_URL: 'https://myapp.example.com/' },
        'https://myapp.example.com/api/auth/oidc/callback',
      ],
      [
        {
          OIDC_REDIRECT_URI: undefined,
          OPENID_REDIRECT_URI: undefined,
          NUXT_APP_URL: undefined,
          APP_URL: 'https://app.example.com',
        },
        'https://app.example.com/api/auth/oidc/callback',
      ],
    ])('returns correct URI for env: %j', (env, expected) => {
      withTemporaryEnv(env, () => {
        expect(getOidcRedirectUri()).toBe(expected)
      })
    })
  })

  describe('getOidcConfig', () => {
    it.each([
      [{ OIDC_ISSUER: undefined, OIDC_CLIENT_ID: undefined, OIDC_CLIENT_SECRET: undefined }],
      [{ OIDC_ISSUER: 'https://idp.example.com', OIDC_CLIENT_ID: 'client', OIDC_CLIENT_SECRET: undefined }],
    ])('returns null when OIDC vars missing or incomplete: %j', async (env) => {
      withTemporaryEnv(env, async () => {
        expect(await getOidcConfig()).toBeNull()
      })
    })
  })

  describe('buildAuthorizeUrl', () => {
    it('is a function that accepts config and params', () => {
      expect(buildAuthorizeUrl).toBeInstanceOf(Function)
      expect(buildAuthorizeUrl.length).toBe(2)
    })

    it('calls oidc.buildAuthorizationUrl with valid config', async () => {
      withTemporaryEnv(
        {
          OIDC_ISSUER: 'https://accounts.google.com',
          OIDC_CLIENT_ID: 'test-client',
          OIDC_CLIENT_SECRET: 'test-secret',
        },
        async () => {
          try {
            const config = await getOidcConfig()
            if (config) {
              const result = buildAuthorizeUrl(config, createOidcParams())
              expect(result).toBeDefined()
            }
          }
          catch {
            // Discovery failures are acceptable
          }
        },
      )
    })
  })

  describe('getOidcConfig caching', () => {
    it('caches config when called multiple times with same issuer', async () => {
      withTemporaryEnv(
        {
          OIDC_ISSUER: 'https://accounts.google.com',
          OIDC_CLIENT_ID: 'test-client',
          OIDC_CLIENT_SECRET: 'test-secret',
        },
        async () => {
          try {
            const config1 = await getOidcConfig()
            if (config1) {
              const config2 = await getOidcConfig()
              expect(config2).toBeDefined()
            }
          }
          catch {
            // Network/discovery failures are acceptable
          }
        },
      )
    })
  })

  describe('exchangeCode', () => {
    it('rejects when grant fails', async () => {
      await expect(
        exchangeCode({}, 'https://app/api/auth/oidc/callback?code=abc&state=s', {
          state: 's',
          nonce: 'n',
          codeVerifier: 'v',
        }),
      ).rejects.toBeDefined()
    })
  })
})
