/**
 * 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 type { CreateNextContextOptions } from "@trpc/server/adapters/next";
import type { NextApiRequest, NextApiResponse } from "next";
import superjson from "superjson";
import z, { ZodError } from "zod/v4";
import { env } from "~/env";
import { verifyToken } from "~/server/auth/jwt";
import { zUserId } from "~/server/utils/zod_types";
import { TOKEN_CONFIG } from "../auth/configs";

/**
 * 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.
 */
export const sessionSchema = z.object({
	email: z.email(),
	id: zUserId,
	isAdmin: z.boolean(),
	isBlocked: z.boolean(),
	nome: z.string(),
	username: z.string(),
	mustChangePassword: z.boolean(),
});

export type Session = z.infer<typeof sessionSchema>;

export type ValidSession = {
	user: Session;
	status: "success";
};

interface CreateInnerContextOptions extends Partial<CreateNextContextOptions> {
	session: Session | null;
	res?: NextApiResponse;
	req?: NextApiRequest;
}

export function createInnerTRPCContext(opts: CreateInnerContextOptions) {
	return {
		...opts,
		session: opts.session,
	};
}

export const createTRPCContext = async ({
	req,
	res,
}: Partial<CreateNextContextOptions>) => {
	if (!req || !res) {
		throw new Error(
			"You are missing `req` or `res` in your call to createTRPCContext.",
		);
	}

	const accessToken = req.cookies[TOKEN_CONFIG.ACCESS_TOKEN_COOKIE_NAME];
	// Get the session from the server using the getServerSession wrapper function

	const session = accessToken ? await verifyToken(accessToken) : null;
	const contextInner = createInnerTRPCContext({ session });
	return {
		...contextInner,
		req,
		res,
	};
};

export type Context = Awaited<ReturnType<typeof createInnerTRPCContext>>;

/**
 * 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.
 */

export const t = initTRPC.context<Context>().create({
	errorFormatter({ shape, error }) {
		return {
			...shape,
			data: {
				...shape.data,
				zodError:
					error.cause instanceof ZodError ? z.treeifyError(error.cause) : null,
			},
		};
	},
	sse: {
		client: {
			reconnectAfterInactivityMs: 5_000,
		},
		//maxDurationMs: 5 * 60 * 1_000, // 5 minutes
		ping: {
			enabled: true,
			intervalMs: 3_000,
		},
	},
	transformer: superjson,
});

/**
 * 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;

/** Reusable middleware that enforces users are logged in before running the procedure. */
const enforceUserIsAuthed = t.middleware(({ ctx, next, path }) => {
	if (!ctx.session) {
		throw new TRPCError({
			code: "UNAUTHORIZED",
			message: `User is not logged, error in path: ${path}`,
		});
	}
	return next({
		ctx: {
			// infers the `session` as non-nullable
			session: { ...ctx.session },
		},
	});
});

/**
 * 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 = t.procedure.use(enforceUserIsAuthed);

/** Reusable middleware that enforces users are logged in before running the procedure. */
const enforceUserIsAdmin = t.middleware(({ ctx, next, path }) => {
	if (!ctx.session || !ctx.session.isAdmin) {
		throw new TRPCError({
			code: "UNAUTHORIZED",
			message: `User is not Admin, error in path: ${path}`,
		});
	}
	return next({
		ctx: {
			// infers the `session` as non-nullable
			session: { ...ctx.session },
		},
	});
});

/**
 * 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 adminProcedure = t.procedure.use(enforceUserIsAdmin);

export const apiProcedure = publicProcedure.use((opts) => {
	if (!opts.ctx.req || !opts.ctx.res) {
		throw new Error("You are missing `req` or `res` in your call.");
	}
	return opts.next({
		ctx: {
			// We overwrite the context with the truthy `req` & `res`, which will also overwrite the types used in your procedure.
			req: opts.ctx.req,
			res: opts.ctx.res,
		},
	});
});

const enforceProtectedApi = t.middleware(async ({ ctx, next, path }) => {
	if (!ctx.req || !ctx.res) {
		throw new TRPCError({
			code: "UNAUTHORIZED",
			message: `No request or response object, error in path: ${path}`,
		});
	}
	const basicAuth = ctx.req.headers.authorization;
	if (!basicAuth) {
		throw new TRPCError({
			code: "UNAUTHORIZED",
			message: `No Auth header found: ${path}`,
		});
	}

	const authValue = basicAuth.split(" ")[1];

	if (!authValue) {
		throw new TRPCError({
			code: "UNAUTHORIZED",
			message: `Auth value invalid: ${path}`,
		});
	}

	const [user, pwd] = atob(authValue).split(":");

	if (user !== env.EXP_API_USER || pwd !== env.EXP_API_PASS) {
		throw new TRPCError({
			code: "UNAUTHORIZED",
			message: `User or Secret not valid: ${path}`,
		});
	}
	return next({
		ctx: {
			// infers the `session` as non-nullable
			req: ctx.req,
			res: ctx.res,
		},
	});
});

export const protectedApiProcedure = publicProcedure.use(enforceProtectedApi);
