import {
	chmodSync,
	existsSync,
	mkdirSync,
	readFileSync,
	writeFileSync,
} from "node:fs";
import { homedir } from "node:os";
import { dirname, join } from "node:path";
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

// ─── Constants (replicated from extensions — cannot import due to Turbopack constraint) ───

type RemoteChannel = "slack" | "discord" | "telegram";

const CHANNEL_ID_PATTERNS: Record<RemoteChannel, RegExp> = {
	slack: /^[A-Z0-9]{9,12}$/,
	discord: /^\d{17,20}$/,
	telegram: /^-?\d{5,20}$/,
};

const ENV_KEYS: Record<RemoteChannel, string> = {
	slack: "SLACK_BOT_TOKEN",
	discord: "DISCORD_BOT_TOKEN",
	telegram: "TELEGRAM_BOT_TOKEN",
};

const DEFAULT_TIMEOUT_MINUTES = 5;
const DEFAULT_POLL_INTERVAL_SECONDS = 5;
const MIN_TIMEOUT_MINUTES = 1;
const MAX_TIMEOUT_MINUTES = 30;
const MIN_POLL_INTERVAL_SECONDS = 2;
const MAX_POLL_INTERVAL_SECONDS = 30;

const VALID_CHANNELS: readonly RemoteChannel[] = [
	"slack",
	"discord",
	"telegram",
] as const;

// Map channel → auth.json provider ID (matches key-manager.ts PROVIDER_REGISTRY)
const AUTH_PROVIDER_IDS: Record<RemoteChannel, string> = {
	slack: "slack_bot",
	discord: "discord_bot",
	telegram: "telegram_bot",
};

// ─── Auth.json Helpers ────────────────────────────────────────────────────────

function getAuthPath(): string {
	return join(homedir(), ".sf", "agent", "auth.json");
}

function readAuthData(): Record<string, unknown> {
	const authPath = getAuthPath();
	if (!existsSync(authPath)) return {};
	try {
		const content = readFileSync(authPath, "utf-8");
		const parsed = JSON.parse(content);
		return typeof parsed === "object" && parsed !== null
			? (parsed as Record<string, unknown>)
			: {};
	} catch {
		return {};
	}
}

function writeAuthData(data: Record<string, unknown>): void {
	const authPath = getAuthPath();
	const parentDir = dirname(authPath);
	if (!existsSync(parentDir))
		mkdirSync(parentDir, { recursive: true, mode: 0o700 });
	writeFileSync(authPath, JSON.stringify(data, null, 2), "utf-8");
	chmodSync(authPath, 0o600);
}

function hasStoredBotToken(channel: RemoteChannel): boolean {
	const data = readAuthData();
	const providerId = AUTH_PROVIDER_IDS[channel];
	const entry = data[providerId];
	if (!entry) return false;
	// Could be a single credential or an array
	const creds = Array.isArray(entry) ? entry : [entry];
	return creds.some((c: unknown) => {
		if (typeof c !== "object" || c === null) return false;
		const cred = c as Record<string, unknown>;
		return (
			cred.type === "api_key" &&
			typeof cred.key === "string" &&
			cred.key.length > 0
		);
	});
}

function maskToken(token: string): string {
	if (token.length <= 8) return token.slice(0, 2) + "***" + token.slice(-2);
	return token.slice(0, 4) + "***" + token.slice(-4);
}

// ─── Helpers ──────────────────────────────────────────────────────────────────

function getPreferencesPath(): string {
	return join(homedir(), ".sf", "PREFERENCES.md");
}

function clamp(
	value: number | undefined,
	defaultVal: number,
	min: number,
	max: number,
): number {
	const v =
		typeof value === "number" && Number.isFinite(value) ? value : defaultVal;
	return Math.max(min, Math.min(max, v));
}

function isValidChannel(ch: unknown): ch is RemoteChannel {
	return (
		typeof ch === "string" && (VALID_CHANNELS as readonly string[]).includes(ch)
	);
}

/**
 * Parse YAML frontmatter from a markdown file.
 * Uses the same indexOf-based approach as parsePreferencesMarkdown() in preferences.ts.
 */
function parseFrontmatter(content: string): {
	data: Record<string, unknown>;
	body: string;
	hasFrontmatter: boolean;
} {
	const startMarker = content.startsWith("---\r\n") ? "---\r\n" : "---\n";
	if (!content.startsWith(startMarker)) {
		return { data: {}, body: content, hasFrontmatter: false };
	}
	const searchStart = startMarker.length;
	const endIdx = content.indexOf("\n---", searchStart);
	if (endIdx === -1) {
		return { data: {}, body: content, hasFrontmatter: false };
	}
	const block = content.slice(searchStart, endIdx);
	const afterFrontmatter = content.slice(endIdx + 4); // skip \n---

	try {
		const parsed = parseYaml(block.replace(/\r/g, ""));
		const data =
			typeof parsed === "object" && parsed !== null
				? (parsed as Record<string, unknown>)
				: {};
		return { data, body: afterFrontmatter, hasFrontmatter: true };
	} catch {
		return { data: {}, body: content, hasFrontmatter: false };
	}
}

