import chalk from "chalk"
import fetch from "node-fetch"
import { execSync, spawnSync } from "node:child_process"
import fs from "node:fs/promises"
import { existsSync } from "node:fs"
import path from "node:path"
import {
	addSolanaDependencies,
	appCSSContent,
	appTsxContent,
	DEPENDENCY_VERSIONS,
	ENV_FILE_TEMPLATE,
	generateMainTsxContent,
	generateMethodsTsxContent,
	generateReactReadme,
	generateViteConfigForSolana,
	GIT_CONFIG,
	indexCSSContent,
	indexHTMLContent,
	methodsCssContent,
	updatePackageJsonScripts,
	useDarkModeContent,
} from "./react-templates"
import type { AssetInfo, Chain, PackageJson } from "./types"

/**
 * Detects the package manager being used in the current project
 * @returns The package manager command to use with create-vite
 */
const detectPackageManagerForVite = (): string => {
	try {
		// Check for lockfiles to determine the package manager
		if (existsSync("bun.lock")) {
			return "bun create"
		} else if (existsSync("pnpm-lock.yaml")) {
			return "pnpm create"
		} else if (existsSync("yarn.lock")) {
			return "yarn create"
		} else {
			// Default to npm if no lockfile is found
			return "npm create"
		}
	} catch {
		console.warn(
			chalk.yellow("Failed to detect package manager, defaulting to npm")
		)
		return "npm create"
	}
}

export const generateReactApp = async (
	parentDir: string,
	appName: string,
	useViem: boolean,
	useWagmi: boolean,
	selectedChains: Chain[]
): Promise<void> => {
	const baseDir = path.resolve(parentDir, appName)
	const hasSolana = selectedChains.some((chain) => chain.name === "Solana")
	const hasEthereum = selectedChains.some((chain) => chain.name === "Ethereum")
	const packageManagerCmd = detectPackageManagerForVite()
	const isBun = packageManagerCmd.startsWith("bun")
	const viteCommand = isBun
		? `${packageManagerCmd} vite@latest ${appName} --template react-ts`
		: `${packageManagerCmd} vite@latest ${appName} -- --template react-ts`

	console.log(chalk.blue(`Creating React app with Vite: ${appName}`))
	execSync(viteCommand, {
		cwd: parentDir,
	})

	const packageJsonPath = path.join(baseDir, "package.json")
	let packageJson: PackageJson = JSON.parse(
		await fs.readFile(packageJsonPath, "utf8")
	)

	packageJson.type = "module"
	packageJson = updatePackageJsonScripts(packageJson)

	packageJson.dependencies = {
		"@dynamic-labs/sdk-react-core": DEPENDENCY_VERSIONS.dynamicSdk,
		react: DEPENDENCY_VERSIONS.react,
		"react-dom": DEPENDENCY_VERSIONS.reactDom,
		...(useViem && hasEthereum
			? { viem: DEPENDENCY_VERSIONS.viem }
			: !useViem && hasEthereum
				? {
						"@dynamic-labs/ethers-v6": DEPENDENCY_VERSIONS.dynamicEthers,
						ethers: DEPENDENCY_VERSIONS.ethers,
					}
				: {}),
		...(useWagmi && {
			"@dynamic-labs/wagmi-connector": DEPENDENCY_VERSIONS.dynamicSdk,
			"@tanstack/react-query": DEPENDENCY_VERSIONS.tanstackQuery,
			wagmi: DEPENDENCY_VERSIONS.wagmi,
		}),
		...Object.fromEntries(
			selectedChains.map((chain) => [
				chain.package,
				DEPENDENCY_VERSIONS.dynamicSdk,
			])
		),
	}

	if (hasSolana) {
		packageJson = addSolanaDependencies(packageJson)
		packageJson.devDependencies = {
			...(packageJson.devDependencies ?? {}),
			"vite-plugin-node-polyfills": DEPENDENCY_VERSIONS.nodePolyfills,
		}
	}

	packageJson.devDependencies = {
		...packageJson.devDependencies,
		"@vitejs/plugin-react": DEPENDENCY_VERSIONS.vitejsPluginReact,
		"@types/react": DEPENDENCY_VERSIONS.react,
		"@types/react-dom": DEPENDENCY_VERSIONS.reactDom,
		eslint: DEPENDENCY_VERSIONS.eslint,
		"eslint-plugin-react-hooks": DEPENDENCY_VERSIONS.eslintPluginReactHooks,
		"eslint-plugin-react-refresh": DEPENDENCY_VERSIONS.eslintPluginReactRefresh,
		typescript: DEPENDENCY_VERSIONS.typescript,
		vite: DEPENDENCY_VERSIONS.vite,
	}

	await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))

	const viteConfigContent = generateViteConfigForSolana(selectedChains)
	await fs.writeFile(path.join(baseDir, "vite.config.ts"), viteConfigContent)

	const libDir = path.join(baseDir, "src", "lib")
	await fs.mkdir(libDir, { recursive: true })
	await fs.writeFile(path.join(libDir, "useDarkMode.ts"), useDarkModeContent)

	const componentsDir = path.join(baseDir, "src", "components")
	await fs.mkdir(componentsDir, { recursive: true })

	await fs.writeFile(path.join(baseDir, "src", "App.css"), appCSSContent)
	await fs.writeFile(path.join(baseDir, "src", "App.tsx"), appTsxContent)

	const mainTsxContent = generateMainTsxContent(
		useViem,
		useWagmi,
		selectedChains
	)
	await fs.writeFile(path.join(baseDir, "src", "main.tsx"), mainTsxContent)
	await fs.writeFile(path.join(baseDir, "index.html"), indexHTMLContent)
	await fs.writeFile(path.join(baseDir, "src", "index.css"), indexCSSContent)

	const methodsTsxContent = generateMethodsTsxContent(selectedChains, useViem)
	await fs.writeFile(path.join(componentsDir, "Methods.tsx"), methodsTsxContent)
	await fs.writeFile(path.join(componentsDir, "Methods.css"), methodsCssContent)

	await fs.writeFile(path.join(baseDir, ".env"), ENV_FILE_TEMPLATE)
	await fs.writeFile(path.join(baseDir, ".env.example"), ENV_FILE_TEMPLATE)

	const readmeContent = generateReactReadme(appName)
	await fs.writeFile(path.join(baseDir, "README.md"), readmeContent)

	console.log(chalk.blue("Initializing git repository..."))
	if (process.env.CI !== "true") {
		try {
			// Initialize repository with proper config
			spawnSync("git", ["config", "user.email", "test@example.com"], {
				cwd: baseDir,
				stdio: "pipe",
			})
			spawnSync("git", ["config", "user.name", "Test User"], {
				cwd: baseDir,
				stdio: "pipe",
			})
			spawnSync("git", ["init"], { cwd: baseDir, stdio: "pipe" })

			// Add and commit files
			spawnSync("git", ["add", "."], { cwd: baseDir, stdio: "pipe" })
			spawnSync("git", ["commit", "-m", GIT_CONFIG.commitMessage], {
				cwd: baseDir,
				stdio: "pipe",
			})

			console.log(chalk.green("✅ Git repository initialized successfully!"))
		} catch (error) {
			console.warn(
				chalk.yellow(
					"Couldn't initialize git repository. This is not critical for the app to work."
				)
			)
			if (process.env.DEBUG) {
				console.error(error)
			}
		}
	} else {
		console.log(chalk.yellow("Skipping git initialization in CI environment"))
	}
	await copyAssets(baseDir)
}

