import { safeSql } from '@supabase/pg-meta'
import { describe, expect, it } from 'vitest'

import type { RoleImpersonationState } from './role-impersonation'
import {
  getExp1HourFromNow,
  getPostgrestClaims,
  wrapWithRoleImpersonation,
} from './role-impersonation'

const createBaseUser = (overrides = {}) => ({
  id: 'user123',
  email: 'test@example.com',
  phone: undefined,
  role: 'authenticated',
  is_anonymous: false,
  raw_app_meta_data: { provider: 'email' },
  raw_user_meta_data: { name: 'Tester' },
  ...overrides,
})

const createTestClaims = (overrides = {}) => ({
  ref: 'test-project',
  exp: getExp1HourFromNow(),
  iat: Math.floor(Date.now() / 1000),
  iss: 'https://test-project.supabase.co/auth/v1',
  role: 'authenticated' as const,
  ...overrides,
})

describe('getExp1HourFromNow', () => {
  it('returns a timestamp 1 hour in the future', () => {
    const now = Math.floor(Date.now() / 1000)
    const exp = getExp1HourFromNow()
    expect(exp).toBeGreaterThan(now)
    expect(exp).toBeLessThanOrEqual(now + 3600)
  })
})

describe('getPostgrestClaims', () => {
  describe('native user claims', () => {
    it('returns basic user claims', () => {
      const claims = getPostgrestClaims('test-project', {
        type: 'postgrest',
        role: 'authenticated',
        userType: 'native',
        user: createBaseUser() as any,
      })

      expect(claims.aud).toBe('authenticated')
      expect(claims.email).toBe('test@example.com')
      expect(claims.sub).toBe('user123')
    })

    it('handles missing user data gracefully', () => {
      const claims = getPostgrestClaims('test-project', {
        type: 'postgrest',
        role: 'authenticated',
        userType: 'native',
      } as any)

      expect(claims.role).toBe('authenticated')
      expect(claims.ref).toBe('test-project')
    })
  })

  describe('external user claims', () => {
    it('returns claims with additional data', () => {
      const claims = getPostgrestClaims('test-project', {
        type: 'postgrest',
        role: 'authenticated',
        userType: 'external',
        externalAuth: {
          sub: 'ext123',
          additionalClaims: { foo: 'bar', custom: 'value' },
        },
      })

      expect(claims.sub).toBe('ext123')
      expect((claims as any).foo).toBe('bar')
      expect((claims as any).custom).toBe('value')
    })

    it('handles missing additional claims', () => {
      const claims = getPostgrestClaims('test-project', {
        type: 'postgrest',
        role: 'authenticated',
        userType: 'external',
        externalAuth: {
          sub: 'ext123',
        },
      })

      expect(claims.sub).toBe('ext123')
      expect((claims as any).foo).toBeUndefined()
    })
  })

  describe('system roles', () => {
    it('returns basic claims for anon role', () => {
      const claims = getPostgrestClaims('test-project', {
        type: 'postgrest',
        role: 'anon',
      })

      expect(claims.role).toBe('anon')
      expect(claims.ref).toBe('test-project')
    })

    it('returns basic claims for service_role', () => {
      const claims = getPostgrestClaims('test-project', {
        type: 'postgrest',
        role: 'service_role',
      })

      expect(claims.role).toBe('service_role')
      expect(claims.ref).toBe('test-project')
    })
  })
})

describe('wrapWithRoleImpersonation', () => {
  const sql = safeSql`select * from colors;`
  const ref = 'default'

  describe('postgres role (undefined)', () => {
    it('returns SQL as is when no role is selected', () => {
      const roleImpersonationState: RoleImpersonationState = {
        role: undefined,
        claims: undefined,
      }
      const result = wrapWithRoleImpersonation(sql, roleImpersonationState)
      expect(result).toBe(sql)
    })
  })

  describe('anon role', () => {
    it('wraps SQL with anon user configuration', () => {
      const claims = createTestClaims({
        iss: 'briven',
        ref,
        role: 'anon' as const,
      })

      const roleImpersonationState: RoleImpersonationState = {
        role: { type: 'postgrest', role: 'anon' },
        claims,
      }
      const result = wrapWithRoleImpersonation(sql, roleImpersonationState)

      expect(result).toContain("set_config('role', 'anon', true)")
      expect(result).toContain('request.jwt.claims')
      expect(result).toContain('ROLE_IMPERSONATION_NO_RESULTS')
      expect(result).toContain(sql)
    })
  })

  describe('authenticated user', () => {
    it('wraps SQL with native user configuration', () => {
      const claims = createTestClaims({
        iss: `https://${ref}.supabase.co/auth/v1`,
        role: 'authenticated' as const,
      })

      const roleImpersonationState: RoleImpersonationState = {
        role: {
          type: 'postgrest',
          role: 'authenticated',
          aal: 'aal1',
          userType: 'native',
          user: {
            email: 'test@email.com',
            id: 'abc',
            providers: [],
          },
        },
        claims,
      }
      const result = wrapWithRoleImpersonation(sql, roleImpersonationState)

      expect(result).toContain("set_config('role', 'authenticated', true)")
      expect(result).toContain('request.jwt.claims')
      expect(result).toContain('ROLE_IMPERSONATION_NO_RESULTS')
      expect(result).toContain(sql)
    })

    it('wraps SQL with external user configuration', () => {
      const claims = createTestClaims({
        aal: 'aal1' as const,
        aud: 'authenticated',
        role: 'authenticated' as const,
        session_id: 'ecab6bfd-3707-4e63-9b3b-d37af69449d9',
        sub: 'user123',
      })

      const roleImpersonationState: RoleImpersonationState = {
        role: {
          type: 'postgrest',
          role: 'authenticated',
          userType: 'external',
          externalAuth: {
            sub: 'user123',
            additionalClaims: {},
          },
          aal: 'aal1',
        },
        claims,
      }
      const result = wrapWithRoleImpersonation(sql, roleImpersonationState)

      expect(result).toContain("set_config('role', 'authenticated', true)")
      expect(result).toContain('request.jwt.claims')
      expect(result).toContain('ROLE_IMPERSONATION_NO_RESULTS')
      expect(result).toContain(sql)
    })
  })

  describe('custom role', () => {
    it('wraps SQL with custom role configuration', () => {
      const customRole = 'test'
      const roleImpersonationState: RoleImpersonationState = {
        role: { type: 'custom', role: customRole },
        claims: undefined,
      }
      const result = wrapWithRoleImpersonation(sql, roleImpersonationState)

      expect(result).toContain(`set local role '${customRole}'`)
      expect(result).toContain('ROLE_IMPERSONATION_NO_RESULTS')
      expect(result).toContain(sql)
    })
  })
})
