/**
 * Codex Discovery Provider
 *
 * Loads configuration from OpenAI Codex format:
 * - System Instructions: AGENTS.md (user-level only at ~/.codex/AGENTS.md)
 *
 * User directory: ~/.codex
 */
import * as path from "node:path";
import { logger, parseFrontmatter } from "@oh-my-pi/pi-utils";
import { registerProvider } from "../capability";
import type { ContextFile } from "../capability/context-file";
import { contextFileCapability } from "../capability/context-file";
import { type ExtensionModule, extensionModuleCapability } from "../capability/extension-module";
import { readFile } from "../capability/fs";
import type { Hook } from "../capability/hook";
import { hookCapability } from "../capability/hook";
import type { MCPServer } from "../capability/mcp";
import { mcpCapability } from "../capability/mcp";
import type { Prompt } from "../capability/prompt";
import { promptCapability } from "../capability/prompt";
import type { Settings } from "../capability/settings";
import { settingsCapability } from "../capability/settings";
import type { Skill } from "../capability/skill";
import { skillCapability } from "../capability/skill";
import type { SlashCommand } from "../capability/slash-command";
import { slashCommandCapability } from "../capability/slash-command";
import type { CustomTool } from "../capability/tool";
import { toolCapability } from "../capability/tool";
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";

import {
	buildExtensionModuleItems,
	createSourceMeta,
	discoverExtensionModulePaths,
	loadFilesFromDir,
	SOURCE_PATHS,
	scanSkillsFromDir,
} from "./helpers";

const PROVIDER_ID = "codex";
const DISPLAY_NAME = "OpenAI Codex";
const PRIORITY = 70;

function getProjectCodexDir(ctx: LoadContext): string {
	return path.join(ctx.cwd, ".codex");
}

// =============================================================================
// Context Files (AGENTS.md)
// =============================================================================

async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFile>> {
	const items: ContextFile[] = [];
	const warnings: string[] = [];

	// User level only: ~/.codex/AGENTS.md
	const agentsMd = path.join(ctx.home, SOURCE_PATHS.codex.userBase, "AGENTS.md");
	const agentsContent = await readFile(agentsMd);
	if (agentsContent) {
		items.push({
			path: agentsMd,
			content: agentsContent,
			level: "user",
			_source: createSourceMeta(PROVIDER_ID, agentsMd, "user"),
		});
	}

	return { items, warnings };
}

// =============================================================================
// MCP Servers (config.toml)
// =============================================================================

async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>> {
	const warnings: string[] = [];

	const userConfigPath = path.join(ctx.home, SOURCE_PATHS.codex.userBase, "config.toml");
	const codexDir = getProjectCodexDir(ctx);
	const projectConfigPath = path.join(codexDir, "config.toml");

	const [userConfig, projectConfig] = await Promise.all([
		loadTomlConfig(ctx, userConfigPath),
		loadTomlConfig(ctx, projectConfigPath),
	]);

	const items: MCPServer[] = [];
	if (userConfig) {
		const servers = extractMCPServersFromToml(userConfig);
		for (const [name, config] of Object.entries(servers)) {
			items.push({
				name,
				...config,
				_source: createSourceMeta(PROVIDER_ID, userConfigPath, "user"),
			});
		}
	}
	if (projectConfig) {
		const servers = extractMCPServersFromToml(projectConfig);
		for (const [name, config] of Object.entries(servers)) {
			items.push({
				name,
				...config,
				_source: createSourceMeta(PROVIDER_ID, projectConfigPath, "project"),
			});
		}
	}

	return { items, warnings };
}

async function loadTomlConfig(_ctx: LoadContext, path: string): Promise<Record<string, unknown> | null> {
	const content = await readFile(path);
	if (!content) return null;

	try {
		return Bun.TOML.parse(content) as Record<string, unknown>;
	} catch (error) {
		logger.warn("Failed to parse TOML config", { path, error: String(error) });
		return null;
	}
}