const copyAssets = async (baseDir: string): Promise<void> => {
	// Skip asset downloads in CI
	if (process.env.CI === "true") {
		console.log("Skipping asset downloads in CI environment")
		return
	}

	const publicDir = path.join(baseDir, "public")

	// Hardcoded remote URLs for assets
	const assetUrls: AssetInfo[] = [
		{
			name: "image-light",
			url: "https://cdn.prod.website-files.com/626692727bba3f384e008e8a/66e1b9f35ab65dff341d8266_image-light.png",
		},
		{
			name: "image-dark",
			url: "https://cdn.prod.website-files.com/626692727bba3f384e008e8a/66e1b9f31261f4025001cbff_image-dark.png",
		},
		{
			name: "logo-light",
			url: "https://cdn.prod.website-files.com/626692727bba3f384e008e8a/66e1b9f3915ce2792a3677b1_logo-light.png",
		},
		{
			name: "logo-dark",
			url: "https://cdn.prod.website-files.com/626692727bba3f384e008e8a/66e1b9e7144147903a39ab97_logo-dark.png",
		},
	]

	try {
		// Ensure the public directory exists
		await fs.mkdir(publicDir, { recursive: true })

		// Download and save each asset
		const downloadPromises = assetUrls.map(async (asset) => {
			try {
				console.log(`Downloading ${asset.name}...`)
				const response = await fetch(asset.url)
				if (!response.ok) {
					throw new Error(`HTTP error! status: ${response.status}`)
				}
				const fileName = `${asset.name}.png`
				const filePath = path.join(publicDir, fileName)
				const arrayBuffer = await response.arrayBuffer()
				await fs.writeFile(filePath, Buffer.from(arrayBuffer))
				console.log(`Successfully downloaded ${asset.name}`)
			} catch (error) {
				console.warn(
					chalk.yellow(
						`Failed to download ${asset.name}: ${
							error instanceof Error ? error.message : String(error)
						}`
					)
				)
			}
		})

		await Promise.all(downloadPromises)
	} catch (error) {
		console.warn(
			chalk.yellow(
				`Asset download failed: ${
					error instanceof Error ? error.message : String(error)
				}`
			)
		)
	}
}
