import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { nanoid } from "nanoid";
import { createHash, randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
import { SignJWT } from "jose";
import { COOKIE_NAME, LOCAL_AUTH_COOKIE } from "@shared/const";
import { getSessionCookieOptions } from "./_core/cookies";
import { systemRouter } from "./_core/systemRouter";
import { adminProcedure, protectedProcedure, publicProcedure, router } from "./_core/trpc";
import {
  createLink, createLocalUser, deleteLink, getDashboardSummary, getLinkBySlug, getLinkStats,
  getLocalCredential, listAllLinks, listLinksByUser, recordClick, updateLink,
} from "./db";
import { ENV } from "./_core/env";

function normalizeIdentifier(value: string) { return value.trim().toLowerCase(); }
export function hashPassword(password: string) {
  const salt = randomBytes(16).toString("hex");
  const derived = scryptSync(password, salt, 64).toString("hex");
  return `scrypt$${salt}$${derived}`;
}
export function verifyPassword(password: string, encoded: string) {
  const [, salt, expected] = encoded.split("$");
  if (!salt || !expected) return false;
  const actual = scryptSync(password, salt, 64);
  return timingSafeEqual(actual, Buffer.from(expected, "hex"));
}
async function createLocalToken(userId: number) {
  return new SignJWT({ userId }).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime("30d").sign(new TextEncoder().encode(ENV.cookieSecret));
}

export const slugSchema = z.string().trim().min(3).max(48).regex(/^[a-zA-Z0-9_-]+$/, "Alias hanya boleh berisi huruf, angka, tanda hubung, atau underscore.");
const reservedSlugs = new Set(["app", "admin", "api", "login", "settings", "dashboard", "404"]);

export const appRouter = router({
  system: systemRouter,
  auth: router({
    me: publicProcedure.query(opts => opts.ctx.user),
    register: publicProcedure.input(z.object({ name: z.string().trim().min(2).max(120), identifier: z.string().trim().min(3).max(190), password: z.string().min(8).max(128) })).mutation(async ({ ctx, input }) => {
      const identifier = normalizeIdentifier(input.identifier);
      if (!ENV.cookieSecret) throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Session secret belum dikonfigurasi." });
      try {
        const user = await createLocalUser({ name: input.name, identifier, passwordHash: hashPassword(input.password) });
        if (!user) throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Gagal membuat akun." });
        ctx.res.cookie(LOCAL_AUTH_COOKIE, await createLocalToken(user.id), { httpOnly: true, secure: true, sameSite: "none", path: "/", maxAge: 30 * 24 * 60 * 60 * 1000 });
        return user;
      } catch (error: any) {
        if (error?.code === "ER_DUP_ENTRY") throw new TRPCError({ code: "CONFLICT", message: "ID atau email tersebut sudah terdaftar." });
        throw error;
      }
    }),
    login: publicProcedure.input(z.object({ identifier: z.string().trim().min(3).max(190), password: z.string().min(1).max(128) })).mutation(async ({ ctx, input }) => {
      const match = await getLocalCredential(normalizeIdentifier(input.identifier));
      if (!match || !verifyPassword(input.password, match.credential.passwordHash)) throw new TRPCError({ code: "UNAUTHORIZED", message: "ID/email atau password salah." });
      if (!ENV.cookieSecret) throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Session secret belum dikonfigurasi." });
      ctx.res.cookie(LOCAL_AUTH_COOKIE, await createLocalToken(match.user.id), { httpOnly: true, secure: true, sameSite: "none", path: "/", maxAge: 30 * 24 * 60 * 60 * 1000 });
      return match.user;
    }),
    logout: publicProcedure.mutation(({ ctx }) => {
      const cookieOptions = getSessionCookieOptions(ctx.req);
      ctx.res.clearCookie(COOKIE_NAME, { ...cookieOptions, maxAge: -1 });
      ctx.res.clearCookie(LOCAL_AUTH_COOKIE, { httpOnly: true, secure: true, sameSite: "none", path: "/", maxAge: -1 });
      return { success: true } as const;
    }),
  }),
  links: router({
    list: protectedProcedure.query(async ({ ctx }) => listLinksByUser(ctx.user.id)),
    summary: protectedProcedure.query(async ({ ctx }) => getDashboardSummary(ctx.user.id)),
    create: protectedProcedure.input(z.object({
      title: z.string().trim().min(1).max(180),
      targetUrl: z.string().url().max(2000),
      slug: z.string().trim().max(48).optional(),
      expiresAt: z.number().int().positive().optional(),
    })).mutation(async ({ ctx, input }) => {
      const slug = input.slug ? slugSchema.parse(input.slug) : nanoid(7);
      if (reservedSlugs.has(slug.toLowerCase())) throw new TRPCError({ code: "BAD_REQUEST", message: "Alias tersebut dicadangkan sistem." });
      try {
        return await createLink({ userId: ctx.user.id, title: input.title, targetUrl: input.targetUrl, slug, expiresAt: input.expiresAt ?? null });
      } catch (error: any) {
        if (error?.code === "ER_DUP_ENTRY") throw new TRPCError({ code: "CONFLICT", message: "Alias sudah digunakan. Pilih alias lain." });
        throw error;
      }
    }),
    toggle: protectedProcedure.input(z.object({ id: z.number().int().positive(), status: z.enum(["active", "paused"]) })).mutation(({ ctx, input }) => updateLink(input.id, ctx.user.id, { status: input.status })),
    remove: protectedProcedure.input(z.object({ id: z.number().int().positive() })).mutation(async ({ ctx, input }) => { await deleteLink(input.id, ctx.user.id); return { success: true }; }),
    stats: protectedProcedure.input(z.object({ id: z.number().int().positive() })).query(({ ctx, input }) => getLinkStats(input.id, ctx.user.id)),
    resolve: publicProcedure.input(z.object({ slug: slugSchema })).query(async ({ input }) => {
      const link = await getLinkBySlug(input.slug);
      if (!link || link.status !== "active" || (link.expiresAt && link.expiresAt < Date.now())) return null;
      await recordClick(link.id);
      return { targetUrl: link.targetUrl, title: link.title };
    }),
  }),
  admin: router({
    overview: adminProcedure.query(async () => listAllLinks()),
  }),
});

export type AppRouter = typeof appRouter;
