/**
 * Credential API schemas tests
 * Tests all validation schemas for credentials endpoints
 * Focus: Security (path traversal, DoS, mass assignment, type coercion)
 */

import { describe, it, expect } from 'vitest'
import {
  CredentialTypeSchema,
  CreateCredentialRequestSchema,
  UpdateCredentialParamsSchema,
  UpdateCredentialRequestSchema,
  DeleteCredentialParamsSchema,
  ListCredentialsResponseSchema
} from '../api-schemas'

describe('CredentialTypeSchema', () => {
  it('accepts valid credential types', () => {
    // These are the actual types stored in the database
    expect(CredentialTypeSchema.parse('oauth')).toBe('oauth')
    expect(CredentialTypeSchema.parse('api-key')).toBe('api-key')
    expect(CredentialTypeSchema.parse('webhook-secret')).toBe('webhook-secret')
  })

  it('rejects invalid credential types', () => {
    expect(() => CredentialTypeSchema.parse('invalid-type')).toThrow()
    expect(() => CredentialTypeSchema.parse('')).toThrow()
  })

  it('rejects provider names (these are CREDENTIAL_SCHEMAS keys, not stored types)', () => {
    // OAuth providers store type='oauth', not their provider name
    expect(() => CredentialTypeSchema.parse('notion')).toThrow()
    expect(() => CredentialTypeSchema.parse('google-sheets')).toThrow()
  })
})

