import { subjects } from "@/subjects";
import { MiddlewareHandler } from "hono";
import { Actor } from "@nestri/core/actor";
import { Api } from "@nestri/core/api/index";
import { Steam } from "@nestri/core/steam/index";
import { createClient } from "@openauthjs/openauth/client";
import { VisibleError, ErrorCodes } from "@nestri/core/error";

const client = createClient({
  clientID: "api",
  issuer: process.env.AUTH_URL || "http://localhost:3000",
  subjects,
});

export const auth: MiddlewareHandler = async (c, next) => {
  const authHeader =
    c.req.query("authorization") ?? c.req.header("authorization");
  if (authHeader) {
    const match = authHeader.match(/^Bearer (.+)$/);
    if (!match || !match[1]) {
      throw new VisibleError(
        "authentication",
        ErrorCodes.Authentication.UNAUTHORIZED,
        "Bearer token not found or improperly formatted",
      );
    }

    const bearerToken = match[1];

    if (bearerToken?.startsWith("nst_")) {
      const token = await Api.Personal.fromToken(bearerToken);
      if (!token)
        throw new VisibleError(
          "authentication",
          ErrorCodes.Authentication.INVALID_TOKEN,
          "Invalid personal access token",
        );
      return Actor.provide(
        "token",
        {
          teamID: token.teamID,
          tokenID: token.id,
        },
        next,
      );
    }

    const result = await client.verify(bearerToken!);
    if (result.err)
      throw new VisibleError(
        "authentication",
        ErrorCodes.Authentication.INVALID_TOKEN,
        "Invalid bearer token",
      );
    if (result.subject.type === "user") {
      const steamID = c.req.header("x-nestri-steam") || c.req.query("steamID");
      if (!steamID) {
        return Actor.provide(
          "user",
          {
            email: result.subject.properties.email,
            userID: result.subject.properties.userID,
          },
          next,
        );
      }

      return Actor.provide(
        "system",
        {
          userID: result.subject.properties.userID,
        },
        async () => {
          const steamAcc = await Steam.fromID(steamID);
          if (!steamAcc) {
            throw new VisibleError(
              "authentication",
              ErrorCodes.Authentication.INVALID_TOKEN,
              "Invalid bearer token",
            );
          }
          return Actor.provide(
            "steam",
            { steamID: steamAcc.id, userID: steamAcc.ownerID },
            next,
          );
        },
      );
    }
  }

  return Actor.provide("public", {}, next);
};
