import { createHash } from 'node:crypto'
import { CompositeDidDocumentResolver, PlcDidDocumentResolver, WebDidDocumentResolver } from '@atcute/identity-resolver'
import { json, XRPCError, XRPCRouter } from '@atcute/xrpc-server'
import { ServiceJwtVerifier } from '@atcute/xrpc-server/auth'
import { isValidWebhookSecretId, WEBHOOK_SECRET_ENCRYPTION_ERROR } from '@wispplace/atproto-utils'
import { BASE_HOST } from '@wispplace/constants'
import {
	PlaceWispV2DomainAddSite,
	PlaceWispV2DomainClaim,
	PlaceWispV2DomainClaimSubdomain,
	PlaceWispV2DomainDelete,
	PlaceWispV2DomainGetList,
	PlaceWispV2DomainGetStatus,
	PlaceWispV2DomainVerify,
	PlaceWispV2SecretCreate,
	PlaceWispV2SecretDelete,
	PlaceWispV2SecretList,
	PlaceWispV2SecretRotate,
	PlaceWispV2SiteDelete,
	PlaceWispV2SiteGetDomains,
	PlaceWispV2SiteGetList,
} from '@wispplace/lexicons/atcute'
import { createLogger } from '@wispplace/observability'
import { Elysia } from 'elysia'
import { publishDomainCacheInvalidation } from '../lib/cache-invalidation'
import {
	claimCustomDomain,
	claimDomain,
	createWebhookSecret,
	deleteCustomDomain,
	deleteWebhookSecret,
	deleteWispDomain,
	eventualRead,
	getCustomDomainInfo,
	getDomainsBySite,
	getSitesByDid,
	isDomainRegistered,
	listWebhookSecrets,
	rotateWebhookSecret,
	updateCustomDomainRkey,
	updateCustomDomainVerification,
	updateWispDomainSite,
} from '../lib/db'
import { verifyCustomDomain } from '../lib/dns-verify'
import { extractWispHandle, isValidHandle, normalizeDomain, toDomain, validateCustomDomain } from '../lib/domain-utils'
import { registerPrivateSiteMethods } from './xrpc-private-site'

const logger = createLogger('main-app')
const isLocalDev = Bun.env.LOCAL_DEV === 'true'

interface XrpcAuthContext {
	did: string
}

const DEFAULT_SERVICE_DID = isLocalDev ? 'did:web:localhost' : 'did:web:wisp.place'
const SERVICE_DID = Bun.env.SERVICE_DID ?? DEFAULT_SERVICE_DID
const serviceJwtVerifier = new ServiceJwtVerifier({
	serviceDid: SERVICE_DID as any,
	resolver: new CompositeDidDocumentResolver({
		methods: {
			// Devnet DIDs exist only in its local PLC directory.
			plc: new PlcDidDocumentResolver(
				Bun.env.WISP_PLC_DIRECTORY_URL ? { apiUrl: Bun.env.WISP_PLC_DIRECTORY_URL } : undefined,
			),
			web: new WebDidDocumentResolver(),
		},
	}),
})

const NSID_ALIASES: Record<string, string> = {
	'place.wisp.v2.domain.add-site': 'place.wisp.v2.domain.addSite',
	'place.wisp.v2.domain.addsite': 'place.wisp.v2.domain.addSite',
	'place.wisp.v2.domain.claim-subdomain': 'place.wisp.v2.domain.claimSubdomain',
	'place.wisp.v2.domain.claimsubdomain': 'place.wisp.v2.domain.claimSubdomain',
	'place.wisp.v2.domain.claimsub-domain': 'place.wisp.v2.domain.claimSubdomain',
	'place.wisp.v2.domain.get-list': 'place.wisp.v2.domain.getList',
	'place.wisp.v2.domain.getlist': 'place.wisp.v2.domain.getList',
	'place.wisp.v2.domain.getstatus': 'place.wisp.v2.domain.getStatus',
	'place.wisp.v2.domain.get-status': 'place.wisp.v2.domain.getStatus',
	'place.wisp.v2.site.delete-site': 'place.wisp.v2.site.delete',
	'place.wisp.v2.site.deletesite': 'place.wisp.v2.site.delete',
	'place.wisp.v2.site.get-domains': 'place.wisp.v2.site.getDomains',
	'place.wisp.v2.site.getdomains': 'place.wisp.v2.site.getDomains',
	'place.wisp.v2.site.get-list': 'place.wisp.v2.site.getList',
	'place.wisp.v2.site.getlist': 'place.wisp.v2.site.getList',
}

