import chalk from "chalk"
import fetch from "node-fetch"
import { execSync } from "node:child_process"
import fs from "node:fs/promises"
import { existsSync } from "node:fs"
import path from "node:path"
import {
	generateDynamicLibContent,
	generateLayoutContent,
	generateMethodsContent,
	generateProvidersContent,
	methodsCss,
	pageContent,
	pageCssContent,
	useDarkMode,
	wagmiContent,
} from "./next-templates"
import {
	addSolanaDependencies,
	createConfigOverrides,
	createNonSolanaConfig,
} from "./next-templates/solana-config"
import { DEPENDENCY_VERSIONS } from "./react-templates"
import type { AssetInfo, Chain, PackageJson } from "./types"

interface FileToCreate {
	path: string
	content: string
}

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

export const generateNextApp = 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 packageManagerFlag = detectPackageManager()

	console.log(chalk.blue(`Creating Next.js app: ${appName}`))
	execSync(
		`npx create-next-app@latest ${baseDir} --yes --no-tailwind ${packageManagerFlag}`,
		{ stdio: "inherit" }
	)

	// Remove existing .git directory and re-initialize Git
	try {
		await fs.rm(path.join(baseDir, ".git"), { recursive: true, force: true })
		console.log(chalk.blue("Initializing new Git repository..."))
		execSync("git init", { cwd: baseDir, stdio: "inherit" })
		execSync("git add .", { cwd: baseDir, stdio: "inherit" })
		execSync('git commit -m "initial commit from create dynamic app"', {
			cwd: baseDir,
			stdio: "inherit",
		})
		console.log(chalk.green("Created initial commit."))
	} catch (error) {
		console.error(chalk.red("Failed to re-initialize Git repository:", error))
	}

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

	packageJson.dependencies = {
		...packageJson.dependencies,
		"@dynamic-labs/sdk-react-core": DEPENDENCY_VERSIONS.dynamicSdk,
		...(useViem && selectedChains.some((chain) => chain.name === "Ethereum")
			? { viem: DEPENDENCY_VERSIONS.viem }
			: !useViem && selectedChains.some((chain) => chain.name === "Ethereum")
				? {
						"@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,
			])
		),
		"crypto-browserify": "^3.12.0",
		"stream-browserify": "^3.0.0",
		process: "^0.11.10",
	}

	if (hasSolana) {
		packageJson = addSolanaDependencies(packageJson)
	}

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

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

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

	const filesToCreate: FileToCreate[] = [
		{
			path: path.join(baseDir, "app", "layout.tsx"),
			content: generateLayoutContent(),
		},
		{ path: path.join(baseDir, "app", "page.tsx"), content: pageContent },
		{ path: path.join(baseDir, "app", "page.css"), content: pageCssContent },
		{
			path: path.join(baseDir, "app/components", "Methods.tsx"),
			content: generateMethodsContent(selectedChains, useViem),
		},
		{
			path: path.join(baseDir, "app/components", "Methods.css"),
			content: methodsCss,
		},
		{
			path: path.join(baseDir, "lib", "providers.tsx"),
			content: generateProvidersContent(useWagmi, selectedChains),
		},
		{
			path: path.join(baseDir, "lib", "useDarkMode.ts"),
			content: useDarkMode,
		},
		{
			path: path.join(baseDir, "lib", "dynamic.ts"),
			content: generateDynamicLibContent(useViem, useWagmi, selectedChains),
		},
		...(useWagmi
			? [{ path: path.join(baseDir, "lib", "wagmi.ts"), content: wagmiContent }]
			: []),
	]

	await Promise.all(
		filesToCreate.map((file) => fs.writeFile(file.path, file.content))
	)

	if (hasSolana) {
		await fs.writeFile(
			path.join(baseDir, "next.config.js"),
			createConfigOverrides()
		)
		return
	}

	await fs.writeFile(
		path.join(baseDir, "next.config.js"),
		createNonSolanaConfig()
	)

	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
		await Promise.all(
			assetUrls.map(async (asset) => {
				const response = await fetch(asset.url)
				if (response.ok) {
					const fileName = `${asset.name}.png`
					const filePath = path.join(publicDir, fileName)
					const arrayBuffer = await response.arrayBuffer()
					await fs.writeFile(filePath, Buffer.from(arrayBuffer))
				}
			})
		)
	} catch (error) {
		const errorMessage = error instanceof Error ? error.message : String(error)
		console.error(
			chalk.red(`Error copying assets from remote URLs: ${errorMessage}`)
		)
	}
}