describe('CreateCredentialRequestSchema', () => {
  const validPayload = {
    name: 'gmail-prod',
    type: 'api-key' as const,
    value: { apiKey: 'test-key-123' }
  }

  describe('valid requests', () => {
    it('accepts valid credential creation request', () => {
      const result = CreateCredentialRequestSchema.parse(validPayload)
      expect(result).toEqual(validPayload)
    })

    it('accepts oauth type credentials', () => {
      const payload = {
        name: 'notion-dev',
        type: 'oauth' as const,
        value: { accessToken: 'token', refreshToken: 'refresh' }
      }
      const result = CreateCredentialRequestSchema.parse(payload)
      expect(result).toEqual(payload)
    })

    it('accepts webhook-secret type credentials', () => {
      const payload = {
        name: 'stripe-webhook',
        type: 'webhook-secret' as const,
        value: { signingSecret: 'whsec_abc123' }
      }
      const result = CreateCredentialRequestSchema.parse(payload)
      expect(result).toEqual(payload)
    })
  })

  describe('SECURITY: mass assignment prevention', () => {
    it('rejects unknown fields (strict mode)', () => {
      const payload = {
        ...validPayload,
        organizationId: 'attacker-org-id', // Injected field
        createdBy: null // Override creator
      }
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow()
    })

    it('rejects extra top-level fields', () => {
      const payload = {
        ...validPayload,
        maliciousField: 'value'
      }
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow()
    })
  })

  describe('SECURITY: credential name validation', () => {
    it('rejects invalid credential names', () => {
      const payload = { ...validPayload, name: 'gmail prod' }
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow(/must be lowercase/)
    })

    it('rejects path traversal in name', () => {
      const payload = { ...validPayload, name: '../admin-cred' }
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow(/must be lowercase/)
    })

    it('rejects special characters in name', () => {
      const payload = { ...validPayload, name: 'gmail@prod' }
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow(/must be lowercase/)
    })

    it('rejects names without hyphens', () => {
      const payload = { ...validPayload, name: 'gmailprod' }
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow(/must be lowercase/)
    })

    it('rejects underscores', () => {
      const payload = { ...validPayload, name: 'gmail_prod' }
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow(/must be lowercase/)
    })

    it('auto-lowercases uppercase input', () => {
      const payload = { ...validPayload, name: 'Gmail-Prod' }
      const result = CreateCredentialRequestSchema.parse(payload)
      expect(result.name).toBe('gmail-prod')
    })
  })

  describe('SECURITY: credential type validation', () => {
    it('rejects invalid credential types', () => {
      const payload = { ...validPayload, type: 'invalid-type' }
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow()
    })

    it('rejects null type', () => {
      const payload = { ...validPayload, type: null }
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow()
    })

    it('rejects array as type', () => {
      const payload = { ...validPayload, type: ['api-key'] }
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow()
    })
  })

  describe('SECURITY: credential value validation', () => {
    it('rejects empty credential value', () => {
      const payload = { ...validPayload, value: {} }
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow(/must not be empty/)
    })

    it('rejects non-object value', () => {
      const payload = { ...validPayload, value: 'string-instead-of-object' }
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow()
    })

    it('rejects null value', () => {
      const payload = { ...validPayload, value: null }
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow()
    })

    it('rejects array as value', () => {
      const payload = { ...validPayload, value: ['array', 'as', 'value'] }
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow()
    })
  })

  describe('SECURITY: DoS prevention - credential value size', () => {
    it('rejects credential value with too many keys (over 50)', () => {
      const largeValue = Object.fromEntries(Array.from({ length: 51 }, (_, i) => [`key${i}`, 'value']))
      const payload = { ...validPayload, value: largeValue }
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow(/too many keys/)
    })

    it('accepts credential value with max keys (50)', () => {
      const maxValue = Object.fromEntries(Array.from({ length: 50 }, (_, i) => [`key${i}`, 'value']))
      const payload = { ...validPayload, value: maxValue }
      const result = CreateCredentialRequestSchema.parse(payload)
      expect(Object.keys(result.value).length).toBe(50)
    })

    it('rejects individual string values over 10KB', () => {
      const hugeString = 'a'.repeat(10241)
      const payload = { ...validPayload, value: { apiKey: hugeString } }
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow(/too large/)
    })

    it('accepts string values at max size (10KB)', () => {
      const maxString = 'a'.repeat(10240)
      const payload = { ...validPayload, value: { apiKey: maxString } }
      const result = CreateCredentialRequestSchema.parse(payload)
      expect(result.value.apiKey).toBe(maxString)
    })

    it('allows non-string values without size checks', () => {
      const payload = {
        ...validPayload,
        value: {
          apiKey: 'test',
          number: 12345,
          boolean: true,
          nested: { key: 'value' }
        }
      }
      const result = CreateCredentialRequestSchema.parse(payload)
      expect(result.value).toEqual(payload.value)
    })
  })

  describe('required fields', () => {
    it('rejects missing name', () => {
      const { name: _name, ...payload } = validPayload
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow()
    })

    it('rejects missing type', () => {
      const { type: _type, ...payload } = validPayload
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow()
    })

    it('rejects missing value', () => {
      const { value: _value, ...payload } = validPayload
      expect(() => CreateCredentialRequestSchema.parse(payload)).toThrow()
    })
  })

  describe('provider field (OAuth provider identification)', () => {
    it('accepts OAuth credential with provider', () => {
      const payload = {
        name: 'my-dropbox',
        type: 'oauth' as const,
        value: { accessToken: 'token', refreshToken: 'refresh' },
        provider: 'dropbox'
      }
      const result = CreateCredentialRequestSchema.parse(payload)
      expect(result.provider).toBe('dropbox')
    })

    it('accepts request without provider (optional field)', () => {
      const result = CreateCredentialRequestSchema.parse(validPayload)
      expect(result.provider).toBeUndefined()
    })

    it('accepts various OAuth provider values', () => {
      const providers = ['dropbox', 'notion', 'google-sheets']
      for (const provider of providers) {
        const payload = {
          name: `my-${provider}`,
          type: 'oauth' as const,
          value: { accessToken: 'token' },
          provider
        }
        const result = CreateCredentialRequestSchema.parse(payload)
        expect(result.provider).toBe(provider)
      }
    })
  })
})

describe('UpdateCredentialParamsSchema', () => {
  const validUuid = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'

  it('accepts valid UUID', () => {
    const result = UpdateCredentialParamsSchema.parse({ credentialId: validUuid })
    expect(result.credentialId).toBe(validUuid)
  })

  it('rejects invalid UUID format', () => {
    expect(() => UpdateCredentialParamsSchema.parse({ credentialId: 'not-a-uuid' })).toThrow()
  })

  it('rejects empty string', () => {
    expect(() => UpdateCredentialParamsSchema.parse({ credentialId: '' })).toThrow()
  })

  it('rejects number instead of UUID', () => {
    expect(() => UpdateCredentialParamsSchema.parse({ credentialId: 12345 })).toThrow()
  })
})

