import { existsSync, readFileSync } from "node:fs";
import { mkdir } from "node:fs/promises";
import { dirname, isAbsolute, join } from "node:path";
import { fileURLToPath } from "node:url";
import {
	AGENT_GUIDELINES,
	CLAUDE_AGENT_CONTENT,
	CLAUDE_GUIDELINES,
	COPILOT_GUIDELINES,
	GEMINI_GUIDELINES,
	MCP_AGENT_NUDGE,
	README_GUIDELINES,
} from "./constants/index.ts";
import type { GitOperations } from "./git/operations.ts";

export type AgentInstructionFile =
	| "AGENTS.md"
	| "CLAUDE.md"
	| "GEMINI.md"
	| ".github/copilot-instructions.md"
	| "README.md";

const __dirname = dirname(fileURLToPath(import.meta.url));

async function loadContent(textOrPath: string): Promise<string> {
	if (textOrPath.includes("\n")) return textOrPath;
	try {
		const path = isAbsolute(textOrPath) ? textOrPath : join(__dirname, textOrPath);
		return await Bun.file(path).text();
	} catch {
		return textOrPath;
	}
}

type GuidelineMarkerKind = "default" | "mcp";

/**
 * Gets the appropriate markers for a given file type
 */
function getMarkers(fileName: string, kind: GuidelineMarkerKind = "default"): { start: string; end: string } {
	const label = kind === "mcp" ? "BACKLOG.MD MCP GUIDELINES" : "BACKLOG.MD GUIDELINES";
	if (fileName === ".cursorrules") {
		// .cursorrules doesn't support HTML comments, use markdown-style comments
		return {
			start: `# === ${label} START ===`,
			end: `# === ${label} END ===`,
		};
	}
	// All markdown files support HTML comments
	return {
		start: `<!-- ${label} START -->`,
		end: `<!-- ${label} END -->`,
	};
}

/**
 * Checks if the Backlog.md guidelines are already present in the content
 */
function hasBacklogGuidelines(content: string, fileName: string): boolean {
	const { start } = getMarkers(fileName);
	return content.includes(start);
}

/**
 * Wraps the Backlog.md guidelines with appropriate markers
 */
function wrapWithMarkers(content: string, fileName: string, kind: GuidelineMarkerKind = "default"): string {
	const { start, end } = getMarkers(fileName, kind);
	return `\n${start}\n${content}\n${end}\n`;
}

function stripGuidelineSection(
	content: string,
	fileName: string,
	kind: GuidelineMarkerKind,
): { content: string; removed: boolean; firstIndex?: number } {
	const { start, end } = getMarkers(fileName, kind);
	let removed = false;
	let result = content;
	let firstIndex: number | undefined;

	while (true) {
		const startIndex = result.indexOf(start);
		if (startIndex === -1) {
			break;
		}

		const endIndex = result.indexOf(end, startIndex);
		if (endIndex === -1) {
			break;
		}

		let removalStart = startIndex;
		while (removalStart > 0 && (result[removalStart - 1] === " " || result[removalStart - 1] === "\t")) {
			removalStart -= 1;
		}
		if (removalStart > 0 && result[removalStart - 1] === "\n") {
			removalStart -= 1;
			if (removalStart > 0 && result[removalStart - 1] === "\r") {
				removalStart -= 1;
			}
		} else if (removalStart > 0 && result[removalStart - 1] === "\r") {
			removalStart -= 1;
		}

		let removalEnd = endIndex + end.length;
		if (removalEnd < result.length && result[removalEnd] === "\r") {
			removalEnd += 1;
		}
		if (removalEnd < result.length && result[removalEnd] === "\n") {
			removalEnd += 1;
		}

		if (firstIndex === undefined) {
			firstIndex = removalStart;
		}
		result = result.slice(0, removalStart) + result.slice(removalEnd);
		removed = true;
	}

	return { content: result, removed, firstIndex };
}