/**
 * Write frontmatter data back to a markdown file, preserving the body content.
 */
function writeFrontmatter(data: Record<string, unknown>, body: string): string {
	const yamlStr = stringifyYaml(data, { lineWidth: 0 }).trimEnd();
	return `---\n${yamlStr}\n---${body}`;
}

interface RemoteQuestionsResponse {
	config: {
		channel: RemoteChannel;
		channelId: string;
		timeoutMinutes: number;
		pollIntervalSeconds: number;
	} | null;
	envVarSet: boolean;
	tokenSet: boolean;
	envVarName: string | null;
	status: string;
}

// ─── GET ──────────────────────────────────────────────────────────────────────

export async function GET(): Promise<Response> {
	try {
		const prefsPath = getPreferencesPath();

		if (!existsSync(prefsPath)) {
			const response: RemoteQuestionsResponse = {
				config: null,
				envVarSet: false,
				tokenSet: false,
				envVarName: null,
				status: "not_configured",
			};
			return Response.json(response, {
				headers: { "Cache-Control": "no-store" },
			});
		}

		const content = readFileSync(prefsPath, "utf-8");
		const { data } = parseFrontmatter(content);
		const rq = data.remote_questions as Record<string, unknown> | undefined;

		if (!rq || typeof rq !== "object" || !rq.channel) {
			const response: RemoteQuestionsResponse = {
				config: null,
				envVarSet: false,
				tokenSet: false,
				envVarName: null,
				status: "not_configured",
			};
			return Response.json(response, {
				headers: { "Cache-Control": "no-store" },
			});
		}

		const channel = rq.channel as string;
		if (!isValidChannel(channel)) {
			const response: RemoteQuestionsResponse = {
				config: null,
				envVarSet: false,
				tokenSet: false,
				envVarName: null,
				status: "invalid_channel",
			};
			return Response.json(response, {
				headers: { "Cache-Control": "no-store" },
			});
		}

		const channelId = rq.channel_id != null ? String(rq.channel_id) : "";
		const timeoutMinutes = clamp(
			rq.timeout_minutes as number | undefined,
			DEFAULT_TIMEOUT_MINUTES,
			MIN_TIMEOUT_MINUTES,
			MAX_TIMEOUT_MINUTES,
		);
		const pollIntervalSeconds = clamp(
			rq.poll_interval_seconds as number | undefined,
			DEFAULT_POLL_INTERVAL_SECONDS,
			MIN_POLL_INTERVAL_SECONDS,
			MAX_POLL_INTERVAL_SECONDS,
		);
		const envVarName = ENV_KEYS[channel];
		const envVarSet = !!process.env[envVarName];
		const tokenSet = hasStoredBotToken(channel) || envVarSet;

		const response: RemoteQuestionsResponse = {
			config: {
				channel,
				channelId,
				timeoutMinutes,
				pollIntervalSeconds,
			},
			envVarSet,
			tokenSet,
			envVarName,
			status: "configured",
		};
		return Response.json(response, {
			headers: { "Cache-Control": "no-store" },
		});
	} catch (error) {
		const message = error instanceof Error ? error.message : String(error);
		return Response.json(
			{ error: `Failed to read remote questions config: ${message}` },
			{ status: 500, headers: { "Cache-Control": "no-store" } },
		);
	}
}

// ─── POST ─────────────────────────────────────────────────────────────────────