const XRPC_NSIDS = {
	addSite: 'place.wisp.v2.domain.addSite',
	getStatus: 'place.wisp.v2.domain.getStatus',
	getList: 'place.wisp.v2.domain.getList',
	siteGetDomains: 'place.wisp.v2.site.getDomains',
	siteGetList: 'place.wisp.v2.site.getList',
	claimSubdomain: 'place.wisp.v2.domain.claimSubdomain',
	claim: 'place.wisp.v2.domain.claim',
	delete: 'place.wisp.v2.domain.delete',
	verify: 'place.wisp.v2.domain.verify',
	deleteSite: 'place.wisp.v2.site.delete',
	secretCreate: 'place.wisp.v2.secret.create',
	secretList: 'place.wisp.v2.secret.list',
	secretDelete: 'place.wisp.v2.secret.delete',
	secretRotate: 'place.wisp.v2.secret.rotate',
	privateSiteCreate: 'place.wisp.v2.privateSite.create',
	privateSiteList: 'place.wisp.v2.privateSite.list',
	privateSiteDelete: 'place.wisp.v2.privateSite.delete',
	privateSiteCreateShare: 'place.wisp.v2.privateSite.createShare',
	privateSiteListShares: 'place.wisp.v2.privateSite.listShares',
	privateSiteRevokeShare: 'place.wisp.v2.privateSite.revokeShare',
} as const

const toIsoFromEpoch = (epoch: unknown): string | undefined => {
	let numeric: number | undefined

	if (typeof epoch === 'number') {
		numeric = epoch
	} else if (typeof epoch === 'string') {
		numeric = Number(epoch)
	}

	if (!Number.isFinite(numeric)) {
		return undefined
	}

	const ms = numeric! < 1_000_000_000_000 ? numeric! * 1000 : numeric!
	const date = new Date(ms)

	if (Number.isNaN(date.getTime())) {
		return undefined
	}

	return date.toISOString()
}

const mapSiteDomains = (
	mappedDomains: Array<{
		type: 'wisp' | 'custom'
		domain: string
		verified?: boolean
	}>,
): PlaceWispV2SiteGetDomains.SiteDomain[] => {
	return mappedDomains
		.map((entry) => {
			const verified = entry.type === 'wisp' || Boolean(entry.verified)
			return {
				domain: entry.domain,
				kind: entry.type,
				status: verified ? ('verified' as const) : ('pendingVerification' as const),
				verified,
			}
		})
		.sort((a, b) => a.domain.localeCompare(b.domain))
}

type DidString = `did:${string}:${string}`

const buildCustomDnsInstructions = (domain: string, did: DidString, challengeId: string) => {
	return {
		challengeId,
		txtName: `_wisp.${domain}`,
		txtValue: did,
		cnameTarget: `${challengeId}.dns.${BASE_HOST}`,
	}
}

const authRequired = (): never => {
	throw new XRPCError({ status: 401, error: 'AuthenticationRequired', description: 'authentication required' })
}

const invalidDomain = (description: string): never => {
	throw new XRPCError({
		status: 400,
		error: 'InvalidDomain',
		description,
	})
}

const alreadyClaimed = (description: string): never => {
	throw new XRPCError({
		status: 409,
		error: 'AlreadyClaimed',
		description,
	})
}

const domainLimitReached = (): never => {
	throw new XRPCError({
		status: 400,
		error: 'DomainLimitReached',
		description: 'free tier users can claim up to 3 wisp subdomains',
	})
}

const notFound = (description = 'domain not found'): never => {
	throw new XRPCError({
		status: 404,
		error: 'NotFound',
		description,
	})
}

const invalidRequest = (description: string): never => {
	throw new XRPCError({
		status: 400,
		error: 'InvalidRequest',
		description,
	})
}

const alreadyExists = (description: string): never => {
	throw new XRPCError({
		status: 409,
		error: 'AlreadyExists',
		description,
	})
}

const webhookSecretEncryptionUnavailable = (): never => {
	throw new XRPCError({
		status: 503,
		error: 'WebhookSecretEncryptionUnavailable',
		description: WEBHOOK_SECRET_ENCRYPTION_ERROR,
	})
}

const isWebhookSecretEncryptionUnavailable = (error: unknown): boolean =>
	error instanceof Error && error.message === WEBHOOK_SECRET_ENCRYPTION_ERROR

const requireAuthenticated = (auth: XrpcAuthContext | undefined): XrpcAuthContext => {
	if (!auth) {
		authRequired()
	}

	return auth!
}

