import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import fs from "node:fs/promises";
import path from "node:path";
import { exec } from "node:child_process";
import { promisify } from "node:util";

const execAsync = promisify(exec);

let tmpDir = ".";

describe("info command", () => {
	beforeEach(async () => {
		const tmp = path.join(
			process.cwd(),
			"node_modules",
			".cache",
			"info_test-",
		);
		await fs.mkdir(path.join(tmp, "node_modules", ".cache"), {
			recursive: true,
		});
		tmpDir = await fs.mkdtemp(tmp);

		// Mock console methods to avoid noise in test output
		vi.spyOn(console, "log").mockImplementation(() => {});
		vi.spyOn(console, "error").mockImplementation(() => {});
	});

	afterEach(async () => {
		await fs.rm(tmpDir, { recursive: true });
		vi.restoreAllMocks();
	});

	it("should display system information without auth config", async () => {
		// Create a minimal package.json
		await fs.writeFile(
			path.join(tmpDir, "package.json"),
			JSON.stringify({
				name: "test-project",
				version: "1.0.0",
				dependencies: {
					"better-auth": "^1.0.0",
				},
			}),
		);

		const cliPath = path.join(process.cwd(), "dist", "index.mjs");
		const { stdout } = await execAsync(`node ${cliPath} info --json`, {
			cwd: tmpDir,
		});

		const output = JSON.parse(stdout);

		// Check system information
		expect(output.system).toHaveProperty("platform");
		expect(output.system).toHaveProperty("arch");
		expect(output.system).toHaveProperty("cpuCount");
		expect(output.system).toHaveProperty("totalMemory");

		// Check node information
		expect(output.node).toHaveProperty("version");
		expect(output.node).toHaveProperty("env");

		// Check package manager
		expect(output.packageManager).toHaveProperty("name");
		expect(output.packageManager).toHaveProperty("version");

		// Better Auth config should have an error since no auth file exists
		expect(output.betterAuth).toHaveProperty("version");
		expect(output.betterAuth.config).toBeNull();
	});

	it("should load and sanitize auth configuration", async () => {
		// Create package.json with dependencies
		await fs.writeFile(
			path.join(tmpDir, "package.json"),
			JSON.stringify({
				name: "test-project",
				version: "1.0.0",
				dependencies: {
					"better-auth": "^1.0.0",
					next: "^14.0.0",
					react: "^18.0.0",
				},
			}),
		);

		// Create auth.ts with sensitive data - using in-memory database to avoid adapter errors
		await fs.writeFile(
			path.join(tmpDir, "auth.ts"),
			`import { betterAuth } from "better-auth";

			export const auth = betterAuth({
				secret: "super-secret-key-123",
				baseURL: "https://example.com",
				emailAndPassword: {
					enabled: true,
				},
				socialProviders: {
					github: {
						clientId: "github-client-id",
						clientSecret: "github-client-secret"
					},
					google: {
						clientId: "google-client-id",
						clientSecret: "google-client-secret"
					}
				}
			})`,
		);

		const cliPath = path.join(process.cwd(), "dist", "index.mjs");
		const { stdout } = await execAsync(`node ${cliPath} info --json`, {
			cwd: tmpDir,
		});

		const output = JSON.parse(stdout);

		// Check that sensitive data is redacted
		expect(output.betterAuth.config).toBeDefined();
		expect(output.betterAuth.config.secret).toBe("[REDACTED]");

		// Check social providers are sanitized
		expect(output.betterAuth.config.socialProviders).toBeDefined();
		expect(output.betterAuth.config.socialProviders.github.clientId).toBe(
			"[REDACTED]",
		);
		expect(output.betterAuth.config.socialProviders.github.clientSecret).toBe(
			"[REDACTED]",
		);
		expect(output.betterAuth.config.socialProviders.google.clientId).toBe(
			"[REDACTED]",
		);
		expect(output.betterAuth.config.socialProviders.google.clientSecret).toBe(
			"[REDACTED]",
		);

		// Check non-sensitive data is preserved
		expect(output.betterAuth.config.emailAndPassword).toEqual({
			enabled: true,
		});
		expect(output.betterAuth.config.baseURL).toBe("https://example.com");
	});

	it("should detect installed frameworks", async () => {
		// Create package.json with various frameworks
		await fs.writeFile(
			path.join(tmpDir, "package.json"),
			JSON.stringify({
				name: "test-project",
				version: "1.0.0",
				dependencies: {
					"better-auth": "^1.0.0",
					next: "^14.0.0",
					react: "^18.0.0",
				},
				devDependencies: {
					"@sveltejs/kit": "^2.0.0",
					svelte: "^4.0.0",
				},
			}),
		);

		const cliPath = path.join(process.cwd(), "dist", "index.mjs");
		const { stdout } = await execAsync(`node ${cliPath} info --json`, {
			cwd: tmpDir,
		});

		const output = JSON.parse(stdout);

		// Check frameworks are detected
		expect(output.frameworks).toContainEqual({
			name: "next",
			version: "^14.0.0",
		});
		expect(output.frameworks).toContainEqual({
			name: "react",
			version: "^18.0.0",
		});
		expect(output.frameworks).toContainEqual({
			name: "@sveltejs/kit",
			version: "^2.0.0",
		});
		expect(output.frameworks).toContainEqual({
			name: "svelte",
			version: "^4.0.0",
		});
	});

	it("should detect database clients", async () => {
		// Create package.json with database clients
		await fs.writeFile(
			path.join(tmpDir, "package.json"),
			JSON.stringify({
				name: "test-project",
				version: "1.0.0",
				dependencies: {
					"better-auth": "^1.0.0",
					"@prisma/client": "^5.0.0",
					kysely: "^0.26.0",
				},
				devDependencies: {
					"drizzle-orm": "^0.29.0",
					"better-sqlite3": "^9.0.0",
				},
			}),
		);

		const cliPath = path.join(process.cwd(), "dist", "index.mjs");
		const { stdout } = await execAsync(`node ${cliPath} info --json`, {
			cwd: tmpDir,
		});

		const output = JSON.parse(stdout);

		// Check database clients are detected
		expect(output.databases).toContainEqual({
			name: "@prisma/client",
			version: "^5.0.0",
		});
		expect(output.databases).toContainEqual({
			name: "kysely",
			version: "^0.26.0",
		});
		expect(output.databases).toContainEqual({
			name: "drizzle",
			version: "^0.29.0",
		});
		expect(output.databases).toContainEqual({
			name: "better-sqlite3",
			version: "^9.0.0",
		});
	});

	it("should support custom config path", async () => {
		// Create package.json
		await fs.writeFile(
			path.join(tmpDir, "package.json"),
			JSON.stringify({
				name: "test-project",
				version: "1.0.0",
				dependencies: {
					"better-auth": "^1.0.0",
				},
			}),
		);

		// Create custom directory for auth config
		const customPath = path.join(tmpDir, "config");
		await fs.mkdir(customPath, { recursive: true });

		// Create auth config in custom location
		await fs.writeFile(
			path.join(customPath, "auth.config.ts"),
			`import { betterAuth } from "better-auth";

			export const auth = betterAuth({
				secret: "my-secret",
				appName: "Custom Config App",
				emailAndPassword: {
					enabled: true,
				}
			})`,
		);

		const cliPath = path.join(process.cwd(), "dist", "index.mjs");
		const { stdout } = await execAsync(
			`node ${cliPath} info --config config/auth.config.ts --json`,
			{ cwd: tmpDir },
		);

		const output = JSON.parse(stdout);

		// Check that custom config was loaded
		expect(output.betterAuth.config).toBeDefined();
		expect(output.betterAuth.config.appName).toBe("Custom Config App");
		expect(output.betterAuth.config.secret).toBe("[REDACTED]");
		expect(output.betterAuth.config.emailAndPassword).toEqual({
			enabled: true,
		});
	});

	it("should sanitize plugin configurations", async () => {
		// Create package.json
		await fs.writeFile(
			path.join(tmpDir, "package.json"),
			JSON.stringify({
				name: "test-project",
				version: "1.0.0",
				dependencies: {
					"better-auth": "^1.0.0",
				},
			}),
		);

		// Create auth.ts with plugins
		await fs.writeFile(
			path.join(tmpDir, "auth.ts"),
			`import { betterAuth } from "better-auth";
			import { twoFactor, organization } from "better-auth/plugins";

			export const auth = betterAuth({
				plugins: [
					twoFactor({
						otpOptions: {
							secret: "otp-secret-key"
						}
					}),
					organization({
						apiKey: "org-api-key",
						webhookSecret: "webhook-secret"
					})
				]
			})`,
		);

		const cliPath = path.join(process.cwd(), "dist", "index.mjs");
		const { stdout } = await execAsync(`node ${cliPath} info --json`, {
			cwd: tmpDir,
		});

		const output = JSON.parse(stdout);

		// Check that plugin configs are sanitized
		expect(output.betterAuth.config.plugins).toBeDefined();
		expect(Array.isArray(output.betterAuth.config.plugins)).toBe(true);

		// Plugin sensitive data should be redacted
		const plugins = output.betterAuth.config.plugins;
		plugins.forEach((plugin: any) => {
			if (plugin.config) {
				// Check that sensitive keys are redacted
				const configStr = JSON.stringify(plugin.config);
				expect(configStr).toContain("[REDACTED]");
			}
		});
	});

	it("should handle missing package.json gracefully", async () => {
		// Don't create package.json
		const cliPath = path.join(process.cwd(), "dist", "index.mjs");
		const { stdout } = await execAsync(`node ${cliPath} info --json`, {
			cwd: tmpDir,
		});

		const output = JSON.parse(stdout);

		// Should still return system info
		expect(output.system).toBeDefined();
		expect(output.node).toBeDefined();
		expect(output.packageManager).toBeDefined();

		// Frameworks and databases should be null
		expect(output.frameworks).toBeNull();
		expect(output.databases).toBeNull();
	});
}, 20000);