/** Codex MCP server config format (from config.toml) */
interface CodexMCPConfig {
	command?: string;
	args?: string[];
	env?: Record<string, string>;
	env_vars?: string[]; // Environment variable names to forward from parent
	url?: string;
	http_headers?: Record<string, string>;
	env_http_headers?: Record<string, string>; // Header name -> env var name
	bearer_token_env_var?: string;
	cwd?: string;
	startup_timeout_sec?: number;
	tool_timeout_sec?: number;
	enabled_tools?: string[];
	disabled_tools?: string[];
}

function extractMCPServersFromToml(toml: Record<string, unknown>): Record<string, Partial<MCPServer>> {
	// Check for [mcp_servers.*] sections (Codex format)
	if (!toml.mcp_servers || typeof toml.mcp_servers !== "object") {
		return {};
	}

	const codexServers = toml.mcp_servers as Record<string, CodexMCPConfig>;
	const result: Record<string, Partial<MCPServer>> = {};

	for (const [name, config] of Object.entries(codexServers)) {
		const server: Partial<MCPServer> = {
			command: config.command,
			args: config.args,
			url: config.url,
		};

		// Build env by merging explicit env and forwarded env_vars
		const env: Record<string, string> = { ...config.env };
		if (config.env_vars) {
			for (const varName of config.env_vars) {
				const value = Bun.env[varName];
				if (value !== undefined) {
					env[varName] = value;
				}
			}
		}
		if (Object.keys(env).length > 0) {
			server.env = env;
		}

		// Build headers from http_headers, env_http_headers, and bearer_token_env_var
		const headers: Record<string, string> = { ...config.http_headers };
		if (config.env_http_headers) {
			for (const [headerName, envVarName] of Object.entries(config.env_http_headers)) {
				const value = Bun.env[envVarName];
				if (value !== undefined) {
					headers[headerName] = value;
				}
			}
		}
		if (config.bearer_token_env_var) {
			const token = Bun.env[config.bearer_token_env_var];
			if (token) {
				headers.Authorization = `Bearer ${token}`;
			}
		}
		if (Object.keys(headers).length > 0) {
			server.headers = headers;
		}

		// Determine transport type (infer from config if not explicit)
		if (config.url) {
			server.transport = "http";
		} else if (config.command) {
			server.transport = "stdio";
		}
		// Note: validation of transport vs endpoint is handled by mcpCapability.validate()

		// Map Codex tool_timeout_sec (seconds) to MCPServer timeout (milliseconds)
		if (typeof config.tool_timeout_sec === "number" && config.tool_timeout_sec > 0) {
			server.timeout = config.tool_timeout_sec * 1000;
		}
		result[name] = server;
	}

	return result;
}

// =============================================================================
// Skills (skills/)
// =============================================================================

async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
	const userSkillsDir = path.join(ctx.home, SOURCE_PATHS.codex.userBase, "skills");
	const codexDir = getProjectCodexDir(ctx);
	const projectSkillsDir = path.join(codexDir, "skills");

	const results = await Promise.all([
		scanSkillsFromDir(ctx, {
			dir: userSkillsDir,
			providerId: PROVIDER_ID,
			level: "user",
		}),
		scanSkillsFromDir(ctx, {
			dir: projectSkillsDir,
			providerId: PROVIDER_ID,
			level: "project",
		}),
	]);

	const items = results.flatMap(r => r.items);
	const warnings = results.flatMap(r => r.warnings || []);

	return { items, warnings };
}

// =============================================================================
// Extension Modules (extensions/)
// =============================================================================

async function loadExtensionModules(ctx: LoadContext): Promise<LoadResult<ExtensionModule>> {
	const warnings: string[] = [];

	const userExtensionsDir = path.join(ctx.home, SOURCE_PATHS.codex.userBase, "extensions");
	const codexDir = getProjectCodexDir(ctx);
	const projectExtensionsDir = path.join(codexDir, "extensions");

	const [userPaths, projectPaths] = await Promise.all([
		discoverExtensionModulePaths(ctx, userExtensionsDir),
		discoverExtensionModulePaths(ctx, projectExtensionsDir),
	]);

	const items = buildExtensionModuleItems(PROVIDER_ID, userPaths, projectPaths);

	return { items, warnings };
}

// =============================================================================
// Slash Commands (commands/)
// =============================================================================