const authRequiredWith = (description: string): never => {
	throw new XRPCError({ status: 401, error: 'AuthenticationRequired', description })
}

const resolveServiceAuth = async (request: Request, nsid: string): Promise<XrpcAuthContext | undefined> => {
	const authorization = request.headers.get('authorization')
	if (!authorization) {
		return undefined
	}

	if (!authorization.startsWith('Bearer ')) {
		authRequiredWith('missing or invalid authorization header')
	}

	const jwt = authorization.slice('Bearer '.length).trim()
	if (!jwt) {
		authRequiredWith('missing service authorization token')
	}

	const result = await serviceJwtVerifier.verify(jwt, { lxm: nsid as any })
	if (!result.ok) {
		authRequiredWith(result.error.description)
		throw new Error('unreachable')
	}

	return { did: result.value.issuer }
}

const serializeBodyForForwarding = (body: unknown): string | undefined => {
	if (body === undefined || body === null) {
		return undefined
	}

	if (typeof body === 'string') {
		return body
	}

	return JSON.stringify(body)
}

const prepareXrpcRequest = async (request: Request, parsedBody: unknown): Promise<Request> => {
	if (request.method === 'GET' || request.method === 'HEAD') {
		return request
	}

	// Reconstructing multipart would discard its boundary-delimited body.
	if ((request.headers.get('content-type') ?? '').toLowerCase().includes('multipart/form-data')) {
		return request
	}

	const headers = new Headers(request.headers)
	headers.delete('content-length')

	let bodyText: string | undefined

	if (!request.bodyUsed) {
		try {
			bodyText = await request.text()
		} catch {
			bodyText = undefined
		}
	}

	if (bodyText === undefined) {
		bodyText = serializeBodyForForwarding(parsedBody)
		if (bodyText !== undefined && !headers.has('content-type')) {
			headers.set('content-type', 'application/json')
		}
	}

	return new Request(request.url, {
		method: request.method,
		headers,
		body: bodyText,
	})
}

const normalizeNsidPath = (request: Request): { request: Request; rawNsid: string; nsid: string } => {
	const url = new URL(request.url)
	const rawNsidFull = url.pathname.startsWith('/xrpc/') ? url.pathname.slice('/xrpc/'.length) : url.pathname
	const rawNsid = rawNsidFull.replace(/^\/+|\/+$/g, '')
	const nsid = NSID_ALIASES[rawNsid] ?? rawNsid

	if (nsid === rawNsid) {
		return { request, rawNsid, nsid }
	}

	url.pathname = `/xrpc/${nsid}`

	return {
		request: new Request(url.toString(), request),
		rawNsid,
		nsid,
	}
}

const withNsid = <T extends { nsid: string }>(schema: T, nsid: string): T => {
	if (schema.nsid === nsid) {
		return schema
	}

	return { ...schema, nsid } as T
}

const addQueryWithAliases = (
	router: XRPCRouter,
	schema: { nsid: string },
	aliases: readonly string[],
	config: { handler: (ctx: any) => Promise<Response> | Response },
) => {
	const seen = new Set<string>()
	for (const nsid of [schema.nsid, ...aliases]) {
		if (seen.has(nsid)) {
			continue
		}
		seen.add(nsid)
		router.addQuery(withNsid(schema as any, nsid), config as any)
	}
}

const addProcedureWithAliases = (
	router: XRPCRouter,
	schema: { nsid: string },
	aliases: readonly string[],
	config: { handler: (ctx: any) => Promise<Response> | Response },
) => {
	const seen = new Set<string>()
	for (const nsid of [schema.nsid, ...aliases]) {
		if (seen.has(nsid)) {
			continue
		}
		seen.add(nsid)
		router.addProcedure(withNsid(schema as any, nsid), config as any)
	}
}

