import { browser } from '$app/environment';
import {
	CompositeDidDocumentResolver,
	LocalActorResolver,
	PlcDidDocumentResolver,
	WebDidDocumentResolver,
	XrpcHandleResolver
} from '@atcute/identity-resolver';
import type { ActorIdentifier, Did } from '@atcute/lexicons/syntax';
import {
	OAuthUserAgent,
	configureOAuth,
	createAuthorizationUrl,
	deleteStoredSession,
	finalizeAuthorization,
	getSession,
	listStoredSessions
} from '@atcute/oauth-browser-client';
import { getContext } from 'svelte';
import { SvelteURL, SvelteURLSearchParams } from 'svelte/reactivity';
import oauthMetadata from '../../static/oauth-client-metadata.json';

export const AUTH_KEY = Symbol('auth');
const CURRENT_DID_KEY = 'tangled.currentDid';
const CURRENT_HANDLE_KEY = 'tangled.currentHandle';
const DEFAULT_APPVIEW_SERVICE = 'https://bobbin.klbr.net';
const DEV_REDIRECT_URI = 'http://127.0.0.1:5173/oauth/callback';
const DEV_CLIENT_ID = `http://localhost?redirect_uri=${encodeURIComponent(DEV_REDIRECT_URI)}&scope=${encodeURIComponent(oauthMetadata.scope)}`;

const identityResolver = new LocalActorResolver({
	handleResolver: new XrpcHandleResolver({
		serviceUrl: 'https://public.api.bsky.app'
	}),
	didDocumentResolver: new CompositeDidDocumentResolver({
		methods: {
			plc: new PlcDidDocumentResolver(),
			web: new WebDidDocumentResolver()
		}
	})
});

let configured = false;

export interface AuthProfile {
	did: Did;
	handle: string;
	avatar?: string;
}

export interface CurrentUser {
	did: Did;
	handle: string;
	avatar?: string;
}

export interface Auth {
	readonly agent: OAuthUserAgent | null;
	readonly currentDid: Did | null;
	readonly profile: AuthProfile | null;
	readonly error: string | null;
	readonly profileLoading: boolean;
	readonly authenticating: boolean;
	readonly currentUser: CurrentUser | null;
	refresh(): Promise<void>;
	signIn(identifier: string, returnTo?: string): Promise<void>;
	completeSignIn(): Promise<string>;
	signOut(): Promise<void>;
}

type MiniDoc = {
	did: Did;
	handle: string;
	pds?: string;
	avatar?: string;
};

type OAuthSession = ConstructorParameters<typeof OAuthUserAgent>[0];

const ENV_OAUTH_REDIRECT_URI = import.meta.env.VITE_OAUTH_REDIRECT_URI as string | undefined;
const HAS_LOCALHOST_REDIRECT = ENV_OAUTH_REDIRECT_URI?.includes('://localhost') ?? false;
const OAUTH_CLIENT_ID = HAS_LOCALHOST_REDIRECT
	? DEV_CLIENT_ID
	: ((import.meta.env.VITE_OAUTH_CLIENT_ID as string | undefined) ?? DEV_CLIENT_ID);
const OAUTH_REDIRECT_URI = HAS_LOCALHOST_REDIRECT
	? DEV_REDIRECT_URI
	: (ENV_OAUTH_REDIRECT_URI ?? DEV_REDIRECT_URI);
const OAUTH_SCOPE = (import.meta.env.VITE_OAUTH_SCOPE as string | undefined) ?? oauthMetadata.scope;
const APPVIEW_SERVICE =
	(import.meta.env.VITE_TANGLED_APPVIEW_SERVICE as string | undefined)?.replace(/\/+$/, '') ??
	DEFAULT_APPVIEW_SERVICE;

const configure = () => {
	if (!browser || configured) return;

	configureOAuth({
		metadata: {
			client_id: OAUTH_CLIENT_ID,
			redirect_uri: OAUTH_REDIRECT_URI
		},
		identityResolver
	});

	configured = true;
};

const readStoredDid = (): Did | null => {
	if (!browser) return null;
	const value = localStorage.getItem(CURRENT_DID_KEY);
	return value?.startsWith('did:') ? (value as Did) : null;
};

const persistAuth = (did: string, handle: string) => {
	if (!browser) return;
	localStorage.setItem(CURRENT_DID_KEY, did);
	localStorage.setItem(CURRENT_HANDLE_KEY, handle);
	const secure = location.protocol === 'https:' ? '; secure' : '';
	const attrs = `; path=/; max-age=31536000; samesite=lax${secure}`;
	document.cookie = `${CURRENT_DID_KEY}=${encodeURIComponent(did)}${attrs}`;
	document.cookie = `${CURRENT_HANDLE_KEY}=${encodeURIComponent(handle)}${attrs}`;
};

const clearAuth = () => {
	if (!browser) return;
	localStorage.removeItem(CURRENT_DID_KEY);
	localStorage.removeItem(CURRENT_HANDLE_KEY);
	document.cookie = `${CURRENT_DID_KEY}=; path=/; max-age=0; samesite=lax`;
	document.cookie = `${CURRENT_HANDLE_KEY}=; path=/; max-age=0; samesite=lax`;
};

