/**
 * MCP auth helper — centralised access control for all MCP tools.
 *
 * Every tool calls resolveAccess() before touching data.
 *
 * Access matrix:
 *   public       → read open (unless forceAuth), write requires token+member
 *   permissioned → read requires any token, write requires token+member
 *   private      → read+write require token+member
 */

import { verifyToken } from "../auth";
import type { EncryptIDClaims } from "../auth";
import { resolveCallerRole, roleAtLeast } from "../spaces";
import type { SpaceRoleString } from "../spaces";
import { loadCommunity, getDocumentData, normalizeVisibility } from "../community-store";

export interface AccessResult {
	allowed: boolean;
	claims: EncryptIDClaims | null;
	role: SpaceRoleString;
	reason?: string;
}

/**
 * Resolve access for an MCP tool call.
 *
 * @param token   JWT string (may be undefined for unauthenticated callers)
 * @param space   Space slug
 * @param forWrite   true for mutating operations
 * @param forceAuth  true to always require token+member (e.g. rinbox)
 */
export async function resolveAccess(
	token: string | undefined,
	space: string,
	forWrite = false,
	forceAuth = false,
): Promise<AccessResult> {
	// Load space doc
	await loadCommunity(space);
	const data = getDocumentData(space);
	if (!data) {
		return { allowed: false, claims: null, role: "viewer", reason: "Space not found or access denied" };
	}

	const visibility = normalizeVisibility(data.meta.visibility || "private");

	// Verify token if provided
	let claims: EncryptIDClaims | null = null;
	if (token) {
		try {
			claims = await verifyToken(token);
		} catch {
			return { allowed: false, claims: null, role: "viewer", reason: "Invalid or expired token" };
		}
	}

	// Resolve caller's role in this space
	const resolved = claims ? await resolveCallerRole(space, claims) : null;
	const role: SpaceRoleString = resolved?.role ?? "viewer";
	const isMember = roleAtLeast(role, "member");

	// Write always requires token + member
	if (forWrite) {
		if (!claims) return { allowed: false, claims, role, reason: "Authentication required" };
		if (!isMember) return { allowed: false, claims, role, reason: "Space membership required" };
		return { allowed: true, claims, role };
	}

	// forceAuth → always requires token + member (e.g. email/inbox)
	if (forceAuth) {
		if (!claims) {
			// Don't reveal that the space exists for private spaces
			return { allowed: false, claims, role, reason: "Space not found or access denied" };
		}
		if (!isMember) return { allowed: false, claims, role, reason: "Space membership required" };
		return { allowed: true, claims, role };
	}

	// Read access by visibility
	switch (visibility) {
		case "public":
			return { allowed: true, claims, role };

		case "permissioned":
			if (!claims) return { allowed: false, claims, role, reason: "Authentication required" };
			return { allowed: true, claims, role };

		case "private":
			if (!claims) return { allowed: false, claims, role, reason: "Space not found or access denied" };
			if (!isMember) return { allowed: false, claims, role, reason: "Space not found or access denied" };
			return { allowed: true, claims, role };

		default:
			return { allowed: false, claims, role, reason: "Space not found or access denied" };
	}
}

/** Standard MCP error response for denied access. */
export function accessDeniedResponse(reason: string) {
	return { content: [{ type: "text" as const, text: JSON.stringify({ error: reason }) }], isError: true };
}