export async function POST(request: Request): Promise<Response> {
	try {
		const body = (await request.json()) as Record<string, unknown>;
		const {
			channel,
			channelId,
			timeoutMinutes: rawTimeout,
			pollIntervalSeconds: rawPoll,
		} = body as {
			channel: unknown;
			channelId: unknown;
			timeoutMinutes: unknown;
			pollIntervalSeconds: unknown;
		};

		// Validate channel
		if (!isValidChannel(channel)) {
			return Response.json(
				{
					error: `Invalid channel type: must be one of ${VALID_CHANNELS.join(", ")}`,
				},
				{ status: 400, headers: { "Cache-Control": "no-store" } },
			);
		}

		// Validate channelId
		if (typeof channelId !== "string" || !channelId) {
			return Response.json(
				{ error: "channelId is required and must be a non-empty string" },
				{ status: 400, headers: { "Cache-Control": "no-store" } },
			);
		}

		if (!CHANNEL_ID_PATTERNS[channel].test(channelId)) {
			return Response.json(
				{
					error: `Invalid channel ID format for ${channel}. Expected pattern: ${CHANNEL_ID_PATTERNS[channel].source}`,
				},
				{ status: 400, headers: { "Cache-Control": "no-store" } },
			);
		}

		// Clamp timeout and poll interval
		const timeoutMinutes = clamp(
			rawTimeout as number | undefined,
			DEFAULT_TIMEOUT_MINUTES,
			MIN_TIMEOUT_MINUTES,
			MAX_TIMEOUT_MINUTES,
		);
		const pollIntervalSeconds = clamp(
			rawPoll as number | undefined,
			DEFAULT_POLL_INTERVAL_SECONDS,
			MIN_POLL_INTERVAL_SECONDS,
			MAX_POLL_INTERVAL_SECONDS,
		);

		// Read current preferences
		const prefsPath = getPreferencesPath();
		let data: Record<string, unknown> = {};
		let body2 = "";

		if (existsSync(prefsPath)) {
			const content = readFileSync(prefsPath, "utf-8");
			const parsed = parseFrontmatter(content);
			data = parsed.data;
			body2 = parsed.body;
		}

		// Update remote_questions block
		data.remote_questions = {
			channel,
			channel_id: channelId,
			timeout_minutes: timeoutMinutes,
			poll_interval_seconds: pollIntervalSeconds,
		};

		// Write back
		const dir = dirname(prefsPath);
		if (!existsSync(dir)) {
			mkdirSync(dir, { recursive: true });
		}
		writeFileSync(prefsPath, writeFrontmatter(data, body2), "utf-8");

		return Response.json(
			{
				success: true,
				config: { channel, channelId, timeoutMinutes, pollIntervalSeconds },
			},
			{ headers: { "Cache-Control": "no-store" } },
		);
	} catch (error) {
		const message = error instanceof Error ? error.message : String(error);
		return Response.json(
			{ error: `Failed to save remote questions config: ${message}` },
			{ status: 500, headers: { "Cache-Control": "no-store" } },
		);
	}
}

// ─── DELETE ───────────────────────────────────────────────────────────────────

export async function DELETE(): Promise<Response> {
	try {
		const prefsPath = getPreferencesPath();

		if (!existsSync(prefsPath)) {
			return Response.json(
				{ success: true },
				{ headers: { "Cache-Control": "no-store" } },
			);
		}

		const content = readFileSync(prefsPath, "utf-8");
		const { data, body, hasFrontmatter } = parseFrontmatter(content);

		if (!hasFrontmatter || !data.remote_questions) {
			return Response.json(
				{ success: true },
				{ headers: { "Cache-Control": "no-store" } },
			);
		}

		delete data.remote_questions;
		writeFileSync(prefsPath, writeFrontmatter(data, body), "utf-8");

		return Response.json(
			{ success: true },
			{ headers: { "Cache-Control": "no-store" } },
		);
	} catch (error) {
		const message = error instanceof Error ? error.message : String(error);
		return Response.json(
			{ error: `Failed to remove remote questions config: ${message}` },
			{ status: 500, headers: { "Cache-Control": "no-store" } },
		);
	}
}

// ─── PATCH (save bot token) ───────────────────────────────────────────────────

export async function PATCH(request: Request): Promise<Response> {
	try {
		const body = (await request.json()) as Record<string, unknown>;
		const { channel, token } = body as { channel: unknown; token: unknown };

		if (!isValidChannel(channel)) {
			return Response.json(
				{
					error: `Invalid channel type: must be one of ${VALID_CHANNELS.join(", ")}`,
				},
				{ status: 400, headers: { "Cache-Control": "no-store" } },
			);
		}

		if (typeof token !== "string" || !token.trim()) {
			return Response.json(
				{ error: "token is required and must be a non-empty string" },
				{ status: 400, headers: { "Cache-Control": "no-store" } },
			);
		}

		const trimmedToken = token.trim();
		const providerId = AUTH_PROVIDER_IDS[channel];

		// Read existing auth data, merge the new credential
		const authData = readAuthData();
		const existingEntry = authData[providerId];
		const existingCreds: unknown[] = existingEntry
			? Array.isArray(existingEntry)
				? existingEntry
				: [existingEntry]
			: [];

		// Replace any existing api_key credential, keep OAuth
		const oauthCreds = existingCreds.filter((c: unknown) => {
			if (typeof c !== "object" || c === null) return false;
			return (c as Record<string, unknown>).type === "oauth";
		});
		const newCred = { type: "api_key", key: trimmedToken };
		const merged = [...oauthCreds, newCred];
		authData[providerId] = merged.length === 1 ? merged[0] : merged;
		writeAuthData(authData);

		// Also set in process.env so it's available immediately
		const envVar = ENV_KEYS[channel];
		process.env[envVar] = trimmedToken;

		return Response.json(
			{ success: true, masked: maskToken(trimmedToken) },
			{ headers: { "Cache-Control": "no-store" } },
		);
	} catch (error) {
		const message = error instanceof Error ? error.message : String(error);
		return Response.json(
			{ error: `Failed to save bot token: ${message}` },
			{ status: 500, headers: { "Cache-Control": "no-store" } },
		);
	}
}
