/**
 * Network module — community relationship graph viewer.
 *
 * Visualizes CRM data as interactive force-directed graphs.
 * Nodes: people, companies, opportunities. Edges: relationships.
 * Syncs from Twenty CRM via GraphQL API proxy.
 */

import { Hono } from "hono";
import { renderShell, renderExternalAppShell } from "../../server/shell";
import { getModuleInfoList } from "../../shared/module";
import type { RSpaceModule } from "../../shared/module";
import { renderLanding } from "./landing";

const routes = new Hono();

const TWENTY_API_URL = process.env.TWENTY_API_URL || "https://crm.rspace.online";
const TWENTY_DEFAULT_TOKEN = process.env.TWENTY_API_TOKEN || "";

// Build token map from env vars: TWENTY_TOKEN_COMMONS_HUB -> "commons-hub"
const twentyTokens = new Map<string, string>();
for (const [key, value] of Object.entries(process.env)) {
	if (key.startsWith("TWENTY_TOKEN_") && value) {
		const slug = key.replace("TWENTY_TOKEN_", "").toLowerCase().replace(/_/g, "-");
		twentyTokens.set(slug, value);
	}
}

function getTokenForSpace(space: string): string {
	return twentyTokens.get(space) || TWENTY_DEFAULT_TOKEN;
}

// ── GraphQL helper ──
async function twentyQuery(query: string, variables?: Record<string, unknown>, space?: string) {
	const token = space ? getTokenForSpace(space) : TWENTY_DEFAULT_TOKEN;
	if (!token) return null;
	const res = await fetch(`${TWENTY_API_URL}/graphql`, {
		method: "POST",
		headers: {
			"Content-Type": "application/json",
			Authorization: `Bearer ${token}`,
		},
		body: JSON.stringify({ query, variables }),
		signal: AbortSignal.timeout(10000),
	});
	if (!res.ok) return null;
	const json = await res.json() as { data?: unknown };
	return json.data ?? null;
}

// ── Per-space cache layer (60s TTL) ──
const graphCaches = new Map<string, { data: unknown; ts: number }>();
const CACHE_TTL = 60_000;

// ── API: Health ──
routes.get("/api/health", (c) => {
	const space = c.req.param("space") || "demo";
	const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
	const token = getTokenForSpace(dataSpace);
	return c.json({ ok: true, module: "network", space, twentyConfigured: !!token });
});

// ── API: Info ──
routes.get("/api/info", (c) => {
	const space = c.req.param("space") || "demo";
	const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
	const token = getTokenForSpace(dataSpace);
	return c.json({
		module: "network",
		description: "Community relationship graph visualization",
		entityTypes: ["person", "company", "opportunity"],
		features: ["force-directed layout", "CRM sync", "real-time collaboration"],
		space,
		twentyConfigured: !!token,
	});
});

// ── API: People ──
routes.get("/api/people", async (c) => {
	const space = c.req.param("space") || "demo";
	const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
	const token = getTokenForSpace(dataSpace);
	const data = await twentyQuery(`{
		people(first: 200) {
			edges {
				node {
					id
					name { firstName lastName }
					email { primaryEmail }
					phone { primaryPhoneNumber }
					city
					company { id name { firstName lastName } }
					createdAt
				}
			}
		}
	}`, undefined, dataSpace);
	if (!data) return c.json({ people: [], error: token ? "Twenty API error" : "Twenty not configured" });
	const people = ((data as any).people?.edges || []).map((e: any) => e.node);
	c.header("Cache-Control", "public, max-age=60");
	return c.json({ people });
});

// ── API: Companies ──
routes.get("/api/companies", async (c) => {
	const space = c.req.param("space") || "demo";
	const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
	const token = getTokenForSpace(dataSpace);
	const data = await twentyQuery(`{
		companies(first: 200) {
			edges {
				node {
					id
					name
					domainName { primaryLinkUrl }
					employees
					address { addressCity addressCountry }
					createdAt
				}
			}
		}
	}`, undefined, dataSpace);
	if (!data) return c.json({ companies: [], error: token ? "Twenty API error" : "Twenty not configured" });
	const companies = ((data as any).companies?.edges || []).map((e: any) => e.node);
	c.header("Cache-Control", "public, max-age=60");
	return c.json({ companies });
});