async function loadSlashCommands(ctx: LoadContext): Promise<LoadResult<SlashCommand>> {
	const userCommandsDir = path.join(ctx.home, SOURCE_PATHS.codex.userBase, "commands");
	const codexDir = getProjectCodexDir(ctx);
	const projectCommandsDir = path.join(codexDir, "commands");

	const transformCommand =
		(level: "user" | "project") => (name: string, content: string, path: string, source: SourceMeta) => {
			const { frontmatter, body } = parseFrontmatter(content, { source: path });
			const commandName = frontmatter.name || name.replace(/\.md$/, "");
			return {
				name: String(commandName),
				path,
				content: body,
				level,
				_source: source,
			};
		};

	const results = await Promise.all([
		loadFilesFromDir(ctx, userCommandsDir, PROVIDER_ID, "user", {
			extensions: ["md"],
			transform: transformCommand("user"),
		}),
		loadFilesFromDir(ctx, projectCommandsDir, PROVIDER_ID, "project", {
			extensions: ["md"],
			transform: transformCommand("project"),
		}),
	]);

	const items = results.flatMap(r => r.items);
	const warnings = results.flatMap(r => r.warnings || []);

	return { items, warnings };
}

// =============================================================================
// Prompts (prompts/*.md)
// =============================================================================

async function loadPrompts(ctx: LoadContext): Promise<LoadResult<Prompt>> {
	const userPromptsDir = path.join(ctx.home, SOURCE_PATHS.codex.userBase, "prompts");
	const codexDir = getProjectCodexDir(ctx);
	const projectPromptsDir = path.join(codexDir, "prompts");

	const transformPrompt = (name: string, content: string, path: string, source: SourceMeta) => {
		const { frontmatter, body } = parseFrontmatter(content, { source: path });
		const promptName = frontmatter.name || name.replace(/\.md$/, "");
		return {
			name: String(promptName),
			path,
			content: body,
			description: frontmatter.description ? String(frontmatter.description) : undefined,
			_source: source,
		};
	};

	const results = await Promise.all([
		loadFilesFromDir(ctx, userPromptsDir, PROVIDER_ID, "user", {
			extensions: ["md"],
			transform: transformPrompt,
		}),
		loadFilesFromDir(ctx, projectPromptsDir, PROVIDER_ID, "project", {
			extensions: ["md"],
			transform: transformPrompt,
		}),
	]);

	const items = results.flatMap(r => r.items);
	const warnings = results.flatMap(r => r.warnings || []);

	return { items, warnings };
}

// =============================================================================
// Hooks (hooks/)
// =============================================================================

async function loadHooks(ctx: LoadContext): Promise<LoadResult<Hook>> {
	const userHooksDir = path.join(ctx.home, SOURCE_PATHS.codex.userBase, "hooks");
	const codexDir = getProjectCodexDir(ctx);
	const projectHooksDir = path.join(codexDir, "hooks");

	const transformHook =
		(level: "user" | "project") => (name: string, _content: string, path: string, source: SourceMeta) => {
			const baseName = name.replace(/\.(ts|js)$/, "");
			const match = baseName.match(/^(pre|post)-(.+)$/);
			const hookType = (match?.[1] as "pre" | "post") || "pre";
			const toolName = match?.[2] || baseName;
			return {
				name,
				path,
				type: hookType,
				tool: toolName,
				level,
				_source: source,
			};
		};

	const results = await Promise.all([
		loadFilesFromDir(ctx, userHooksDir, PROVIDER_ID, "user", {
			extensions: ["ts", "js"],
			transform: transformHook("user"),
		}),
		loadFilesFromDir(ctx, projectHooksDir, PROVIDER_ID, "project", {
			extensions: ["ts", "js"],
			transform: transformHook("project"),
		}),
	]);

	const items = results.flatMap(r => r.items);
	const warnings = results.flatMap(r => r.warnings || []);

	return { items, warnings };
}

// =============================================================================
// Tools (tools/)
// =============================================================================

