import { describe, it, expect, beforeEach, afterEach } from "bun:test"
import { promises as fs } from "node:fs"
import path from "node:path"
import { execSync, spawn } from "node:child_process"
import { generateNextApp } from "../../generate-next"
import { generateReactApp } from "../../generate-react"
import type { Chain } from "../../types"

const TEST_DIR = path.join(process.cwd(), "test-output")
let testFailed = false
const isCI = process.env.CI === "true"

describe("Development Server", () => {
	const mockChains: Chain[] = [
		{
			name: "Ethereum",
			package: "@dynamic-labs/ethereum",
			connector: "EthereumWalletConnectors",
		},
	]

	beforeEach(async () => {
		testFailed = false
		// Clean up any existing test directory
		try {
			await fs.rm(TEST_DIR, { recursive: true, force: true })
		} catch {
			console.error("Error during cleanup")
		}
		await fs.mkdir(TEST_DIR, { recursive: true })
	})

	afterEach(async () => {
		// Only cleanup if test passed
		if (testFailed) {
			console.log(`Test failed - keeping directory ${TEST_DIR} for inspection`)
			return
		}
		try {
			await fs.rm(TEST_DIR, { recursive: true, force: true })
		} catch {
			console.error("Error during cleanup")
		}
	})

	it("should start Next.js development server", async () => {
		try {
			const appName = "test-next-app"
			const appDir = path.join(TEST_DIR, appName)

			// Generate the app
			await generateNextApp(TEST_DIR, appName, true, false, mockChains)

			// Install dependencies
			console.log("Installing dependencies...")
			try {
				// First try with basic install
				execSync("npm install --ignore-scripts", {
					cwd: appDir,
					stdio: "inherit",
					timeout: 300000,
					env: {
						...process.env,
						GENERATE_SOURCEMAP: "false",
					},
				})
			} catch (error) {
				console.error("npm install failed:", error)
				// Try again with a clean cache
				console.log("Retrying npm install with clean cache...")
				execSync("npm cache clean --force", {
					cwd: appDir,
					stdio: "inherit",
				})
				execSync("npm install --ignore-scripts", {
					cwd: appDir,
					stdio: "inherit",
					timeout: 300000,
					env: {
						...process.env,
						GENERATE_SOURCEMAP: "false",
					},
				})
			}

			// Start the development server
			console.log("Starting development server...")
			const server = spawn("npm", ["run", "dev"], {
				cwd: appDir,
				env: {
					...process.env,
					CI: isCI ? "true" : "false", // Pass CI flag to skip asset downloads
					NEXT_TELEMETRY_DISABLED: "1", // Disable telemetry
				},
			})

			// Log server output
			server.stdout.on("data", (data) => {
				console.log(`Server stdout: ${data}`)
			})
			server.stderr.on("data", (data) => {
				console.error(`Server stderr: ${data}`)
			})

			// Wait for server to start (increased timeout for CI)
			const waitTime = isCI ? 15000 : 10000
			console.log(`Waiting ${waitTime}ms for server to start...`)
			await new Promise((resolve) => setTimeout(resolve, waitTime))

			// Make a request to the server
			console.log("Making request to server...")
			const response = await fetch("http://localhost:3000")
			expect(response.status).toBe(200)

			// Kill the server
			console.log("Killing server...")
			server.kill()
		} catch (error) {
			testFailed = true
			console.error("Test failed with error:", error)
			throw error
		}
	}, 300000) // 5 minute timeout

	it("should start Vite development server", async () => {
		try {
			const appName = "test-react-app"
			const appDir = path.join(TEST_DIR, appName)

			// Generate the app
			await generateReactApp(TEST_DIR, appName, true, false, mockChains)

			// Install dependencies
			console.log("Installing dependencies...")
			try {
				// Clean up any existing node_modules and package-lock.json
				try {
					await fs.rm(path.join(appDir, "node_modules"), {
						recursive: true,
						force: true,
					})
					await fs.rm(path.join(appDir, "package-lock.json"), { force: true })
				} catch {
					console.log(
						"No existing node_modules or package-lock.json to clean up"
					)
				}

				// First try with basic install
				execSync("npm install --ignore-scripts", {
					cwd: appDir,
					stdio: "inherit",
					timeout: 300000,
					env: {
						...process.env,
						GENERATE_SOURCEMAP: "false",
					},
				})
			} catch (error) {
				console.error("npm install failed:", error)
				// Try again with a clean cache
				console.log("Retrying npm install with clean cache...")
				execSync("npm cache clean --force", {
					cwd: appDir,
					stdio: "inherit",
				})
				// Clean up again before retry
				try {
					await fs.rm(path.join(appDir, "node_modules"), {
						recursive: true,
						force: true,
					})
					await fs.rm(path.join(appDir, "package-lock.json"), { force: true })
				} catch {
					console.log(
						"No existing node_modules or package-lock.json to clean up"
					)
				}
				execSync("npm install --ignore-scripts", {
					cwd: appDir,
					stdio: "inherit",
					timeout: 300000,
					env: {
						...process.env,
						GENERATE_SOURCEMAP: "false",
					},
				})
			}

			// Start the development server
			console.log("Starting development server...")
			const server = spawn("npm", ["run", "dev"], {
				cwd: appDir,
				env: {
					...process.env,
					CI: isCI ? "true" : "false", // Pass CI flag to skip asset downloads
					VITE_TELEMETRY_DISABLED: "1", // Disable telemetry
				},
			})

			// Log server output
			server.stdout.on("data", (data) => {
				console.log(`Server stdout: ${data}`)
			})
			server.stderr.on("data", (data) => {
				console.error(`Server stderr: ${data}`)
			})

			// Wait for server to start (increased timeout for CI)
			const waitTime = isCI ? 15000 : 10000
			console.log(`Waiting ${waitTime}ms for server to start...`)
			await new Promise((resolve) => setTimeout(resolve, waitTime))

			// Make a request to the server
			console.log("Making request to server...")
			let retries = 3
			let response: Response | undefined
			while (retries > 0) {
				try {
					response = await fetch("http://localhost:5173")
					if (response.status === 200) {
						break
					}
				} catch (error) {
					console.log(`Connection attempt ${4 - retries} failed, retrying...`)
					await new Promise((resolve) => setTimeout(resolve, 5000)) // Wait 5 seconds between retries
					retries--
				}
			}
			if (!response || response.status !== 200) {
				throw new Error("Failed to connect to server after multiple attempts")
			}
			expect(response.status).toBe(200)

			// Kill the server
			console.log("Killing server...")
			server.kill()
		} catch (error) {
			testFailed = true
			console.error("Test failed with error:", error)
			throw error
		}
	}, 300000) // 5 minute timeout
})
