/**
 * OAuth authorize PKCE enforcement — S10-002 regression.
 */
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { Hono } from 'hono'
import { getOAuthClientByClientId, insertAuthorizationCode } from '@zync/db/queries'
import type { AppEnv } from '../src/types'
import type { SessionPayload } from '@zync/types'

vi.mock('@zync/db/queries', () => ({
  createDb: vi.fn(() => ({})),
  getOAuthClientByClientId: vi.fn(),
  insertAuthorizationCode: vi.fn(),
}))

const publicClient = {
  id: 'client-row-id',
  clientId: 'zync-mobile',
  clientSecretHash: '',
  name: 'Zync Mobile',
  redirectUris: ['zync://oauth/callback'],
  scopes: ['read:customers'],
  isFirstParty: true,
  logoUrl: null,
}

const session: SessionPayload = {
  type: 'user',
  sub: 'user-id',
  tid: 'tenant-id',
  role: 'owner',
  tv: 1,
}

async function makeAuthorizeApp() {
  const { authorizeRoute } = await import('../src/routes/oauth/authorize')
  const app = new Hono<AppEnv>()
  app.use('*', async (c, next) => {
    c.set('session', session)
    await next()
  })
  app.route('/oauth', authorizeRoute)
  return app
}

describe('GET /oauth/authorize public client PKCE (S10-002)', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    vi.mocked(getOAuthClientByClientId).mockResolvedValue(publicClient as never)
    vi.mocked(insertAuthorizationCode).mockResolvedValue(undefined)
  })

  it('redirects with invalid_request when public client omits code_challenge', async () => {
    const app = await makeAuthorizeApp()
    const url = new URL('http://localhost/oauth/authorize')
    url.searchParams.set('client_id', publicClient.clientId)
    url.searchParams.set('redirect_uri', publicClient.redirectUris[0]!)
    url.searchParams.set('response_type', 'code')
    url.searchParams.set('scope', 'read:customers')
    url.searchParams.set('state', 'csrf-state')

    const res = await app.request(url.toString())
    expect(res.status).toBe(302)
    const location = res.headers.get('Location')!
    expect(location).toContain('error=invalid_request')
    expect(location).toContain('PKCE')
    expect(insertAuthorizationCode).not.toHaveBeenCalled()
  }, 30_000)

  it('issues code when public client sends S256 PKCE challenge', async () => {
    const app = await makeAuthorizeApp()
    const url = new URL('http://localhost/oauth/authorize')
    url.searchParams.set('client_id', publicClient.clientId)
    url.searchParams.set('redirect_uri', publicClient.redirectUris[0]!)
    url.searchParams.set('response_type', 'code')
    url.searchParams.set('scope', 'read:customers')
    url.searchParams.set('code_challenge', 'E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM')
    url.searchParams.set('code_challenge_method', 'S256')

    const res = await app.request(url.toString(), {}, {
      HYPERDRIVE: {} as AppEnv['Bindings']['HYPERDRIVE'],
      JWT_SECRET: 'test',
    } as AppEnv['Bindings'])

    expect(res.status).toBe(302)
    const location = res.headers.get('Location')!
    expect(location).toContain('code=')
    expect(insertAuthorizationCode).toHaveBeenCalledOnce()
    const inserted = vi.mocked(insertAuthorizationCode).mock.calls[0]![1]
    expect(inserted.code).not.toContain('code=')
    expect(inserted.codeChallenge).toBe('E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM')
  })
})