// ── API: Graph — transform entities to node/edge format ──
routes.get("/api/graph", async (c) => {
	const space = c.req.param("space") || "demo";
	const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
	const token = getTokenForSpace(dataSpace);

	// Check per-space cache
	const cached = graphCaches.get(dataSpace);
	if (cached && Date.now() - cached.ts < CACHE_TTL) {
		c.header("Cache-Control", "public, max-age=60");
		return c.json(cached.data);
	}

	if (!token) {
		return c.json({
			nodes: [
				{ id: "demo-1", label: "Alice", type: "person", data: {} },
				{ id: "demo-2", label: "Bob", type: "person", data: {} },
				{ id: "demo-3", label: "Acme Corp", type: "company", data: {} },
			],
			edges: [
				{ source: "demo-1", target: "demo-3", type: "works_at" },
				{ source: "demo-2", target: "demo-3", type: "works_at" },
				{ source: "demo-1", target: "demo-2", type: "contact_of" },
			],
			demo: true,
		});
	}

	try {
		const data = await twentyQuery(`{
			people(first: 200) {
				edges {
					node {
						id
						name { firstName lastName }
						email { primaryEmail }
						city
						company { id name { firstName lastName } }
					}
				}
			}
			companies(first: 200) {
				edges {
					node {
						id
						name
						domainName { primaryLinkUrl }
						employees
						address { addressCity addressCountry }
					}
				}
			}
			opportunities(first: 200) {
				edges {
					node {
						id
						name
						stage
						amount { amountMicros currencyCode }
						company { id name }
						pointOfContact { id name { firstName lastName } }
					}
				}
			}
		}`, undefined, dataSpace);

		if (!data) return c.json({ nodes: [], edges: [], error: "Twenty API error" });

		const d = data as any;
		const nodes: Array<{ id: string; label: string; type: string; data: unknown }> = [];
		const edges: Array<{ source: string; target: string; type: string }> = [];
		const nodeIds = new Set<string>();

		// People → nodes
		for (const { node: p } of d.people?.edges || []) {
			const label = [p.name?.firstName, p.name?.lastName].filter(Boolean).join(" ") || "Unknown";
			nodes.push({ id: p.id, label, type: "person", data: { email: p.email?.primaryEmail, location: p.city } });
			nodeIds.add(p.id);

			// Person → Company edge
			if (p.company?.id) {
				edges.push({ source: p.id, target: p.company.id, type: "works_at" });
			}
		}

		// Companies → nodes
		for (const { node: co } of d.companies?.edges || []) {
			nodes.push({ id: co.id, label: co.name || "Unknown", type: "company", data: { domain: co.domainName?.primaryLinkUrl, employees: co.employees, location: co.address?.addressCity } });
			nodeIds.add(co.id);
		}

		// Opportunities → nodes + edges
		for (const { node: opp } of d.opportunities?.edges || []) {
			nodes.push({ id: opp.id, label: opp.name || "Opportunity", type: "opportunity", data: { stage: opp.stage, amount: opp.amount } });
			nodeIds.add(opp.id);

			if (opp.company?.id && nodeIds.has(opp.company.id)) {
				edges.push({ source: opp.id, target: opp.company.id, type: "involves" });
			}
			if (opp.pointOfContact?.id && nodeIds.has(opp.pointOfContact.id)) {
				edges.push({ source: opp.pointOfContact.id, target: opp.id, type: "involved_in" });
			}
		}

		const result = { nodes, edges, demo: false };
		graphCaches.set(dataSpace, { data: result, ts: Date.now() });
		c.header("Cache-Control", "public, max-age=60");
		return c.json(result);
	} catch (e) {
		console.error("[Network] Graph fetch error:", e);
		return c.json({ nodes: [], edges: [], error: "Graph fetch failed" });
	}
});

// ── API: Workspaces ──
routes.get("/api/workspaces", (c) => {
	return c.json([
		{ slug: "demo", name: "Demo Network", nodeCount: 0, edgeCount: 0 },
	]);
});

