/**
 * Vault Secret Resolver Tests
 *
 * Tests URI parsing, auth chain, caching, and fallback behavior.
 */
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
	clearVaultCache,
	getVaultCacheStats,
	isVaultUri,
	parseVaultUri,
	resolveSecret,
} from "../vault-resolver.js";

describe("Vault Secret Resolver", () => {
	beforeEach(() => {
		clearVaultCache();
		// Clear mocked env vars
		delete process.env.VAULT_ADDR;
		delete process.env.VAULT_TOKEN;
	});

	afterEach(() => {
		clearVaultCache();
	});

	describe("parseVaultUri", () => {
		it("parses valid vault:// URIs", () => {
			const result = parseVaultUri("vault://secret/anthropic/prod#api_key");
			expect(result).toHaveProperty("path", "secret/anthropic/prod");
			expect(result).toHaveProperty("field", "api_key");
			expect(result).toHaveProperty("vaultAddr");
		});

		it("returns error for missing scheme", () => {
			const result = parseVaultUri("secret/anthropic/prod#api_key");
			expect(result).toHaveProperty("error");
			expect(result.error).toContain("vault://");
		});

		it("returns error for missing fragment", () => {
			const result = parseVaultUri("vault://secret/anthropic/prod");
			expect(result).toHaveProperty("error");
			expect(result.error).toContain("format");
		});

		it("returns error for non-string input", () => {
			const result = parseVaultUri(null);
			expect(result).toHaveProperty("error");
		});

		it("uses VAULT_ADDR environment variable", () => {
			process.env.VAULT_ADDR = "https://vault.example.com";
			const result = parseVaultUri("vault://secret/path#field");
			expect(result.vaultAddr).toBe("https://vault.example.com");
		});

		it("defaults to localhost:8200 if VAULT_ADDR not set", () => {
			const result = parseVaultUri("vault://secret/path#field");
			expect(result.vaultAddr).toContain("8200");
		});
	});

	describe("isVaultUri", () => {
		it("returns true for vault:// URIs", () => {
			expect(isVaultUri("vault://secret/path#field")).toBe(true);
		});

		it("returns false for plaintext values", () => {
			expect(isVaultUri("my-secret-key")).toBe(false);
			expect(isVaultUri("https://example.com")).toBe(false);
		});

		it("returns false for non-strings", () => {
			expect(isVaultUri(null)).toBe(false);
			expect(isVaultUri(undefined)).toBe(false);
			expect(isVaultUri(123)).toBe(false);
		});
	});

	describe("resolveSecret", () => {
		it("returns plaintext values as-is", async () => {
			const result = await resolveSecret("my-api-key");
			expect(result.resolved).toBe(true);
			expect(result.value).toBe("my-api-key");
			expect(result.source).toBe("plaintext");
		});

		it("fails open with vault:// URIs when vault unavailable", async () => {
			// Mock fetch to reject (vault unavailable)
			global.fetch = vi.fn().mockRejectedValue(new Error("Connection refused"));

			const result = await resolveSecret("vault://secret/path#field", {
				failOpen: true,
			});

			expect(result.resolved).toBe(true);
			expect(result.value).toBe("vault://secret/path#field");
			expect(result.source).toBe("plaintext");
			expect(result.warning).toContain("Falling back");
		});

		it("throws in strict mode if vault unavailable", async () => {
			// Mock fetch to reject
			global.fetch = vi.fn().mockRejectedValue(new Error("Connection refused"));

			await expect(
				resolveSecret("vault://secret/path#field", {
					failOpen: false,
				}),
			).rejects.toThrow();
		});
	});

	describe("Cache stats", () => {
		it("reports cache size and keys", async () => {
			const stats = getVaultCacheStats();
			expect(stats.size).toBe(0);
			expect(stats.keys).toEqual([]);
		});

		it("clears cache on demand", async () => {
			clearVaultCache();
			const stats = getVaultCacheStats();
			expect(stats.size).toBe(0);
		});
	});

	describe("URI format validation", () => {
		it("rejects URIs with no path", () => {
			const result = parseVaultUri("vault://#field");
			expect(result).toHaveProperty("error");
		});

		it("rejects URIs with no field", () => {
			const result = parseVaultUri("vault://secret/path#");
			expect(result).toHaveProperty("error");
		});

		it("handles paths with multiple segments", () => {
			const result = parseVaultUri("vault://secret/deeply/nested/path#field");
			expect(result.path).toBe("secret/deeply/nested/path");
			expect(result.field).toBe("field");
		});

		it("handles field names with underscores and numbers", () => {
			const result = parseVaultUri("vault://secret/path#api_key_123");
			expect(result.field).toBe("api_key_123");
		});
	});
});
