import { describe, it, expect } from 'vitest'
import { eqOrgId, sanitizePostgrestValue } from '../helpers'
import { brandSupabaseOrgId, brandWorkOsOrgId } from '../../auth/multi-tenancy/org-id'

describe('sanitizePostgrestValue', () => {
  it('passes through normal search strings unchanged', () => {
    expect(sanitizePostgrestValue('hello')).toBe('hello')
    expect(sanitizePostgrestValue('John Doe')).toBe('John Doe')
    expect(sanitizePostgrestValue('test@example.com')).toBe('test@example.com')
  })

  it('strips commas that would split .or() conditions', () => {
    expect(sanitizePostgrestValue('foo,bar')).toBe('foobar')
    expect(sanitizePostgrestValue('a,b,c')).toBe('abc')
  })

  it('strips parentheses that would create logical grouping', () => {
    expect(sanitizePostgrestValue('foo(bar)')).toBe('foobar')
    expect(sanitizePostgrestValue('(test)')).toBe('test')
  })

  it('strips all special chars together', () => {
    expect(sanitizePostgrestValue('test,name.eq.admin()')).toBe('testname.eq.admin')
  })

  it('preserves dots (safe in ilike values)', () => {
    expect(sanitizePostgrestValue('example.com')).toBe('example.com')
    expect(sanitizePostgrestValue('v1.2.3')).toBe('v1.2.3')
  })

  it('preserves percent and underscore (SQL wildcards used intentionally)', () => {
    expect(sanitizePostgrestValue('100%')).toBe('100%')
    expect(sanitizePostgrestValue('test_value')).toBe('test_value')
  })

  it('handles empty string', () => {
    expect(sanitizePostgrestValue('')).toBe('')
  })

  it('prevents the specific attack vector: injecting extra filter conditions', () => {
    // Without sanitization, this would produce:
    //   name.ilike.%test,name.eq.secret%,domain.ilike.%test,name.eq.secret%
    // PostgREST would parse "name.eq.secret%" as an additional filter condition
    const malicious = 'test,name.eq.secret'
    const sanitized = sanitizePostgrestValue(malicious)
    const filter = `name.ilike.%${sanitized}%,domain.ilike.%${sanitized}%`

    // Should produce exactly 2 conditions (one comma separator)
    const topLevelCommas = filter.split(',').length - 1
    expect(topLevelCommas).toBe(1)
  })
})

/** Minimal stand-in for a PostgREST filter builder: records `.eq` calls, returns itself for chaining. */
function createMockQuery() {
  const calls: Array<[string, string]> = []
  const query = {
    calls,
    eq(column: 'organization_id', value: string) {
      calls.push([column, value])
      return query
    }
  }
  return query
}

describe('eqOrgId', () => {
  it('applies an organization_id equality filter and returns the same builder for chaining', () => {
    const query = createMockQuery()
    const id = brandSupabaseOrgId('2ac228f7-a0a1-49f9-920e-8a348f5253d3')

    const result = eqOrgId(query, id)

    expect(result).toBe(query)
    expect(query.calls).toEqual([['organization_id', id]])
  })

  it('rejects the dual-org-ID swap at compile time', () => {
    // Compile-time guard. The standing CI gate lives at the Supabase-direct call sites in
    // command-center (which `check-types`), since @repo/core excludes test files from check-types
    // (see memory: reference_package_test_typecheck). These assertions document + dev-hook-enforce it.
    const query = createMockQuery()
    const workOsId = brandWorkOsOrgId('org_01K64D59CA5J3PJCWJFQV87PPN')

    // @ts-expect-error a WorkOsOrgId must not be accepted where a SupabaseOrgId is required
    eqOrgId(query, workOsId)
    // @ts-expect-error a raw string must not be accepted either
    eqOrgId(query, 'plain-string')

    expect(query.calls).toHaveLength(2)
  })
})