const claimWispSubdomain = async (did: DidString, input: { handle: string; siteRkey?: string }) => {
	const handle = input.handle.trim().toLowerCase()
	if (!isValidHandle(handle)) {
		invalidDomain('invalid wisp subdomain handle')
	}

	const domain = toDomain(handle)
	const existing = await isDomainRegistered(domain)
	if (existing.registered && existing.did !== did) {
		alreadyClaimed('domain is already claimed')
	}

	if (existing.registered && existing.did === did) {
		if (input.siteRkey !== undefined) {
			await updateWispDomainSite(domain, input.siteRkey)
			await publishDomainCacheInvalidation(domain, 'wisp')
		}

		return json({
			domain,
			kind: 'wisp',
			status: 'verified',
			siteRkey: input.siteRkey ?? existing.rkey ?? undefined,
		})
	}

	try {
		await claimDomain(did, handle)
	} catch (err) {
		const message = err instanceof Error ? err.message : ''

		if (message === 'domain_limit_reached') {
			domainLimitReached()
		}
		if (message === 'invalid_handle') {
			invalidDomain('invalid wisp subdomain handle')
		}

		alreadyClaimed('domain is already claimed')
	}

	if (input.siteRkey !== undefined) {
		await updateWispDomainSite(domain, input.siteRkey)
	}
	await publishDomainCacheInvalidation(domain, 'wisp')

	return json({
		domain,
		kind: 'wisp',
		status: 'verified',
		siteRkey: input.siteRkey,
	})
}

const claimCustomDomainForDid = async (did: DidString, input: { domain: string; siteRkey?: string }) => {
	const domain = normalizeDomain(input.domain)
	if (domain.length === 0) {
		invalidDomain('domain is required')
	}

	if (extractWispHandle(domain) !== null) {
		invalidDomain('wisp subdomains must be claimed via place.wisp.v2.domain.claimSubdomain')
	}

	const customError = validateCustomDomain(domain)
	if (customError !== null) {
		invalidDomain(customError)
	}

	const existing = await getCustomDomainInfo(domain)
	if (existing?.verified && existing.did !== did) {
		alreadyClaimed('domain is already claimed')
	}

	if (existing && existing.did === did) {
		if (input.siteRkey !== undefined) {
			await updateCustomDomainRkey(existing.id, input.siteRkey)
			await publishDomainCacheInvalidation(domain, 'custom', existing.id)
		}

		const status = existing.verified ? 'verified' : 'pendingVerification'

		return json({
			domain,
			kind: 'custom',
			status,
			siteRkey: input.siteRkey ?? existing.rkey ?? undefined,
			...buildCustomDnsInstructions(domain, did, existing.id),
		})
	}

	const challengeId = createHash('sha256').update(`${did}:${domain}`).digest('hex').substring(0, 16)

	try {
		await claimCustomDomain(did, domain, challengeId, input.siteRkey ?? null)
	} catch {
		alreadyClaimed('domain is already claimed')
	}
	await publishDomainCacheInvalidation(domain, 'custom', challengeId)

	return json({
		domain,
		kind: 'custom',
		status: 'pendingVerification',
		siteRkey: input.siteRkey,
		...buildCustomDnsInstructions(domain, did, challengeId),
	})
}

const mapDomainToSiteForDid = async (did: DidString, input: { domain: string; siteRkey: string }) => {
	const domain = normalizeDomain(input.domain)
	if (domain.length === 0) {
		invalidDomain('domain is required')
	}

	const siteRkey = input.siteRkey.trim()
	if (siteRkey.length === 0) {
		invalidRequest('siteRkey is required')
	}

	const sites = await getSitesByDid(did)
	const ownsSite = sites.some((entry: { rkey: string }) => entry.rkey === siteRkey)
	if (!ownsSite) {
		notFound('site not found')
	}

	const existing = await isDomainRegistered(domain)
	if (!existing.registered || existing.did !== did) {
		notFound('domain not found')
	}

	if (existing.type === 'wisp') {
		await updateWispDomainSite(domain, siteRkey)
		await publishDomainCacheInvalidation(domain, 'wisp')

		return json({
			domain,
			kind: 'wisp',
			status: 'verified',
			siteRkey,
			mapped: true,
		})
	}

	const custom = await getCustomDomainInfo(domain)
	if (!custom || custom.did !== did) {
		notFound('domain not found')
	}

	await updateCustomDomainRkey(custom.id as string, siteRkey)
	await publishDomainCacheInvalidation(domain, 'custom', custom.id as string)

	return json({
		domain,
		kind: 'custom',
		status: custom.verified ? 'verified' : 'pendingVerification',
		siteRkey,
		mapped: true,
	})
}