export async function addAgentInstructions(
	projectRoot: string,
	git?: GitOperations,
	files: AgentInstructionFile[] = ["AGENTS.md", "CLAUDE.md", "GEMINI.md", ".github/copilot-instructions.md"],
	autoCommit = false,
): Promise<void> {
	const mapping: Record<AgentInstructionFile, string> = {
		"AGENTS.md": AGENT_GUIDELINES,
		"CLAUDE.md": CLAUDE_GUIDELINES,
		"GEMINI.md": GEMINI_GUIDELINES,
		".github/copilot-instructions.md": COPILOT_GUIDELINES,
		"README.md": README_GUIDELINES,
	};

	const paths: string[] = [];
	for (const name of files) {
		const content = await loadContent(mapping[name]);
		const filePath = join(projectRoot, name);
		let finalContent = "";

		// Check if file exists first to avoid Windows hanging issue
		if (existsSync(filePath)) {
			try {
				// On Windows, use synchronous read to avoid hanging
				let existing: string;
				if (process.platform === "win32") {
					existing = readFileSync(filePath, "utf-8");
				} else {
					existing = await Bun.file(filePath).text();
				}

				const mcpStripped = stripGuidelineSection(existing, name, "mcp");
				if (mcpStripped.removed) {
					existing = mcpStripped.content;
				}

				// Check if Backlog.md guidelines are already present
				if (hasBacklogGuidelines(existing, name)) {
					// Guidelines already exist, skip this file
					continue;
				}

				// Append Backlog.md guidelines with markers
				if (!existing.endsWith("\n")) existing += "\n";
				finalContent = existing + wrapWithMarkers(content, name);
			} catch (error) {
				console.error(`Error reading existing file ${filePath}:`, error);
				// If we can't read it, just use the new content with markers
				finalContent = wrapWithMarkers(content, name);
			}
		} else {
			// File doesn't exist, create with markers
			finalContent = wrapWithMarkers(content, name);
		}

		await mkdir(dirname(filePath), { recursive: true });
		await Bun.write(filePath, finalContent);
		paths.push(filePath);
	}

	if (git && paths.length > 0 && autoCommit) {
		await git.addFiles(paths);
		await git.commitChanges("Add AI agent instructions");
	}
}

export { loadContent as _loadAgentGuideline };

function _hasMcpGuidelines(content: string, fileName: string): boolean {
	const { start } = getMarkers(fileName, "mcp");
	return content.includes(start);
}

async function readExistingFile(filePath: string): Promise<string> {
	if (process.platform === "win32") {
		return readFileSync(filePath, "utf-8");
	}
	return await Bun.file(filePath).text();
}

export interface EnsureMcpGuidelinesResult {
	changed: boolean;
	created: boolean;
	fileName: AgentInstructionFile;
	filePath: string;
}

export async function ensureMcpGuidelines(
	projectRoot: string,
	fileName: AgentInstructionFile,
): Promise<EnsureMcpGuidelinesResult> {
	const filePath = join(projectRoot, fileName);
	const fileExists = existsSync(filePath);
	let existing = "";
	let original = "";
	let insertIndex: number | null = null;

	if (fileExists) {
		try {
			existing = await readExistingFile(filePath);
			original = existing;
			const cliStripped = stripGuidelineSection(existing, fileName, "default");
			if (cliStripped.removed && cliStripped.firstIndex !== undefined) {
				insertIndex = cliStripped.firstIndex;
			}
			existing = cliStripped.content;
			const mcpStripped = stripGuidelineSection(existing, fileName, "mcp");
			if (mcpStripped.removed && mcpStripped.firstIndex !== undefined) {
				insertIndex = mcpStripped.firstIndex;
			}
			existing = mcpStripped.content;
		} catch (error) {
			console.error(`Error reading existing file ${filePath}:`, error);
			existing = "";
		}
	}

	const nudgeBlock = wrapWithMarkers(MCP_AGENT_NUDGE, fileName, "mcp");
	let nextContent: string;
	if (insertIndex !== null) {
		const normalizedIndex = Math.max(0, Math.min(insertIndex, existing.length));
		nextContent = existing.slice(0, normalizedIndex) + nudgeBlock + existing.slice(normalizedIndex);
	} else {
		nextContent = existing;
		if (nextContent && !nextContent.endsWith("\n")) {
			nextContent += "\n";
		}
		nextContent += nudgeBlock;
	}

	const finalContent = nextContent;
	const changed = !fileExists || finalContent !== original;

	await mkdir(dirname(filePath), { recursive: true });
	if (changed) {
		await Bun.write(filePath, finalContent);
	}

	return { changed, created: !fileExists, fileName, filePath };
}

/**
 * Installs the Claude Code backlog agent to the project's .claude/agents directory
 */
export async function installClaudeAgent(projectRoot: string): Promise<void> {
	const agentDir = join(projectRoot, ".claude", "agents");
	const agentPath = join(agentDir, "project-manager-backlog.md");

	// Create the directory if it doesn't exist
	await mkdir(agentDir, { recursive: true });

	// Write the agent content
	await Bun.write(agentPath, CLAUDE_AGENT_CONTENT);
}
