import { ok } from "../../is.js";
import { getOrigin } from "../config.js";
import { addAuthenticationState, addCookieState, getAuthenticationRoles, getAuthenticationRole, deleteAuthenticationState, getCached, addExpiring, getExternalUser, getUserAuthenticationRoleForUser, getAuthenticationState, getExternalReference, getExchangeStateURL, } from "../../data/index.js";
import { packageIdentifier } from "../../package.js";
import { getExpiresAt, MONTH_MS } from "../../data/index.js";
import "@fastify/cookie";
import { authenticate, setAuthenticationStateCookie } from "../authentication.js";
import { getMaybeAuthenticationState, getMaybeUser } from "../../authentication/index.js";
export const { REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET, REDDIT_REDIRECT_URL, REDDIT_SCOPE, REDDIT_NAME, REDDIT_AUTHORIZE_DURATION, REDDIT_FLAIR, REDDIT_FLAIR_EXPIRES_IN_MS: givenExpiresIn, } = process.env;
export async function redditAuthenticationRoutes(fastify) {
    if (!REDDIT_CLIENT_ID)
        return;
    ok(REDDIT_CLIENT_ID, "Expected REDDIT_CLIENT_ID");
    ok(REDDIT_CLIENT_SECRET, "Expected REDDIT_CLIENT_SECRET");
    ok(REDDIT_NAME, "Expected REDDIT_NAME");
    ok(REDDIT_FLAIR, "Expected REDDIT_FLAIR");
    const REDDIT_FLAIR_EXPIRES_IN_MS = givenExpiresIn
        ? +givenExpiresIn
        : 6 * MONTH_MS;
    ok(REDDIT_FLAIR_EXPIRES_IN_MS > 0, "Expected REDDIT_FLAIR_EXPIRES_IN_MS");
    const REDDIT_CLIENT_BASIC = `Basic ${Buffer.from(`${REDDIT_CLIENT_ID}:${REDDIT_CLIENT_SECRET}`).toString("base64")}`;
    const USER_AGENT = `SSO by ${packageIdentifier}`;
    const API_DOMAIN = "https://oauth.reddit.com";
    {
        const querystring = {
            type: "object",
            properties: {
                code: {
                    type: "string",
                },
                grant_type: {
                    type: "string",
                },
                state: {
                    type: "string",
                },
            },
            required: ["code", "state"],
        };
        const schema = {
            querystring,
            tags: ["system"],
        };
        fastify.get("/reddit/callback", {
            schema,
            preHandler: authenticate(fastify, { anonymous: true }),
            async handler(request, response) {
                const { code, state: stateKey } = request.query;
                const state = await getAuthenticationState(stateKey);
                if (!state?.externalScope) {
                    const message = `Could not find stateKey in storage`;
                    response.status(500);
                    response.send(message);
                    return;
                }
                const { redirectUrl, type, userState } = state;
                ok(type === "reddit", "Expected type to be reddit");
                await deleteAuthenticationState(state.stateId);
                const { message, access_token: accessToken } = await getAccessToken();
                ok(accessToken, message || "Expected access_token to be returned");
                const me = await getMe();
                const subscriptions = await getSubscriptionStatus();
                const flairRole = await getUserFlairRole();
                const roles = getAuthenticationRoles([
                    "member",
                    flairRole,
                    ...Object.entries(subscriptions)
                        .filter((entry) => entry[1])
                        .map((entry) => entry[0]),
                ]);
                const existingUser = getMaybeUser();
                const externalUser = await getExternalReference("reddit", me.name);
                if (externalUser && existingUser && externalUser.userId !== existingUser.userId) {
                    throw new Error("Expected user to be logged in");
                }
                const internalUser = await getExternalUser("reddit", me.name, existingUser);
                if (!existingUser) {
                    const userRoles = await getUserAuthenticationRoleForUser(internalUser);
                    const cookieState = await addCookieState({
                        userId: internalUser.userId,
                        roles: [...new Set([
                                ...roles,
                                ...(userRoles?.roles ?? [])
                            ])],
                        from: {
                            type: "reddit",
                            createdAt: state.createdAt,
                            data: state.data,
                            from: state.from
                        },
                        data: state.data,
                    });
                    setAuthenticationStateCookie(response, cookieState);
                }
                const exchange = await getExchangeStateURL(userState);
                const location = exchange || "/home";
                response.header("Location", location);
                response.status(302);
                response.send();
                async function getSubscriptionStatus() {
                    const subscriptions = await getAllSubscriptions();
                    const moderator = isInList(subscriptions.moderator);
                    const contributor = moderator || isInList(subscriptions.contributor);
                    const subscriber = moderator || isInList(subscriptions.subscriber);
                    return {
                        moderator,
                        contributor,
                        subscriber,
                    };
                    function isInList(values) {
                        return !!values.find((value) => value.display_name === REDDIT_NAME);
                    }
                }
                async function getAllSubscriptions() {
                    const [moderator, subscriber, contributor] = await Promise.all([
                        getSubscriptions("moderator"),
                        getSubscriptions("subscriber"),
                        getSubscriptions("contributor"),
                    ]);
                    return {
                        moderator: moderator.data.children.map((node) => node.data),
                        subscriber: subscriber.data.children.map((node) => node.data),
                        contributor: contributor.data.children.map((node) => node.data),
                    };
                }
                async function getSubscriptions(type) {
                    const response = await fetch(new URL(`/subreddits/mine/${type}`, API_DOMAIN), {
                        method: "GET",
                        headers: {
                            Authorization: `bearer ${accessToken}`,
                            "User-Agent": USER_AGENT,
                        },
                    });
                    ok(response, "getSubscriptions response not ok");
                    return response.json();
                }
                async function getUserFlairRole() {
                    // In this case we get the remote flair first, and then
                    // use cached only if we don't find one
                    const flair = await getUserFlair();
                    const cacheKey = `${REDDIT_NAME} ${me.name}: flair`;
                    if (flair) {
                        const role = getAuthenticationRole(flair);
                        if (!role)
                            return undefined; // Don't use cached, just undefined;
                        await addExpiring({
                            role: false,
                            key: cacheKey,
                            value: role,
                            expiresAt: getExpiresAt(REDDIT_FLAIR_EXPIRES_IN_MS),
                        });
                        return role;
                    }
                    return getCached(cacheKey);
                }
                async function getUserFlair() {
                    const [comments, submitted] = await Promise.all([
                        getUserContent("comments"),
                        getUserContent("submitted"),
                    ]);
                    let found = comments.find(isMatching);
                    if (!found) {
                        found = submitted.find(isMatching);
                        if (!found)
                            return undefined;
                    }
                    return found.author_flair_text.replace(/:[^:]+:/g, "").trim();
                    function isMatching(content) {
                        return (content.author_flair_text?.startsWith(REDDIT_FLAIR) &&
                            content.subreddit === REDDIT_NAME &&
                            content.author === me.name);
                    }
                }
                async function getUserContent(type) {
                    const url = new URL(`/user/${me.name}/${type}`, API_DOMAIN);
                    url.searchParams.set("limit", "100");
                    const response = await fetch(url, {
                        method: "GET",
                        headers: {
                            Authorization: `bearer ${accessToken}`,
                            "User-Agent": USER_AGENT,
                        },
                    });
                    ok(response, "getSidebar response not ok");
                    const { data: { children }, } = await response.json();
                    return children.map(({ data }) => data);
                }
                async function getMe() {
                    const response = await fetch(new URL(`/api/v1/me`, API_DOMAIN), {
                        method: "GET",
                        headers: {
                            Authorization: `bearer ${accessToken}`,
                            "User-Agent": USER_AGENT,
                        },
                    });
                    ok(response, "getMe response not ok");
                    return response.json();
                }
                async function getAccessToken() {
                    const body = new FormData();
                    body.set("code", code);
                    body.set("grant_type", "authorization_code");
                    body.set("redirect_uri", redirectUrl);
                    const response = await fetch("https://ssl.reddit.com/api/v1/access_token", {
                        method: "POST",
                        body,
                        headers: {
                            Authorization: REDDIT_CLIENT_BASIC,
                            "User-Agent": USER_AGENT,
                        },
                    });
                    ok(response, "getAccessToken response not ok");
                    return response.json();
                }
            },
        });
    }
    {
        const querystring = {
            type: "object",
            properties: {
                state: {
                    type: "string",
                    nullable: true,
                },
                bot: {
                    type: "string",
                    nullable: true,
                },
            }
        };
        const schema = {
            querystring,
            tags: ["system"],
        };
        fastify.get("/reddit/redirect", {
            schema,
            preHandler: authenticate(fastify, { anonymous: true }),
            async handler(request, response) {
                const { state: userState } = request.query;
                const scope = REDDIT_SCOPE || "identity mysubreddits read flair history";
                const redirectUrl = REDDIT_REDIRECT_URL ||
                    `${getOrigin()}${fastify.prefix}/reddit/callback`;
                const currentState = getMaybeAuthenticationState();
                const { stateKey, expiresAt } = await addAuthenticationState({
                    type: "reddit",
                    userState,
                    externalScope: scope,
                    redirectUrl,
                    from: currentState ? {
                        type: currentState.type,
                        stateId: currentState.stateId,
                        createdAt: currentState.createdAt,
                        from: currentState.from
                    } : undefined,
                });
                const url = new URL("/api/v1/authorize", "https://ssl.reddit.com");
                url.searchParams.set("client_id", REDDIT_CLIENT_ID);
                url.searchParams.set("response_type", "code");
                url.searchParams.set("state", stateKey);
                url.searchParams.set("redirect_uri", redirectUrl);
                url.searchParams.set("duration", REDDIT_AUTHORIZE_DURATION || "temporary");
                url.searchParams.set("scope", scope);
                response.header("Location", url.toString());
                if (expiresAt) {
                    response.header("X-Expires-At", expiresAt);
                }
                response.status(302);
                response.send("Redirecting");
            },
        });
    }
}
//# sourceMappingURL=reddit.js.map