import { describe, expect, test } from "bun:test"
import { randomBytes } from "node:crypto"

import { CredentialEncryptor } from "./crypto.ts"

const TEST_KEY = randomBytes(32).toString("base64")

describe("CredentialEncryptor", () => {
	const encryptor = new CredentialEncryptor(TEST_KEY)

	test("round-trip with simple string", () => {
		const plaintext = "hello world"
		const encrypted = encryptor.encrypt(plaintext)
		expect(encryptor.decrypt(encrypted)).toBe(plaintext)
	})

	test("round-trip with JSON credentials", () => {
		const credentials = JSON.stringify({
			accessToken: "ya29.a0AfH6SMB...",
			refreshToken: "1//0dx...",
			expiresAt: "2025-12-01T00:00:00Z",
		})
		const encrypted = encryptor.encrypt(credentials)
		expect(encryptor.decrypt(encrypted)).toBe(credentials)
	})

	test("round-trip with empty string", () => {
		const encrypted = encryptor.encrypt("")
		expect(encryptor.decrypt(encrypted)).toBe("")
	})

	test("round-trip with unicode", () => {
		const plaintext = "日本語テスト 🔐"
		const encrypted = encryptor.encrypt(plaintext)
		expect(encryptor.decrypt(encrypted)).toBe(plaintext)
	})

	test("each encryption produces different ciphertext (unique IV)", () => {
		const plaintext = "same input"
		const a = encryptor.encrypt(plaintext)
		const b = encryptor.encrypt(plaintext)
		expect(a).not.toEqual(b)
		expect(encryptor.decrypt(a)).toBe(plaintext)
		expect(encryptor.decrypt(b)).toBe(plaintext)
	})

	test("tampered ciphertext throws", () => {
		const encrypted = encryptor.encrypt("secret")
		encrypted[13]! ^= 0xff
		expect(() => encryptor.decrypt(encrypted)).toThrow()
	})

	test("truncated data throws", () => {
		expect(() => encryptor.decrypt(Buffer.alloc(10))).toThrow("Encrypted data is too short")
	})

	test("throws when key is wrong length", () => {
		expect(() => new CredentialEncryptor(Buffer.from("too-short").toString("base64"))).toThrow(
			"must be 32 bytes",
		)
	})
})
