/**
 * Vault Credential Resolver Tests
 *
 * Tests vault credential resolution, env var support, and fail-open behavior.
 */
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
	couldBeVaultUri,
	formatCredentialInfo,
	getCredentialValue,
	hasProviderCredentialEnvVar,
	resolveProviderCredential,
	resolveProviderCredentials,
} from "../vault-credential-resolver.js";

describe("Vault Credential Resolver", () => {
	beforeEach(() => {
		// Clear vault env vars
		delete process.env.VAULT_ADDR;
		delete process.env.VAULT_TOKEN;
		delete process.env.ANTHROPIC_API_KEY;
		delete process.env.OPENAI_API_KEY;
	});

	afterEach(() => {
		vi.clearAllMocks();
	});

	describe("couldBeVaultUri", () => {
		it("returns true for vault:// URIs", () => {
			expect(couldBeVaultUri("vault://secret/anthropic/prod#api_key")).toBe(
				true,
			);
		});

		it("returns false for plaintext API keys", () => {
			expect(couldBeVaultUri("sk-ant-abc123")).toBe(false);
		});

		it("returns false for env var references", () => {
			expect(couldBeVaultUri("ANTHROPIC_API_KEY")).toBe(false);
		});

		it("returns false for null/undefined", () => {
			expect(couldBeVaultUri(null)).toBe(false);
			expect(couldBeVaultUri(undefined)).toBe(false);
		});
	});

	describe("hasProviderCredentialEnvVar", () => {
		it("returns true when env var is set and non-empty", () => {
			process.env.ANTHROPIC_API_KEY = "sk-ant-abc123";
			expect(hasProviderCredentialEnvVar("ANTHROPIC_API_KEY")).toBe(true);
		});

		it("returns false when env var is not set", () => {
			expect(hasProviderCredentialEnvVar("NONEXISTENT_KEY")).toBe(false);
		});

		it("returns false when env var is empty", () => {
			process.env.ANTHROPIC_API_KEY = "";
			expect(hasProviderCredentialEnvVar("ANTHROPIC_API_KEY")).toBe(false);
		});

		it("returns false when env var is whitespace only", () => {
			process.env.ANTHROPIC_API_KEY = "   ";
			expect(hasProviderCredentialEnvVar("ANTHROPIC_API_KEY")).toBe(false);
		});
	});

	describe("resolveProviderCredential", () => {
		it("returns plaintext values as-is", async () => {
			const result = await resolveProviderCredential("sk-ant-abc123");
			expect(result).toMatchObject({
				resolved: true,
				value: "sk-ant-abc123",
				source: "plaintext",
			});
		});

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

			const result = await resolveProviderCredential(
				"vault://secret/anthropic/prod#api_key",
			);
			expect(result.resolved).toBe(true);
			expect(result.source).toBe("plaintext");
			expect(result.warning).toBeDefined();
		});

		it("returns undefined for null input", async () => {
			expect(await resolveProviderCredential(null)).toBeUndefined();
		});

		it("returns undefined for empty string", async () => {
			expect(await resolveProviderCredential("")).toBeUndefined();
		});

		it("handles successful vault resolution", async () => {
			global.fetch = vi.fn().mockResolvedValue({
				ok: true,
				json: async () => ({
					data: {
						data: {
							api_key: "resolved-secret-key",
						},
					},
				}),
			});

			process.env.VAULT_TOKEN = "test-token";
			const result = await resolveProviderCredential(
				"vault://secret/anthropic/prod#api_key",
			);

			expect(result.resolved).toBe(true);
			expect(result.source).toBe("vault");
			expect(result.value).toBe("resolved-secret-key");
		});
	});

	describe("resolveProviderCredentials", () => {
		it("resolves multiple credentials", async () => {
			const creds = {
				anthropic: "sk-ant-abc123",
				openai: "vault://secret/openai/prod#api_key",
			};

			global.fetch = vi.fn().mockRejectedValue(new Error("Vault unavailable"));

			const results = await resolveProviderCredentials(creds);

			expect(results.anthropic).toMatchObject({
				resolved: true,
				source: "plaintext",
				value: "sk-ant-abc123",
			});
			expect(results.openai).toMatchObject({
				resolved: true,
				source: "plaintext",
			});
		});

		it("handles empty credential map", async () => {
			expect(await resolveProviderCredentials(null)).toEqual({});
			expect(await resolveProviderCredentials({})).toEqual({});
		});

		it("omits missing credentials from result", async () => {
			const creds = {
				anthropic: "sk-ant-abc123",
				openai: null,
				google: undefined,
			};

			const results = await resolveProviderCredentials(creds);

			expect(results).toHaveProperty("anthropic");
			expect(results).not.toHaveProperty("openai");
			expect(results).not.toHaveProperty("google");
		});
	});

	describe("getCredentialValue", () => {
		it("returns value from resolved result", () => {
			const result = {
				resolved: true,
				value: "sk-ant-abc123",
				source: "plaintext",
			};
			expect(getCredentialValue(result)).toBe("sk-ant-abc123");
		});

		it("returns undefined for unresolved result in permissive mode", () => {
			const result = {
				resolved: false,
				value: "vault://secret/path#field",
				warning: "Vault unavailable",
			};
			expect(getCredentialValue(result, false)).toBeUndefined();
		});

		it("throws in strict mode if resolution failed", () => {
			const result = {
				resolved: false,
				value: "vault://secret/path#field",
				warning: "Vault unavailable",
			};
			expect(() => getCredentialValue(result, true)).toThrow();
		});

		it("handles null result", () => {
			expect(getCredentialValue(null, false)).toBeUndefined();
		});

		it("throws in strict mode if result is null", () => {
			expect(() => getCredentialValue(null, true)).toThrow();
		});
	});

	describe("formatCredentialInfo", () => {
		it("formats resolved plaintext credential", () => {
			const result = {
				resolved: true,
				value: "sk-ant-abc123",
				source: "plaintext",
			};
			const formatted = formatCredentialInfo(result, "anthropic");
			expect(formatted).toContain("anthropic");
			expect(formatted).toContain("[13 chars]");
			expect(formatted).toContain("env var");
		});

		it("formats resolved vault credential", () => {
			const result = { resolved: true, value: "secret-key", source: "vault" };
			const formatted = formatCredentialInfo(result, "openai");
			expect(formatted).toContain("openai");
			expect(formatted).toContain("Vault");
		});

		it("includes warning if present", () => {
			const result = {
				resolved: false,
				value: "vault://secret/path#field",
				source: "plaintext",
				warning: "Vault unavailable",
			};
			const formatted = formatCredentialInfo(result, "anthropic");
			expect(formatted).toContain("warning");
			expect(formatted).toContain("Vault unavailable");
		});

		it("handles empty credential value", () => {
			const result = { resolved: true, value: "", source: "plaintext" };
			const formatted = formatCredentialInfo(result, "anthropic");
			expect(formatted).toContain("[empty]");
		});

		it("handles null result", () => {
			const formatted = formatCredentialInfo(null, "anthropic");
			expect(formatted).toContain("not available");
		});
	});

	describe("Integration: doctor-providers flow", () => {
		it("detects vault URIs in env vars for doctor checks", () => {
			process.env.ANTHROPIC_API_KEY = "vault://secret/anthropic/prod#api_key";
			expect(couldBeVaultUri(process.env.ANTHROPIC_API_KEY)).toBe(true);
			expect(hasProviderCredentialEnvVar("ANTHROPIC_API_KEY")).toBe(true);
		});

		it("handles mixed plaintext and vault credentials", async () => {
			const plaintext = "sk-ant-abc123";
			const vault = "vault://secret/openai/prod#api_key";

			global.fetch = vi.fn().mockRejectedValue(new Error("Vault unavailable"));

			const result1 = await resolveProviderCredential(plaintext);
			const result2 = await resolveProviderCredential(vault);

			expect(result1.source).toBe("plaintext");
			expect(result2.source).toBe("plaintext"); // Falls back to plaintext
		});
	});
});
