import { GraphQLError } from "graphql";
import {} from "kysely";
import { getConnection, saveConnection, } from "./db/installations.js";
import { createRepo, exchangeDeviceCode, findRepoInstallationId, findUserRepo, mintInstallationToken, ReinstallRequiredError, RepoAlreadyExistsError, startDeviceFlow, } from "./github-app.js";
/** A GraphQLError carrying the error code in `extensions.code` and `message`. */
function ghError(code) {
    return new GraphQLError(code, { extensions: { code } });
}
/** Canonical did:pkh for the authenticated caller, or null if unauthenticated. */
function callerDid(ctx) {
    const u = ctx.user;
    if (!u)
        return null;
    return `did:pkh:${u.networkId}:${u.chainId}:${u.address.toLowerCase()}`;
}
function requireDid(ctx) {
    const did = callerDid(ctx);
    if (!did)
        throw ghError("UNAUTHENTICATED");
    return did;
}
function toStatus(connection) {
    return {
        connected: !!connection,
        connection: connection
            ? {
                environmentId: connection.environmentId,
                repoFullName: connection.repoFullName,
                repoUrl: `https://github.com/${connection.repoFullName}`,
                createdAt: connection.createdAt,
            }
            : null,
    };
}
export function createResolvers(db) {
    return {
        Query: {
            VetraGithubAuth: () => ({}),
        },
        Mutation: {
            VetraGithubAuth: () => ({}),
        },
        VetraGithubAuthQueries: {
            myGithubConnection: async (_p, { environmentId }, ctx) => {
                const did = requireDid(ctx);
                const connection = await getConnection(db, did, environmentId);
                return toStatus(connection);
            },
            getPushToken: async (_p, { environmentId }, ctx) => {
                const did = requireDid(ctx);
                const connection = await getConnection(db, did, environmentId);
                if (!connection)
                    throw ghError("NOT_CONNECTED");
                const installationId = await findRepoInstallationId(connection.repoFullName);
                if (!installationId)
                    throw ghError("APP_NOT_INSTALLED");
                const repoName = connection.repoFullName.split("/").pop();
                try {
                    return await mintInstallationToken(installationId, repoName);
                }
                catch (error) {
                    if (error instanceof ReinstallRequiredError)
                        throw ghError("APP_NOT_INSTALLED");
                    throw error;
                }
            },
        },
        VetraGithubAuthMutations: {
            startGithubDeviceFlow: async (_p, _a, ctx) => {
                requireDid(ctx);
                return startDeviceFlow();
            },
            connectGithub: async (_p, { deviceCode, repoName, environmentId, }, ctx) => {
                const did = requireDid(ctx);
                const exchange = await exchangeDeviceCode(deviceCode);
                if (exchange.status !== "authorized") {
                    const codes = {
                        pending: "AUTHORIZATION_PENDING",
                        slowDown: "SLOW_DOWN",
                        expired: "DEVICE_CODE_EXPIRED",
                        denied: "ACCESS_DENIED",
                    };
                    throw ghError(codes[exchange.status]);
                }
                const userAccessToken = exchange.accessToken;
                let repo;
                try {
                    repo = await createRepo(userAccessToken, repoName);
                }
                catch (error) {
                    if (error instanceof RepoAlreadyExistsError) {
                        const existing = await findUserRepo(userAccessToken, repoName);
                        if (!existing)
                            throw ghError("REPO_ALREADY_EXISTS");
                        repo = existing;
                    }
                    else {
                        throw error;
                    }
                }
                const connection = await saveConnection(db, did, environmentId, repo.fullName);
                return toStatus(connection);
            },
        },
    };
}