const errorMessage = (cause: unknown) => {
	const message = cause instanceof Error ? cause.message : String(cause);
	return message.toLowerCase().includes('unknown state')
		? 'Could not resume OAuth state. In local development, start login from http://127.0.0.1:5173 instead of localhost.'
		: message;
};

const resolveProfile = async (identifier: string): Promise<AuthProfile | null> => {
	try {
		const url = new URL('/xrpc/com.bad-example.identity.resolveMiniDoc', APPVIEW_SERVICE);
		url.searchParams.set('identifier', identifier);
		const response = await fetch(url, { headers: { accept: 'application/json' } });
		if (response.ok) {
			const profile = (await response.json()) as MiniDoc;
			return {
				did: profile.did,
				handle: profile.handle,
				avatar: profile.avatar
			};
		}
	} catch {
		// fall through to local resolution
	}

	try {
		const identity = await identityResolver.resolve(identifier as ActorIdentifier);
		return {
			did: identity.did as Did,
			handle: identity.handle
		};
	} catch {
		return null;
	}
};

export const createAuth = (initial?: { did: string; handle: string } | null): Auth => {
	const seed = initial ?? null;
	let agent = $state<OAuthUserAgent | null>(null);
	let currentDid = $state<Did | null>((seed?.did as Did | undefined) ?? null);
	let profile = $state<AuthProfile | null>(
		seed ? { did: seed.did as Did, handle: seed.handle } : null
	);
	let error = $state<string | null>(null);
	let authenticating = $state(false);

	const hydrateProfile = async (did: Did) => {
		const resolved = await resolveProfile(did);
		profile = resolved ?? { did, handle: did };
		persistAuth(did, profile.handle);
	};

	const adoptSession = (session: OAuthSession) => {
		const nextAgent = new OAuthUserAgent(session);
		agent = nextAgent;
		const did = nextAgent.sub as Did;
		currentDid = did;
		if (browser) localStorage.setItem(CURRENT_DID_KEY, did);
		void hydrateProfile(did);
	};

	const refresh = async () => {
		if (!browser) return;
		configure();
		error = null;

		const preferred = readStoredDid() ?? currentDid ?? listStoredSessions()[0] ?? null;
		if (!preferred) {
			agent = null;
			currentDid = null;
			profile = null;
			clearAuth();
			return;
		}

		try {
			const session = await getSession(preferred, { allowStale: true });
			adoptSession(session);
		} catch (cause) {
			deleteStoredSession(preferred);
			clearAuth();
			agent = null;
			currentDid = null;
			profile = null;
			error = errorMessage(cause);
		}
	};

	const signIn = async (identifier: string, returnTo = '/') => {
		if (!browser) return;
		configure();
		error = null;

		const trimmed = identifier.trim();
		if (!trimmed) {
			error = 'Handle or DID required';
			return;
		}

		if (location.hostname === 'localhost') {
			const url = new SvelteURL(location.href);
			url.hostname = '127.0.0.1';
			url.searchParams.set('identifier', trimmed);
			url.searchParams.set('return_url', returnTo);
			location.replace(url);
			return;
		}

		try {
			const url = await createAuthorizationUrl({
				target: {
					type: 'account',
					identifier: trimmed as ActorIdentifier
				},
				scope: OAUTH_SCOPE,
				state: { returnTo }
			});

			window.location.assign(url.toString());
		} catch (cause) {
			error = errorMessage(cause);
			throw cause;
		}
	};

	const completeSignIn = async () => {
		if (!browser) return '/';
		configure();
		error = null;
		authenticating = true;

		try {
			const params = new SvelteURLSearchParams(location.hash.slice(1));
			history.replaceState(null, '', location.pathname + location.search);

			const { session, state } = await finalizeAuthorization(params);
			adoptSession(session);

			return typeof (state as { returnTo?: unknown } | null)?.returnTo === 'string'
				? (state as { returnTo: string }).returnTo
				: '/';
		} catch (cause) {
			error = errorMessage(cause);
			throw cause;
		} finally {
			authenticating = false;
		}
	};

	const signOut = async () => {
		error = null;
		const did = currentDid;
		try {
			if (agent) {
				await agent.signOut();
			} else if (did) {
				deleteStoredSession(did);
			}
		} catch {
			if (did) deleteStoredSession(did);
		} finally {
			clearAuth();
			agent = null;
			currentDid = null;
			profile = null;
		}
	};

	return {
		get agent() {
			return agent;
		},
		get currentDid() {
			return currentDid;
		},
		get profile() {
			return profile;
		},
		get error() {
			return error;
		},
		get profileLoading() {
			return currentDid !== null && profile === null;
		},
		get authenticating() {
			return authenticating;
		},
		get currentUser() {
			if (!currentDid) return null;
			return {
				did: currentDid,
				handle: profile?.handle ?? currentDid,
				avatar: profile?.avatar
			};
		},
		refresh,
		signIn,
		completeSignIn,
		signOut
	};
};

export const getAuth = () => getContext<Auth>(AUTH_KEY);
