import { z } from "zod";
import { TRPCError } from "@trpc/server";
import { t } from "@repo/core-shared/trpc/init";
import { defineErrorMiddleware } from "@repo/core-shared/trpc/define-error-middleware";
import type { DsrBinding } from "./di/bind-production";
import { createExportHandler } from "./handlers/export-handler";
import { createDeleteHandler } from "./handlers/delete-handler";
import { createRectifyHandler } from "./handlers/rectify-handler";
import type { RectifyHandlerInput } from "./handlers/rectify-handler";
import { createRestrictHandler } from "./handlers/restrict-handler";

export type DsrTrpcUser = {
  id?: string;
  roles?: string[];
};

const requireAuthenticated = t.middleware(({ ctx, next }) => {
  const user = (ctx as { user?: DsrTrpcUser }).user;
  if (!user) {
    throw new TRPCError({
      code: "UNAUTHORIZED",
      message: "Authentication required",
    });
  }
  return next({ ctx: { ...ctx, user } });
});

const dsrProcedure = t.procedure
  .use(requireAuthenticated)
  .use(defineErrorMiddleware([]));

/**
 * Creates the DSR tRPC router.
 *
 * Capture `binding` at router-creation time. Apps that mount this router
 * must pass the `DsrBinding` returned by `bindProductionDsr` or `bindDevSeedDsr`.
 *
 * @example
 * ```ts
 * const binding = bindProductionDsr({ config, auditLog });
 * const appRouter = t.router({ ..., dsr: createDsrRouter(binding) });
 * ```
 */
export function createDsrRouter(binding: DsrBinding) {
  // Handlers are created lazily (inside procedure closures) so that the
  // dsrRouter singleton proxy doesn't trigger at module init time.
  return t.router({
    export: dsrProcedure
      .input(
        z
          .object({
            subjectId: z.string().min(1),
            format: z.enum(["json", "json-ld"]),
          })
          .strict(),
      )
      .query(async ({ input }) => {
        const res = await createExportHandler(binding.dataExport)(input);
        return res.body;
      }),

    delete: dsrProcedure
      .input(
        z
          .object({
            subjectId: z.string().min(1),
            mode: z.enum(["soft", "cascade-hard"]),
          })
          .strict(),
      )
      .mutation(async ({ ctx, input }) => {
        if (input.mode === "cascade-hard") {
          const user = (ctx as { user: DsrTrpcUser }).user;
          if (!user.roles?.includes("admin")) {
            throw new TRPCError({
              code: "FORBIDDEN",
              message: "Admin role required for cascade-hard deletion",
            });
          }
        }
        const res = await createDeleteHandler(binding.dataDelete)(input);
        return res.body;
      }),

    rectify: dsrProcedure
      .input(
        z
          .object({
            subjectId: z.string().min(1),
            collection: z.string().min(1),
            field: z.string().min(1),
            value: z.unknown(),
          })
          .strict(),
      )
      .mutation(async ({ input }) => {
        // tRPC infers z.unknown() as value?: unknown; cast to assert presence
        const res = await createRectifyHandler(binding.dataRectify)(
          input as RectifyHandlerInput,
        );
        return res.body;
      }),

    restrict: dsrProcedure
      .input(
        z
          .object({
            subjectId: z.string().min(1),
            granted: z.boolean(),
          })
          .strict(),
      )
      .mutation(async ({ input }) => {
        const res = await createRestrictHandler(binding.processingRestriction)(
          input,
        );
        return res.body;
      }),
  });
}

/**
 * Convenience singleton for projects with a single DSR binding instance.
 * Most callers should use `createDsrRouter(binding)` and pass the binding
 * explicitly. This export exists for type inference (`DsrRouter`) only.
 */
export const dsrRouter = createDsrRouter(
  new Proxy({} as DsrBinding, {
    get(_target, prop) {
      if (prop === "then") return undefined; // not a Promise
      throw new Error(
        `dsrRouter singleton used without providing a DsrBinding. ` +
          `Use createDsrRouter(binding) instead.`,
      );
    },
  }),
);

export type DsrRouter = ReturnType<typeof createDsrRouter>;