describe('UpdateCredentialRequestSchema', () => {
  const validValue = { apiKey: 'updated-key' }
  const validName = 'updated-name'

  describe('valid requests', () => {
    it('accepts update with value only', () => {
      const result = UpdateCredentialRequestSchema.parse({ value: validValue })
      expect(result.value).toEqual(validValue)
      expect(result.name).toBeUndefined()
    })

    it('accepts update with name only', () => {
      const result = UpdateCredentialRequestSchema.parse({ name: validName })
      expect(result.name).toBe(validName)
      expect(result.value).toBeUndefined()
    })

    it('accepts update with both value and name', () => {
      const result = UpdateCredentialRequestSchema.parse({ value: validValue, name: validName })
      expect(result.value).toEqual(validValue)
      expect(result.name).toBe(validName)
    })
  })

  describe('SECURITY: strict mode', () => {
    it('rejects unknown fields', () => {
      const payload = { value: validValue, unknownField: 'test' }
      expect(() => UpdateCredentialRequestSchema.parse(payload)).toThrow()
    })
  })

  describe('validation: at least one field required', () => {
    it('rejects empty update (no fields)', () => {
      expect(() => UpdateCredentialRequestSchema.parse({})).toThrow(/At least one field/)
    })

    it('rejects update with undefined fields only', () => {
      expect(() => UpdateCredentialRequestSchema.parse({ value: undefined, name: undefined })).toThrow(
        /At least one field/
      )
    })
  })

  describe('SECURITY: credential value validation', () => {
    it('rejects empty value object', () => {
      expect(() => UpdateCredentialRequestSchema.parse({ value: {} })).toThrow(/must not be empty/)
    })

    it('rejects value with too many keys', () => {
      const largeValue = Object.fromEntries(Array.from({ length: 51 }, (_, i) => [`key${i}`, 'value']))
      expect(() => UpdateCredentialRequestSchema.parse({ value: largeValue })).toThrow(/too many keys/)
    })

    it('rejects individual string values over 10KB', () => {
      const hugeString = 'a'.repeat(10241)
      expect(() => UpdateCredentialRequestSchema.parse({ value: { apiKey: hugeString } })).toThrow(/too large/)
    })
  })

  describe('SECURITY: credential name validation', () => {
    it('rejects invalid name format', () => {
      expect(() => UpdateCredentialRequestSchema.parse({ name: 'gmail prod' })).toThrow(/must be lowercase/)
    })

    it('rejects path traversal in name', () => {
      expect(() => UpdateCredentialRequestSchema.parse({ name: '../admin' })).toThrow(/must be lowercase/)
    })
  })
})

describe('DeleteCredentialParamsSchema', () => {
  const validUuid = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'

  it('accepts valid UUID', () => {
    const result = DeleteCredentialParamsSchema.parse({ credentialId: validUuid })
    expect(result.credentialId).toBe(validUuid)
  })

  it('rejects invalid UUID format', () => {
    expect(() => DeleteCredentialParamsSchema.parse({ credentialId: 'not-a-uuid' })).toThrow()
  })

  it('rejects empty string', () => {
    expect(() => DeleteCredentialParamsSchema.parse({ credentialId: '' })).toThrow()
  })
})

describe('ListCredentialsResponseSchema - Provider Field', () => {
  const validUuid = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'

  it('validates response with provider set', () => {
    const response = {
      credentials: [
        {
          id: validUuid,
          name: 'dropbox-cred',
          type: 'oauth',
          provider: 'dropbox',
          createdAt: '2026-02-02T00:00:00.000Z'
        }
      ]
    }
    const result = ListCredentialsResponseSchema.parse(response)
    expect(result.credentials[0].provider).toBe('dropbox')
  })

  it('validates response with null provider (non-OAuth credentials)', () => {
    const response = {
      credentials: [
        {
          id: validUuid,
          name: 'api-key',
          type: 'api-key',
          provider: null,
          createdAt: '2026-02-02T00:00:00.000Z'
        }
      ]
    }
    const result = ListCredentialsResponseSchema.parse(response)
    expect(result.credentials[0].provider).toBeNull()
  })

  it('validates response with mixed provider values', () => {
    const response = {
      credentials: [
        {
          id: validUuid,
          name: 'dropbox-cred',
          type: 'oauth',
          provider: 'dropbox',
          createdAt: '2026-02-02T00:00:00.000Z'
        },
        {
          id: 'b0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22',
          name: 'api-key',
          type: 'api-key',
          provider: null,
          createdAt: '2026-02-02T00:00:00.000Z'
        }
      ]
    }
    const result = ListCredentialsResponseSchema.parse(response)
    expect(result.credentials[0].provider).toBe('dropbox')
    expect(result.credentials[1].provider).toBeNull()
  })
})