// ── API: Opportunities ──
routes.get("/api/opportunities", async (c) => {
	const space = c.req.param("space") || "demo";
	const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
	const token = getTokenForSpace(dataSpace);
	const data = await twentyQuery(`{
		opportunities(first: 200) {
			edges {
				node {
					id
					name
					stage
					amount { amountMicros currencyCode }
					company { id name }
					pointOfContact { id name { firstName lastName } }
					createdAt
					closeDate
				}
			}
		}
	}`, undefined, dataSpace);
	if (!data) return c.json({ opportunities: [], error: token ? "Twenty API error" : "Twenty not configured" });
	const opportunities = ((data as any).opportunities?.edges || []).map((e: any) => e.node);
	c.header("Cache-Control", "public, max-age=60");
	return c.json({ opportunities });
});

// ── CRM sub-route — embed Twenty CRM via iframe ──
routes.get("/crm", (c) => {
	const space = c.req.param("space") || "demo";
	const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
	return c.html(renderExternalAppShell({
		title: `${space} — CRM | rSpace`,
		moduleId: "rnetwork",
		spaceSlug: space,
		modules: getModuleInfoList(),
		appUrl: "https://crm.rspace.online",
		appName: "Twenty CRM",
	}));
});

// ── Page route ──
routes.get("/", (c) => {
	const space = c.req.param("space") || "demo";
	const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
	const view = c.req.query("view");

	if (view === "app") {
		return c.html(renderExternalAppShell({
			title: `${space} — CRM | rSpace`,
			moduleId: "rnetwork",
			spaceSlug: space,
			modules: getModuleInfoList(),
			appUrl: "https://crm.rspace.online",
			appName: "Twenty CRM",
		}));
	}

	return c.html(renderShell({
		title: `${space} — Network | rSpace`,
		moduleId: "rnetwork",
		spaceSlug: space,
		modules: getModuleInfoList(),
		body: `<div class="rapp-nav" style="padding:0 1rem;margin-top:8px"><span class="rapp-nav__title"></span><a href="?view=app" class="rapp-nav__btn--app-toggle">Open Full App</a></div>
<folk-graph-viewer space="${space}"></folk-graph-viewer>`,
		scripts: `<script type="module" src="/modules/rnetwork/folk-graph-viewer.js"></script>`,
		styles: `<link rel="stylesheet" href="/modules/rnetwork/network.css">`,
	}));
});

export const networkModule: RSpaceModule = {
	id: "rnetwork",
	name: "rNetwork",
	icon: "🌐",
	description: "Community relationship graph visualization with CRM sync",
	scoping: { defaultScope: 'global', userConfigurable: false },
	routes,
	landingPage: renderLanding,
	standaloneDomain: "rnetwork.online",
	externalApp: { url: "https://crm.rspace.online", name: "Twenty CRM" },
	feeds: [
		{
			id: "trust-graph",
			name: "Trust Graph",
			kind: "trust",
			description: "People, companies, and relationship edges — the community web of trust",
			filterable: true,
		},
		{
			id: "connections",
			name: "New Connections",
			kind: "trust",
			description: "Recently added people and relationship links",
		},
	],
	acceptsFeeds: ["data", "trust", "governance"],
	outputPaths: [
		{ path: "connections", name: "Connections", icon: "🤝", description: "Community member connections" },
		{ path: "groups", name: "Groups", icon: "👥", description: "Relationship groups and circles" },
	],
	subPageInfos: [
		{
			path: "crm",
			title: "Community CRM",
			icon: "📇",
			tagline: "rNetwork Tool",
			description: "Full-featured CRM for community relationship management — contacts, companies, deals, and pipelines powered by Twenty CRM.",
			features: [
				{ icon: "👤", title: "Contact Management", text: "Track people, organizations, and their roles in your community." },
				{ icon: "🔗", title: "Relationship Graph", text: "Visualize how members connect and identify key connectors." },
				{ icon: "📊", title: "Pipeline Tracking", text: "Manage opportunities and partnerships through customizable stages." },
			],
		},
	],
};
