/**
 * YOU PROBABLY DON'T NEED TO EDIT THIS FILE, UNLESS:
 * 1. You want to modify request context (see Part 1).
 * 2. You want to create a new middleware or type of procedure (see Part 3).
 *
 * TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will
 * need to use are documented accordingly near the end.
 */

import { initTRPC, TRPCError } from "@trpc/server";
import superjson from "superjson";
import { z, ZodError } from "zod";
import { env } from "~/env";

import { getServerAuthSession } from "~/server/auth";
import { db } from "~/server/db";
import { getChildLogger, logger, withLogger } from "../logger/log";
import { randomUUID } from "crypto";

/**
 * 1. CONTEXT
 *
 * This section defines the "contexts" that are available in the backend API.
 *
 * These allow you to access things when processing a request, like the database, the session, etc.
 *
 * This helper generates the "internals" for a tRPC context. The API handler and RSC clients each
 * wrap this and provides the required context.
 *
 * @see https://trpc.io/docs/server/context
 */
export const createTRPCContext = async (opts: { headers: Headers }) => {
  const session = await getServerAuthSession();

  return {
    db,
    session,
    ...opts,
  };
};

/**
 * 2. INITIALIZATION
 *
 * This is where the tRPC API is initialized, connecting the context and transformer. We also parse
 * ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation
 * errors on the backend.
 */
const t = initTRPC.context<typeof createTRPCContext>().create({
  transformer: superjson,
  errorFormatter({ shape, error }) {
    return {
      ...shape,
      data: {
        ...shape.data,
        zodError:
          error.cause instanceof ZodError ? error.cause.flatten() : null,
      },
    };
  },
});

/**
 * Create a server-side caller.
 *
 * @see https://trpc.io/docs/server/server-side-calls
 */
export const createCallerFactory = t.createCallerFactory;

/**
 * 3. ROUTER & PROCEDURE (THE IMPORTANT BIT)
 *
 * These are the pieces you use to build your tRPC API. You should import these a lot in the
 * "/src/server/api/routers" directory.
 */

/**
 * This is how you create new routers and sub-routers in your tRPC API.
 *
 * @see https://trpc.io/docs/router
 */
export const createTRPCRouter = t.router;

/**
 * Public (unauthenticated) procedure
 *
 * This is the base piece you use to build new queries and mutations on your tRPC API. It does not
 * guarantee that a user querying is authorized, but you can still access user session data if they
 * are logged in.
 */
export const publicProcedure = t.procedure;

/**
 * Authenticated (session-required) procedure
 *
 * Ensures a session exists but does not enforce waitlist status. Useful for flows where waitlisted
 * users should still have access (e.g., waitlist management).
 */
export const authedProcedure = t.procedure.use(({ ctx, next }) => {
  if (!ctx.session || !ctx.session.user) {
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }

  return next({
    ctx: {
      session: { ...ctx.session, user: ctx.session.user },
    },
  });
});

/**
 * Protected (authenticated) procedure
 *
 * If you want a query or mutation to ONLY be accessible to logged in users, use this. It verifies
 * the session is valid and guarantees `ctx.session.user` is not null.
 *
 * @see https://trpc.io/docs/procedures
 */
export const protectedProcedure = authedProcedure.use(({ ctx, next }) => {
  if (ctx.session.user.isWaitlisted) {
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }

  return next();
});

export const teamProcedure = protectedProcedure.use(async ({ ctx, next }) => {
  const teamUser = await db.teamUser.findFirst({
    where: { userId: ctx.session.user.id },
    include: { team: true },
  });

  if (!teamUser) {
    throw new TRPCError({ code: "NOT_FOUND", message: "Team not found" });
  }

  return withLogger(
    getChildLogger({
      teamId: teamUser.team.id,
      requestId: randomUUID(),
    }),
    async () => {
      return next({
        ctx: {
          team: teamUser.team,
          teamUser,
          session: { ...ctx.session, user: ctx.session.user },
        },
      });
    }
  );
});

export const teamAdminProcedure = teamProcedure.use(async ({ ctx, next }) => {
  if (ctx.teamUser.role !== "ADMIN") {
    throw new TRPCError({
      code: "UNAUTHORIZED",
      message: "You are not authorized to perform this action",
    });
  }

  return next();
});

export const domainProcedure = teamProcedure
  .input(z.object({ id: z.number() }))
  .use(async ({ ctx, next, input }) => {
    const domain = await db.domain.findUnique({
      where: { id: input.id, teamId: ctx.team.id },
    });
    if (!domain) {
      throw new TRPCError({ code: "NOT_FOUND", message: "Domain not found" });
    }

    return next({ ctx: { ...ctx, domain } });
  });

export const emailProcedure = teamProcedure
  .input(z.object({ id: z.string() }))
  .use(async ({ ctx, next, input }) => {
    const email = await db.email.findUnique({
      where: { id: input.id, teamId: ctx.team.id },
    });
    if (!email) {
      throw new TRPCError({ code: "NOT_FOUND", message: "Email not found" });
    }

    return next({ ctx: { ...ctx, email } });
  });

export const apiKeyProcedure = teamProcedure
  .input(z.object({ id: z.number() }))
  .use(async ({ ctx, next, input }) => {
    const apiKey = await db.apiKey.findUnique({
      where: { id: input.id, teamId: ctx.team.id },
    });
    if (!apiKey) {
      throw new TRPCError({ code: "NOT_FOUND", message: "API key not found" });
    }

    return next({ ctx: { ...ctx, apiKey } });
  });

export const contactBookProcedure = teamProcedure
  .input(
    z.object({
      contactBookId: z.string(),
    })
  )
  .use(async ({ ctx, next, input }) => {
    const contactBook = await db.contactBook.findUnique({
      where: { id: input.contactBookId, teamId: ctx.team.id },
    });
    if (!contactBook) {
      throw new TRPCError({
        code: "NOT_FOUND",
        message: "Contact book not found",
      });
    }

    return next({ ctx: { ...ctx, contactBook } });
  });

export const campaignProcedure = teamProcedure
  .input(
    z.object({
      campaignId: z.string(),
    })
  )
  .use(async ({ ctx, next, input }) => {
    const campaign = await db.campaign.findUnique({
      where: { id: input.campaignId, teamId: ctx.team.id },
    });
    if (!campaign) {
      throw new TRPCError({
        code: "NOT_FOUND",
        message: "Campaign not found",
      });
    }

    return next({ ctx: { ...ctx, campaign } });
  });

export const templateProcedure = teamProcedure
  .input(
    z.object({
      templateId: z.string(),
    })
  )
  .use(async ({ ctx, next, input }) => {
    const template = await db.template.findUnique({
      where: { id: input.templateId, teamId: ctx.team.id },
    });
    if (!template) {
      throw new TRPCError({
        code: "NOT_FOUND",
        message: "Template not found",
      });
    }

    return next({ ctx: { ...ctx, template } });
  });

/**
 * To manage application settings, for hosted version, authenticated users will be considered as admin
 */
export const adminProcedure = protectedProcedure.use(async ({ ctx, next }) => {
  if (env.NEXT_PUBLIC_IS_CLOUD && ctx.session.user.email !== env.ADMIN_EMAIL) {
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }
  return next();
});