async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
	const userToolsDir = path.join(ctx.home, SOURCE_PATHS.codex.userBase, "tools");
	const codexDir = getProjectCodexDir(ctx);
	const projectToolsDir = path.join(codexDir, "tools");

	const transformTool =
		(level: "user" | "project") => (name: string, _content: string, path: string, source: SourceMeta) => {
			const toolName = name.replace(/\.(ts|js)$/, "");
			return {
				name: toolName,
				path,
				level,
				_source: source,
			} as CustomTool;
		};

	const results = await Promise.all([
		loadFilesFromDir(ctx, userToolsDir, PROVIDER_ID, "user", {
			extensions: ["ts", "js"],
			transform: transformTool("user"),
		}),
		loadFilesFromDir(ctx, projectToolsDir, PROVIDER_ID, "project", {
			extensions: ["ts", "js"],
			transform: transformTool("project"),
		}),
	]);

	const items = results.flatMap(r => r.items);
	const warnings = results.flatMap(r => r.warnings || []);

	return { items, warnings };
}

// =============================================================================
// Settings (config.toml)
// =============================================================================

async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
	const warnings: string[] = [];

	const userConfigPath = path.join(ctx.home, SOURCE_PATHS.codex.userBase, "config.toml");
	const codexDir = getProjectCodexDir(ctx);
	const projectConfigPath = path.join(codexDir, "config.toml");

	const [userConfig, projectConfig] = await Promise.all([
		loadTomlConfig(ctx, userConfigPath),
		loadTomlConfig(ctx, projectConfigPath),
	]);

	const items: Settings[] = [];
	if (userConfig) {
		items.push({
			...userConfig,
			_source: createSourceMeta(PROVIDER_ID, userConfigPath, "user"),
		} as Settings);
	}
	if (projectConfig) {
		items.push({
			...projectConfig,
			_source: createSourceMeta(PROVIDER_ID, projectConfigPath, "project"),
		} as Settings);
	}

	return { items, warnings };
}

// =============================================================================
// Provider Registration (executes on module import)
// =============================================================================

registerProvider<ContextFile>(contextFileCapability.id, {
	id: PROVIDER_ID,
	displayName: DISPLAY_NAME,
	description: "Load context files from ~/.codex/AGENTS.md (user-level only)",
	priority: PRIORITY,
	load: loadContextFiles,
});

registerProvider<MCPServer>(mcpCapability.id, {
	id: PROVIDER_ID,
	displayName: DISPLAY_NAME,
	description: "Load MCP servers from config.toml [mcp_servers.*] sections",
	priority: PRIORITY,
	load: loadMCPServers,
});

registerProvider<Skill>(skillCapability.id, {
	id: PROVIDER_ID,
	displayName: DISPLAY_NAME,
	description: "Load skills from ~/.codex/skills and .codex/skills/",
	priority: PRIORITY,
	load: loadSkills,
});

registerProvider<ExtensionModule>(extensionModuleCapability.id, {
	id: PROVIDER_ID,
	displayName: DISPLAY_NAME,
	description: "Load extension modules from ~/.codex/extensions and .codex/extensions/",
	priority: PRIORITY,
	load: loadExtensionModules,
});

registerProvider<SlashCommand>(slashCommandCapability.id, {
	id: PROVIDER_ID,
	displayName: DISPLAY_NAME,
	description: "Load slash commands from ~/.codex/commands and .codex/commands/",
	priority: PRIORITY,
	load: loadSlashCommands,
});

registerProvider<Prompt>(promptCapability.id, {
	id: PROVIDER_ID,
	displayName: DISPLAY_NAME,
	description: "Load prompts from ~/.codex/prompts and .codex/prompts/",
	priority: PRIORITY,
	load: loadPrompts,
});

registerProvider<Hook>(hookCapability.id, {
	id: PROVIDER_ID,
	displayName: DISPLAY_NAME,
	description: "Load hooks from ~/.codex/hooks and .codex/hooks/",
	priority: PRIORITY,
	load: loadHooks,
});

registerProvider<CustomTool>(toolCapability.id, {
	id: PROVIDER_ID,
	displayName: DISPLAY_NAME,
	description: "Load custom tools from ~/.codex/tools and .codex/tools/",
	priority: PRIORITY,
	load: loadTools,
});

registerProvider<Settings>(settingsCapability.id, {
	id: PROVIDER_ID,
	displayName: DISPLAY_NAME,
	description: "Load settings from config.toml",
	priority: PRIORITY,
	load: loadSettings,
});