const deleteSiteForDid = async (did: DidString, input: { siteRkey: string }) => {
	const siteRkey = input.siteRkey.trim()
	if (siteRkey.length === 0) {
		invalidRequest('siteRkey is required')
	}

	const sites = await getSitesByDid(did)
	const ownsSite = sites.some((entry: { rkey: string }) => entry.rkey === siteRkey)
	if (!ownsSite) {
		notFound('site not found')
	}

	const mappedDomains = await getDomainsBySite(did, siteRkey)

	const unmappedDomains: PlaceWispV2SiteDelete.UnmappedDomain[] = []

	for (const mapped of mappedDomains as Array<{
		type: 'wisp' | 'custom'
		domain: string
		id?: string
		verified?: boolean
	}>) {
		if (mapped.type === 'wisp') {
			await updateWispDomainSite(mapped.domain, null)
			unmappedDomains.push({ domain: mapped.domain, kind: 'wisp', status: 'verified' })
			continue
		}

		if (mapped.id) {
			await updateCustomDomainRkey(mapped.id, null)
			unmappedDomains.push({
				domain: mapped.domain,
				kind: 'custom',
				status: mapped.verified ? 'verified' : 'pendingVerification',
			})
		}
	}

	return json({
		siteRkey,
		deleted: true,
		unmappedDomains: unmappedDomains.sort((a, b) => a.domain.localeCompare(b.domain)),
	})
}

