import { describe, expect, test } from 'vitest';

import {
  computeBindingKey,
  computeDesiredSpecHash,
  computeRequirementKey,
  containsCredentialMaterial,
  isSecretKey,
  isTerminalLifecycleState,
  redactLifecycleSecrets
} from './index.js';

describe('database lifecycle contracts', () => {
  test('computes stable requirement, binding, and desired spec identifiers', async () => {
    const requirement = {
      logicalName: 'Orders DB',
      engine: 'postgres',
      version: '16',
      sizing: {
        storageGb: 20,
        tier: 'shared'
      },
      extensions: ['pgcrypto', 'uuid-ossp']
    } as const;

    expect(computeRequirementKey(requirement)).toBe('orders-db~Orders%20DB:postgres');
    expect(computeRequirementKey({ logicalName: 'orders/db', engine: 'postgres' })).toBe('orders-db~orders%2Fdb:postgres');
    expect(computeRequirementKey({ logicalName: 'orders-db', engine: 'postgres' })).toBe('orders-db~orders-db:postgres');
    expect(
      computeBindingKey({
        organizationId: 'org-1',
        applicationId: 'app-1',
        environmentClass: 'dev',
        environmentName: 'branch/alice',
        requirementKey: 'orders-db~Orders%20DB:postgres'
      })
    ).toBe('org-1:app-1:dev:branch-alice~branch%2Falice:orders-db~Orders%20DB:postgres');
    expect(
      computeBindingKey({
        organizationId: 'org-1',
        applicationId: 'app-1',
        environmentClass: 'dev',
        environmentName: 'branch-alice',
        requirementKey: 'orders-db~Orders%20DB:postgres'
      })
    ).toBe('org-1:app-1:dev:branch-alice~branch-alice:orders-db~Orders%20DB:postgres');

    await expect(computeDesiredSpecHash(requirement)).resolves.toBe(
      await computeDesiredSpecHash({ ...requirement, extensions: ['uuid-ossp', 'pgcrypto'] })
    );
    await expect(computeDesiredSpecHash(requirement)).resolves.toBe('QsCl6RVzJVEwf4Nuk0D27rRtycYe2vQZMxcXLt9WUkQ=');
  });

  test('hashes desired specs with locale-invariant ordering', async () => {
    const first = await computeDesiredSpecHash({
      logicalName: 'Éclair DB',
      engine: 'postgres',
      extensions: ['ä', 'z', 'a']
    });
    const second = await computeDesiredSpecHash({
      logicalName: 'Éclair DB',
      engine: 'postgres',
      extensions: ['z', 'a', 'ä']
    });

    expect(first).toBe(second);
  });

  test('redacts raw credential material while preserving credential refs', () => {
    const redacted = redactLifecycleSecrets({
      host: 'db.internal',
      password: 'raw-password',
      nested: {
        token: 'raw-token',
        credentialRef: {
          resolver: 'aws_secrets_manager',
          ref: 'arn:aws:secretsmanager:us-east-1:123:secret:orders'
        }
      },
      credential_refs: {
        password: {
          resolver: 'vault',
          ref: 'database/orders',
          field: 'password'
        }
      }
    });

    expect(redacted).toEqual({
      host: 'db.internal',
      password: '[REDACTED]',
      nested: {
        token: '[REDACTED]',
        credentialRef: {
          resolver: 'aws_secrets_manager',
          ref: 'arn:aws:secretsmanager:us-east-1:123:secret:orders'
        }
      },
      credential_refs: {
        password: {
          resolver: 'vault',
          ref: 'database/orders',
          field: 'password'
        }
      }
    });
  });

  test('recognizes terminal lifecycle states', () => {
    expect(isTerminalLifecycleState('ready')).toBe(true);
    expect(isTerminalLifecycleState('failed')).toBe(true);
    expect(isTerminalLifecycleState('provisioning')).toBe(false);
  });

  test('flags secret-shaped keys', () => {
    for (const key of ['password', 'PASSWORD', 'api_token', 'private_key', 'private-key', 'dsn']) {
      expect(isSecretKey(key)).toBe(true);
    }
    for (const key of ['host', 'port', 'database', 'username']) {
      expect(isSecretKey(key)).toBe(false);
    }
  });

  test('flags secret-shaped value content', () => {
    expect(containsCredentialMaterial('postgres://user:pass@db.internal:5432/orders')).toBe(true);
    expect(containsCredentialMaterial('mongodb+srv://app:secret@cluster0.example.net/db')).toBe(true);
    expect(containsCredentialMaterial('password=hunter2;host=db.internal')).toBe(true);
    expect(containsCredentialMaterial('{"password":"hunter2"}')).toBe(true);
    expect(containsCredentialMaterial('host=db.internal port=5432')).toBe(false);
    expect(containsCredentialMaterial('arn:aws:secretsmanager:us-east-1:123:secret:orders')).toBe(false);
  });
});
