import { describe, test, expect } from "bun:test";
import { generateSecureValue } from "../../src/modules/credentials/generateSecureValue";
import { readSecureValue } from "../../src/modules/credentials/readSecureValue";

/**
 * Tests for the secure value encryption/decryption round-trip.
 *
 * The test setup.ts mocks the constants module with a matching RSA key pair,
 * so generateSecureValue (uses public key) and readSecureValue (uses private key)
 * will work correctly together.
 */

describe("generateSecureValue", () => {
  test("returns an object with encrypted and hash fields", () => {
    const result = generateSecureValue("my-secret-password");
    expect(result).toHaveProperty("encrypted");
    expect(result).toHaveProperty("hash");
    expect(typeof result.encrypted).toBe("string");
    expect(typeof result.hash).toBe("string");
  });

  test("encrypted is a valid base64 string", () => {
    const result = generateSecureValue("test-value");
    expect(() => Buffer.from(result.encrypted, "base64")).not.toThrow();
    const decoded = Buffer.from(result.encrypted, "base64");
    expect(decoded.length).toBeGreaterThan(0);
  });

  test("hash follows BLAKE2s format", () => {
    const result = generateSecureValue("test-value");
    expect(result.hash).toMatch(/^BLAKE2s\$/);
    const parts = result.hash.split("$");
    expect(parts[0]).toBe("BLAKE2s");
    expect(parts[1].length).toBeGreaterThan(0); // hex hash
  });

  test("different inputs produce different encrypted values", () => {
    const a = generateSecureValue("password-a");
    const b = generateSecureValue("password-b");
    expect(a.encrypted).not.toBe(b.encrypted);
  });

  test("different inputs produce different hashes", () => {
    const a = generateSecureValue("password-a");
    const b = generateSecureValue("password-b");
    expect(a.hash).not.toBe(b.hash);
  });

  test("encrypts empty string without error", () => {
    const result = generateSecureValue("");
    expect(result.encrypted.length).toBeGreaterThan(0);
    expect(result.hash.length).toBeGreaterThan(0);
  });

  test("encrypts special characters", () => {
    const result = generateSecureValue('p@$$w0rd!#%^&*(){}[]|\\:";<>?,./~`');
    expect(result.encrypted.length).toBeGreaterThan(0);
  });

  test("encrypts Unicode content", () => {
    const result = generateSecureValue("密码测试 🔐");
    expect(result.encrypted.length).toBeGreaterThan(0);
  });
});

describe("readSecureValue", () => {
  test("decrypts a value encrypted by generateSecureValue", () => {
    const original = "my-secret-password";
    const { encrypted } = generateSecureValue(original);
    const decrypted = readSecureValue(encrypted);
    expect(decrypted).toBe(original);
  });

  test("decrypts empty string", () => {
    const { encrypted } = generateSecureValue("");
    const decrypted = readSecureValue(encrypted);
    expect(decrypted).toBe("");
  });

  test("decrypts special characters", () => {
    const original = 'p@$$w0rd!#%^&*(){}[]|\\:";<>?,./~`';
    const { encrypted } = generateSecureValue(original);
    const decrypted = readSecureValue(encrypted);
    expect(decrypted).toBe(original);
  });

  test("decrypts Unicode content", () => {
    const original = "密码测试 🔐";
    const { encrypted } = generateSecureValue(original);
    const decrypted = readSecureValue(encrypted);
    expect(decrypted).toBe(original);
  });

  test("validates hash when provided and correct", () => {
    const original = "test-value";
    const { encrypted, hash } = generateSecureValue(original);
    const decrypted = readSecureValue(encrypted, hash);
    expect(decrypted).toBe(original);
  });

  test("throws when hash validation fails", () => {
    const original = "test-value";
    const { encrypted } = generateSecureValue(original);
    const badHash =
      "BLAKE2s$0000000000000000000000000000000000000000000000000000000000000000$salt";
    expect(() => readSecureValue(encrypted, badHash)).toThrow(
      "Secure value hash validation failed",
    );
  });

  test("throws GenericError on invalid encrypted content", () => {
    try {
      readSecureValue("not-valid-encrypted-data");
      expect(true).toBe(false); // should not reach
    } catch (e: any) {
      expect(e.name).toBe("SecureValueDecryptionError");
      expect(e.status).toBe(422);
    }
  });

  test("throws GenericError with descriptive message on key mismatch", () => {
    try {
      readSecureValue("dGhpcyBpcyBub3QgZW5jcnlwdGVk"); // base64 but not RSA
      expect(true).toBe(false);
    } catch (e: any) {
      expect(e.message).toContain("Unable to decrypt secure value");
      expect(e.cause).toContain("RSA key mismatch");
    }
  });

  test("skips hash validation when hash is not provided", () => {
    const original = "no-hash-check";
    const { encrypted } = generateSecureValue(original);
    // Should not throw even though no hash is passed
    const decrypted = readSecureValue(encrypted);
    expect(decrypted).toBe(original);
  });
});

describe("generateSecureValue + readSecureValue round-trip", () => {
  test("round-trips various string types", () => {
    const testValues = [
      "simple",
      "",
      "a".repeat(100),
      "line1\nline2\ttab",
      '{"json": true}',
      "null",
      "undefined",
      "0",
      " leading-trailing ",
    ];

    for (const original of testValues) {
      const { encrypted, hash } = generateSecureValue(original);
      const decrypted = readSecureValue(encrypted, hash);
      expect(decrypted).toBe(original);
    }
  });
});