export const xrpcRoutes = () => {
	const authByRequest = new WeakMap<Request, XrpcAuthContext>()
	const router = new XRPCRouter()
	const registeredNsids = [
		XRPC_NSIDS.addSite,
		XRPC_NSIDS.getStatus,
		XRPC_NSIDS.getList,
		XRPC_NSIDS.siteGetList,
		XRPC_NSIDS.claimSubdomain,
		XRPC_NSIDS.claim,
		XRPC_NSIDS.delete,
		XRPC_NSIDS.deleteSite,
		XRPC_NSIDS.secretCreate,
		XRPC_NSIDS.secretList,
		XRPC_NSIDS.secretDelete,
		XRPC_NSIDS.secretRotate,
		XRPC_NSIDS.privateSiteCreate,
		XRPC_NSIDS.privateSiteList,
		XRPC_NSIDS.privateSiteDelete,
		XRPC_NSIDS.privateSiteCreateShare,
		XRPC_NSIDS.privateSiteListShares,
		XRPC_NSIDS.privateSiteRevokeShare,
	]

	registerPrivateSiteMethods({
		router,
		requireDid: (request) => requireAuthenticated(authByRequest.get(request)).did,
	})

	addProcedureWithAliases(
		router,
		withNsid(PlaceWispV2DomainAddSite.mainSchema as any, XRPC_NSIDS.addSite),
		['place.wisp.v2.domain.addsite', 'place.wisp.v2.domain.add-site'],
		{
			async handler({ input, request }) {
				const auth = requireAuthenticated(authByRequest.get(request))
				const did = auth.did as DidString

				return mapDomainToSiteForDid(did, {
					domain: input.domain,
					siteRkey: input.siteRkey,
				})
			},
		},
	)

	addQueryWithAliases(
		router,
		withNsid(PlaceWispV2DomainGetStatus.mainSchema as any, XRPC_NSIDS.getStatus),
		['place.wisp.v2.domain.getstatus', 'place.wisp.v2.domain.get-status'],
		{
			async handler({ params, request }) {
				const domain = normalizeDomain(params.domain)
				const auth = authByRequest.get(request)

				if (domain.length === 0) {
					invalidDomain('domain parameter is required')
				}

				// Query-only status may use the explicitly eventual presentation replica.
				const { registration: info, customDomain } = await eventualRead.getDomainStatus(domain)
				if (!info.registered) {
					return json({
						domain,
						status: 'unclaimed',
					})
				}

				const kind = info.type
				const ownedByCaller = auth ? auth.did === info.did : undefined

				if (auth && ownedByCaller === false && (kind === 'wisp' || (kind === 'custom' && Boolean(info.verified)))) {
					return json({
						domain,
						kind,
						status: 'alreadyClaimed',
						verified: kind === 'custom' ? Boolean(info.verified) : true,
						siteRkey: info.rkey ?? undefined,
					})
				}

				if (kind === 'custom') {
					const verified = Boolean(customDomain?.verified)

					return json({
						domain,
						kind,
						status: verified ? 'verified' : 'pendingVerification',
						verified,
						siteRkey: info.rkey ?? undefined,
						lastCheckedAt: toIsoFromEpoch(customDomain?.last_verified_at),
					})
				}

				return json({
					domain,
					kind,
					status: 'verified',
					verified: true,
					siteRkey: info.rkey ?? undefined,
				})
			},
		},
	)

	addQueryWithAliases(
		router,
		withNsid(PlaceWispV2DomainGetList.mainSchema as any, XRPC_NSIDS.getList),
		['place.wisp.v2.domain.getlist', 'place.wisp.v2.domain.get-list'],
		{
			async handler({ request }) {
				const auth = requireAuthenticated(authByRequest.get(request))
				const did = auth.did as DidString

				// Query-only list may use the explicitly eventual presentation replica.
				const { wispDomains, customDomains } = await eventualRead.getDomainsForDid(did)

				const domains: PlaceWispV2DomainGetList.DomainSummary[] = [
					...wispDomains.map((entry: { domain: string; rkey: string | null }) => ({
						domain: entry.domain,
						kind: 'wisp' as const,
						status: 'verified' as const,
						verified: true,
						siteRkey: entry.rkey ?? undefined,
					})),
					...customDomains.map(
						(entry: {
							domain: string
							verified: boolean
							rkey: string | null
							last_verified_at?: number | string | null
						}) => {
							const verified = Boolean(entry.verified)
							return {
								domain: entry.domain,
								kind: 'custom' as const,
								status: verified ? ('verified' as const) : ('pendingVerification' as const),
								verified,
								siteRkey: entry.rkey ?? undefined,
								lastCheckedAt: toIsoFromEpoch(entry.last_verified_at),
							}
						},
					),
				].sort((a, b) => a.domain.localeCompare(b.domain))

				return json({ domains })
			},
		},
	)

	addQueryWithAliases(
		router,
		withNsid(PlaceWispV2SiteGetDomains.mainSchema as any, XRPC_NSIDS.siteGetDomains),
		['place.wisp.v2.site.getdomains', 'place.wisp.v2.site.get-domains'],
		{
			async handler({ params }) {
				// This public query only renders mappings; it never authorizes a mutation.
				const domains = mapSiteDomains(await eventualRead.getDomainsForSite(params.did, params.rkey))
				return json({ domains })
			},
		},
	)

	addQueryWithAliases(
		router,
		withNsid(PlaceWispV2SiteGetList.mainSchema as any, XRPC_NSIDS.siteGetList),
		['place.wisp.v2.site.getlist', 'place.wisp.v2.site.get-list'],
		{
			async handler({ request }) {
				const auth = requireAuthenticated(authByRequest.get(request))
				const did = auth.did as DidString

				// Query-only site listing may use the explicitly eventual presentation replica.
				// The repository batches site rows and domain badges into one read query.
				const sites = await eventualRead.getSitesWithDomainsForDid(did)
				const siteSummaries = sites.map((site) => ({
					siteRkey: site.rkey,
					displayName: site.display_name ?? undefined,
					createdAt: toIsoFromEpoch(site.created_at),
					updatedAt: toIsoFromEpoch(site.updated_at),
					domains: mapSiteDomains(site.domains),
				}))

				return json({ sites: siteSummaries })
			},
		},
	)

	addProcedureWithAliases(
		router,
		withNsid(PlaceWispV2DomainClaimSubdomain.mainSchema as any, XRPC_NSIDS.claimSubdomain),
		['place.wisp.v2.domain.claimsubdomain', 'place.wisp.v2.domain.claim-subdomain'],
		{
			async handler({ input, request }) {
				const auth = requireAuthenticated(authByRequest.get(request))
				const did = auth.did as DidString

				return claimWispSubdomain(did, {
					handle: input.handle,
					siteRkey: input.siteRkey,
				})
			},
		},
	)

	addProcedureWithAliases(router, withNsid(PlaceWispV2DomainClaim.mainSchema as any, XRPC_NSIDS.claim), [], {
		async handler({ input, request }) {
			const auth = requireAuthenticated(authByRequest.get(request))
			const did = auth.did as DidString

			return claimCustomDomainForDid(did, {
				domain: input.domain,
				siteRkey: input.siteRkey,
			})
		},
	})

	addProcedureWithAliases(router, withNsid(PlaceWispV2DomainDelete.mainSchema as any, XRPC_NSIDS.delete), [], {
		async handler({ input, request }) {
			const auth = requireAuthenticated(authByRequest.get(request))
			const did = auth.did as DidString

			const domain = normalizeDomain(input.domain)
			if (domain.length === 0) {
				invalidDomain('domain is required')
			}

			const existing = await isDomainRegistered(domain)
			if (!existing.registered) {
				notFound()
			}

			if (existing.did !== did) {
				notFound()
			}

			if (existing.type === 'wisp') {
				await deleteWispDomain(domain)
				await publishDomainCacheInvalidation(domain, 'wisp')
			} else {
				const custom = await getCustomDomainInfo(domain)
				if (!custom || custom.did !== did) {
					notFound()
				}

				await deleteCustomDomain(custom.id as string)
				await publishDomainCacheInvalidation(domain, 'custom', custom.id as string)
			}

			return json({
				domain,
				deleted: true,
			})
		},
	})

	addProcedureWithAliases(router, withNsid(PlaceWispV2DomainVerify.mainSchema as any, XRPC_NSIDS.verify), [], {
		async handler({ input, request }) {
			const auth = requireAuthenticated(authByRequest.get(request))
			const did = auth.did as DidString

			const domain = normalizeDomain(input.domain)
			if (domain.length === 0) {
				invalidDomain('domain is required')
			}

			// Only custom domains require DNS verification; wisp subdomains are
			// implicitly verified, so there is nothing to check for those.
			const registered = await isDomainRegistered(domain)
			if (!registered.registered || registered.type !== 'custom') {
				notFound()
			}

			const custom = await getCustomDomainInfo(domain)
			if (!custom || custom.did !== did) {
				notFound()
			}

			const id = custom.id as string
			const result = await verifyCustomDomain(domain, did, id)

			await updateCustomDomainVerification(id, result.verified)
			await publishDomainCacheInvalidation(domain, 'custom', id)

			return json({
				domain,
				kind: 'custom',
				status: result.verified ? 'verified' : 'pendingVerification',
				verified: result.verified,
				error: result.verified ? undefined : result.error,
				warning: result.warning,
				txtFound: result.found?.txt?.join(', '),
				cnameFound: result.found?.cname,
			})
		},
	})

	addProcedureWithAliases(
		router,
		withNsid(PlaceWispV2SiteDelete.mainSchema as any, XRPC_NSIDS.deleteSite),
		['place.wisp.v2.site.deletesite', 'place.wisp.v2.site.delete-site'],
		{
			async handler({ input, request }) {
				const auth = requireAuthenticated(authByRequest.get(request))
				const did = auth.did as DidString

				return deleteSiteForDid(did, {
					siteRkey: input.siteRkey,
				})
			},
		},
	)

	addProcedureWithAliases(router, withNsid(PlaceWispV2SecretCreate.mainSchema as any, XRPC_NSIDS.secretCreate), [], {
		async handler({ input, request }) {
			const auth = requireAuthenticated(authByRequest.get(request))
			const name = input.name
			if (!isValidWebhookSecretId(name)) invalidRequest('invalid secret name')
			try {
				const { token, createdAt } = await createWebhookSecret(auth.did, name)
				return json({ name, token, createdAt })
			} catch (error) {
				if (isWebhookSecretEncryptionUnavailable(error)) webhookSecretEncryptionUnavailable()
				if (error instanceof Error && error.message === 'already_exists') {
					return alreadyExists('a secret with that name already exists')
				}
				throw new XRPCError({
					status: 500,
					error: 'InternalServerError',
					description: 'webhook secret operation failed',
				})
			}
		},
	})

	addQueryWithAliases(router, withNsid(PlaceWispV2SecretList.mainSchema as any, XRPC_NSIDS.secretList), [], {
		async handler({ request }) {
			const auth = requireAuthenticated(authByRequest.get(request))
			// Secret metadata stays strongly consistent on the primary database.
			const secrets = await listWebhookSecrets(auth.did)
			return json({ secrets })
		},
	})

	addProcedureWithAliases(router, withNsid(PlaceWispV2SecretDelete.mainSchema as any, XRPC_NSIDS.secretDelete), [], {
		async handler({ input, request }) {
			const auth = requireAuthenticated(authByRequest.get(request))
			const name = input.name
			if (!isValidWebhookSecretId(name)) invalidRequest('invalid secret name')
			const deleted = await deleteWebhookSecret(auth.did, name)
			if (!deleted) notFound('secret not found')
			return json({})
		},
	})

	addProcedureWithAliases(router, withNsid(PlaceWispV2SecretRotate.mainSchema as any, XRPC_NSIDS.secretRotate), [], {
		async handler({ input, request }) {
			const auth = requireAuthenticated(authByRequest.get(request))
			const name = input.name
			if (!isValidWebhookSecretId(name)) invalidRequest('invalid secret name')
			let result: Awaited<ReturnType<typeof rotateWebhookSecret>>
			try {
				result = await rotateWebhookSecret(auth.did, name)
			} catch (error) {
				if (isWebhookSecretEncryptionUnavailable(error)) webhookSecretEncryptionUnavailable()
				throw new XRPCError({
					status: 500,
					error: 'InternalServerError',
					description: 'webhook secret operation failed',
				})
			}
			if (!result) return notFound('secret not found')
			return json({ name, token: result.token, rotatedAt: result.rotatedAt })
		},
	})

	const schemaNsids = {
		addSite: (PlaceWispV2DomainAddSite.mainSchema as any).nsid,
		getStatus: (PlaceWispV2DomainGetStatus.mainSchema as any).nsid,
		siteGetDomains: (PlaceWispV2SiteGetDomains.mainSchema as any).nsid,
		getList: (PlaceWispV2DomainGetList.mainSchema as any).nsid,
		siteGetList: (PlaceWispV2SiteGetList.mainSchema as any).nsid,
		claimSubdomain: (PlaceWispV2DomainClaimSubdomain.mainSchema as any).nsid,
		claim: (PlaceWispV2DomainClaim.mainSchema as any).nsid,
		delete: (PlaceWispV2DomainDelete.mainSchema as any).nsid,
		verify: (PlaceWispV2DomainVerify.mainSchema as any).nsid,
		deleteSite: (PlaceWispV2SiteDelete.mainSchema as any).nsid,
	}
	logger.info('[XRPC] Registered methods', {
		expectedNsids: registeredNsids,
		schemaNsids,
	})
	if (isLocalDev) {
		console.log('[XRPC] Registered methods', {
			expectedNsids: registeredNsids,
			schemaNsids,
		})
	}

	const handleXrpcRequest = async (request: Request, body: unknown): Promise<Response> => {
		const startedAt = Date.now()
		let xrpcRequest: Request | undefined
		let nsid = ''
		let rawNsid = ''
		let auth: XrpcAuthContext | undefined

		try {
			const preparedRequest = await prepareXrpcRequest(request, body)
			const normalized = normalizeNsidPath(preparedRequest)
			xrpcRequest = normalized.request
			rawNsid = normalized.rawNsid
			nsid = normalized.nsid

			const authorization = xrpcRequest.headers.get('authorization')
			const origin = xrpcRequest.headers.get('origin') ?? '-'
			const authScheme = authorization ? authorization.split(' ')[0] : '-'
			logger.info('[XRPC] Incoming', {
				method: xrpcRequest.method,
				rawNsid,
				nsid,
				origin,
				hasAuth: Boolean(authorization),
				scheme: authScheme,
			})
			if (isLocalDev) {
				console.log('[XRPC] Incoming', {
					method: xrpcRequest.method,
					rawNsid,
					nsid,
					origin,
					hasAuth: Boolean(authorization),
					scheme: authScheme,
				})
			}

			auth = await resolveServiceAuth(xrpcRequest, nsid)
			if (auth) {
				authByRequest.set(xrpcRequest, auth)
			}

			const response = await router.fetch(xrpcRequest)

			if (!response.ok) {
				let responseData: unknown
				try {
					responseData = await response.clone().json()
				} catch {
					responseData = await response.clone().text()
				}

				logger.warn('[XRPC] Failed', {
					method: xrpcRequest.method,
					rawNsid,
					nsid,
					status: response.status,
					did: auth?.did ?? '-',
					durationMs: Date.now() - startedAt,
					origin: xrpcRequest.headers.get('origin') ?? undefined,
					requestBodyUsed: request.bodyUsed,
					error: responseData,
				})
			} else {
				logger.info('[XRPC] Succeeded', {
					method: xrpcRequest.method,
					rawNsid,
					nsid,
					status: response.status,
					did: auth?.did ?? '-',
					durationMs: Date.now() - startedAt,
				})
			}

			return response
		} catch (err) {
			logger.error('[XRPC] Handler error', err, {
				method: xrpcRequest?.method ?? request.method,
				rawNsid: rawNsid || '-',
				nsid: nsid || '-',
				durationMs: Date.now() - startedAt,
				error: err instanceof Error ? err.message : String(err),
				origin: request.headers.get('origin') ?? undefined,
			})
			throw err
		} finally {
			if (xrpcRequest) {
				authByRequest.delete(xrpcRequest)
			}
		}
	}

	return new Elysia()
		.all('/xrpc/:nsid', ({ body, request }) => handleXrpcRequest(request, body), { parse: 'none' })
		.all(
			'/xrpc/:nsid/',
			async ({ body, request }) => {
				const url = new URL(request.url)
				if (url.pathname.endsWith('/') && url.pathname.length > '/xrpc/'.length) {
					url.pathname = url.pathname.slice(0, -1)
				}

				const rewritten = new Request(url.toString(), request)
				return handleXrpcRequest(rewritten, body)
			},
			{ parse: 'none' },
		)
}
