import {
	OAuthUserAgent,
	configureOAuth,
	createAuthorizationUrl,
	deleteStoredSession,
	finalizeAuthorization,
	getSession,
	listStoredSessions,
} from '@atcute/oauth-browser-client';
import type { ActorIdentifier, Did } from '@atcute/lexicons/syntax';
import type { Component, JSX } from 'solid-js';
import { createContext, createSignal, onMount, useContext } from 'solid-js';

import { TANGLED_OAUTH_SCOPE, identityResolver } from './api';
import { getErrorMessage } from './repo-utils';

const CURRENT_DID_KEY = 'untangled.currentDid';

configureOAuth({
	metadata: {
		client_id: import.meta.env.VITE_OAUTH_CLIENT_ID,
		redirect_uri: import.meta.env.VITE_OAUTH_REDIRECT_URI,
	},
	identityResolver,
});

export interface AuthContextValue {
	agent: () => OAuthUserAgent | null;
	currentDid: () => Did | null;
	error: () => string | null;
	isLoading: () => boolean;
	signIn: (identifier: string) => Promise<void>;
	completeSignIn: () => Promise<string>;
	signOut: () => Promise<void>;
	refresh: () => Promise<void>;
}

const AuthContext = createContext<AuthContextValue>();

export const AuthProvider: Component<{ children: JSX.Element }> = (props) => {
	const [agent, setAgent] = createSignal<OAuthUserAgent | null>(null);
	const [currentDid, setCurrentDid] = createSignal<Did | null>(null);
	const [isLoading, setIsLoading] = createSignal(true);
	const [error, setError] = createSignal<string | null>(null);

	const refresh = async () => {
		setIsLoading(true);
		setError(null);

		const preferred = readStoredDid() ?? currentDid() ?? listStoredSessions()[0] ?? null;

		if (!preferred) {
			setAgent(null);
			setCurrentDid(null);
			setIsLoading(false);
			return;
		}

		try {
			const session = await getSession(preferred, {
				allowStale: true,
			});
			const nextAgent = new OAuthUserAgent(session);
			setAgent(nextAgent);
			setCurrentDid(nextAgent.sub);
			localStorage.setItem(CURRENT_DID_KEY, nextAgent.sub);
		} catch (cause) {
			deleteStoredSession(preferred);
			localStorage.removeItem(CURRENT_DID_KEY);
			setAgent(null);
			setCurrentDid(null);
			setError(getErrorMessage(cause));
		} finally {
			setIsLoading(false);
		}
	};

	onMount(() => {
		void refresh();
	});

	const signIn = async (identifier: string) => {
		setError(null);
		const trimmed = identifier.trim();
		if (!trimmed) {
			setError('Handle or DID required');
			return;
		}

		try {
			const url = await createAuthorizationUrl({
				target: {
					type: 'account',
					identifier: trimmed as ActorIdentifier,
				},
				scope: TANGLED_OAUTH_SCOPE,
				state: {
					returnTo: `${location.pathname}${location.search}${location.hash}`,
				},
			});

			window.location.assign(url.toString());
		} catch (cause) {
			setError(getErrorMessage(cause));
			throw cause;
		}
	};

	const completeSignIn = async (): Promise<string> => {
		setIsLoading(true);
		setError(null);

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

			const { session, state } = await finalizeAuthorization(params);
			const nextAgent = new OAuthUserAgent(session);

			setAgent(nextAgent);
			setCurrentDid(nextAgent.sub);
			localStorage.setItem(CURRENT_DID_KEY, nextAgent.sub);
			return typeof (state as { returnTo?: unknown } | null)?.returnTo === 'string'
				? (state as { returnTo: string }).returnTo
				: '/';
		} catch (cause) {
			setError(getErrorMessage(cause));
			throw cause;
		} finally {
			setIsLoading(false);
		}
	};

	const signOut = async () => {
		setError(null);
		const current = agent();
		const did = currentDid();

		try {
			if (current) {
				await current.signOut();
			} else if (did) {
				deleteStoredSession(did);
			}
		} catch {
			if (did) {
				deleteStoredSession(did);
			}
		} finally {
			localStorage.removeItem(CURRENT_DID_KEY);
			setAgent(null);
			setCurrentDid(null);
		}
	};

	const value: AuthContextValue = {
		agent,
		currentDid,
		error,
		isLoading,
		signIn,
		completeSignIn,
		signOut,
		refresh,
	};

	return <AuthContext.Provider value={value}>{props.children}</AuthContext.Provider>;
};

export const useAuth = (): AuthContextValue => {
	const context = useContext(AuthContext);
	if (!context) {
		throw new Error('useAuth must be used inside AuthProvider');
	}

	return context;
};

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

	return value as Did;
};
