"use node";

import { env } from "@code/env/convex";
import { decodeGitConnectionInput } from "@code/primitives/execution-runtime";
import { ConvexError, v } from "convex/values";
import { Effect } from "effect";

import { internal } from "./_generated/api";
import type { Id } from "./_generated/dataModel";
import { action } from "./_generated/server";
import { authComponent, createAuth } from "./auth";

const encryptionKey = async (): Promise<CryptoKey> => {
  if (!env.GIT_CREDENTIAL_ENCRYPTION_KEY) {
    throw new ConvexError("Git credential encryption is not configured");
  }
  const bytes = Buffer.from(env.GIT_CREDENTIAL_ENCRYPTION_KEY, "base64url");
  if (bytes.byteLength !== 32) {
    throw new ConvexError("Git credential encryption key must be 32 bytes");
  }
  return await crypto.subtle.importKey("raw", bytes, "AES-GCM", false, [
    "encrypt",
    "decrypt",
  ]);
};

const encryptCredential = async (credential: string) => {
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encrypted = await crypto.subtle.encrypt(
    { iv, name: "AES-GCM" },
    await encryptionKey(),
    new TextEncoder().encode(credential)
  );
  return {
    credentialCiphertext: Buffer.from(encrypted).toString("base64url"),
    credentialIv: Buffer.from(iv).toString("base64url"),
  };
};

export const decryptCredential = async (
  credentialCiphertext: string,
  credentialIv: string
): Promise<string> => {
  const decrypted = await crypto.subtle.decrypt(
    {
      iv: Buffer.from(credentialIv, "base64url"),
      name: "AES-GCM",
    },
    await encryptionKey(),
    Buffer.from(credentialCiphertext, "base64url")
  );
  return new TextDecoder().decode(decrypted);
};

export const connectGitea = action({
  args: {
    serverUrl: v.string(),
    token: v.string(),
    username: v.optional(v.string()),
  },
  handler: async (
    ctx,
    args
  ): Promise<{ connectionId: Id<"gitConnections"> }> => {
    const userId = await ctx.auth.getUserIdentity().then((identity) => {
      if (!identity) {
        throw new ConvexError("Authentication required");
      }
      return identity.tokenIdentifier;
    });
    const connection = await Effect.runPromise(
      decodeGitConnectionInput({
        credential: args.token,
        credentialKind: "token",
        provider: "gitea",
        serverUrl: args.serverUrl,
        username: args.username,
      })
    );
    const encrypted = await encryptCredential(connection.credential);
    const connectionId = await ctx.runMutation(
      internal.gitConnectionData.persist,
      {
        ...encrypted,
        credentialKind: connection.credentialKind,
        provider: connection.provider,
        serverUrl: connection.serverUrl,
        userId,
        username: connection.username,
      }
    );
    return { connectionId };
  },
});

export const connectGithub = action({
  args: {},
  handler: async (ctx): Promise<{ connectionId: Id<"gitConnections"> }> => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      throw new ConvexError("Authentication required");
    }
    const { auth, headers } = await authComponent.getAuth(createAuth, ctx);
    const token = await auth.api.getAccessToken({
      body: { providerId: "github" },
      headers,
    });
    if (!token.accessToken) {
      throw new ConvexError("GitHub account is not connected");
    }
    const encrypted = await encryptCredential(token.accessToken);
    const connectionId = await ctx.runMutation(
      internal.gitConnectionData.persist,
      {
        ...encrypted,
        credentialKind: "oauth",
        provider: "github",
        serverUrl: "https://github.com",
        userId: identity.tokenIdentifier,
      }
    );
    return { connectionId };
  },
});
