import { GraphQLError } from "graphql";
import { builder } from "../builder";
import { CurrentUserType } from "../types/index";
import { users } from "../../db/schema/index";
import { eq } from "drizzle-orm";

interface ReplyLike {
  header?: (name: string, value: string) => void;
}
import { hash, verify as verifyArgon } from "../../lib/argon";
import { setSession, deleteSession } from "../../lib/auth";
import { enqueueVerification, enqueuePasswordReset } from "../../lib/email";
import { slugify } from "../../lib/slugify";
import { nanoid } from "nanoid";

builder.mutationField("login", (t) =>
  t.field({
    type: CurrentUserType,
    args: {
      email: t.arg.string({ required: true }),
      password: t.arg.string({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      const user = await ctx.db
        .select()
        .from(users)
        .where(eq(users.email, args.email.toLowerCase()))
        .limit(1);

      if (!user[0]) throw new GraphQLError("Invalid credentials");

      const valid = await verifyArgon(user[0].password_hash, args.password);
      if (!valid) throw new GraphQLError("Invalid credentials");

      const token = nanoid(32);
      const sessionUser = {
        id: user[0].id,
        email: user[0].email,
        role: (user[0].role === "admin" ? "viewer" : user[0].role) as "model" | "viewer",
        is_admin: user[0].is_admin,
        first_name: user[0].first_name,
        last_name: user[0].last_name,
        artist_name: user[0].artist_name,
        slug: user[0].slug,
        avatar: user[0].avatar,
      };

      await setSession(token, sessionUser);

      // Set session cookie
      const isProduction = process.env.NODE_ENV === "production";
      const cookieValue = `session_token=${token}; HttpOnly; Path=/; SameSite=Strict; Max-Age=86400${isProduction ? "; Secure" : ""}`;
      (ctx.reply as ReplyLike).header?.("Set-Cookie", cookieValue);

      return user[0];
    },
  }),
);

builder.mutationField("logout", (t) =>
  t.field({
    type: "Boolean",
    resolve: async (_root, _args, ctx) => {
      const cookieHeader = ctx.request.headers.get("cookie") || "";
      const cookies = Object.fromEntries(
        cookieHeader.split(";").map((c) => {
          const [k, ...v] = c.trim().split("=");
          return [k.trim(), v.join("=")];
        }),
      );
      const token = cookies["session_token"];
      if (token) {
        await deleteSession(token);
      }
      // Clear cookie
      const isProduction = process.env.NODE_ENV === "production";
      const cookieValue = `session_token=; HttpOnly; Path=/; SameSite=Strict; Max-Age=0${isProduction ? "; Secure" : ""}`;
      (ctx.reply as ReplyLike).header?.("Set-Cookie", cookieValue);
      return true;
    },
  }),
);

builder.mutationField("register", (t) =>
  t.field({
    type: "Boolean",
    args: {
      email: t.arg.string({ required: true }),
      password: t.arg.string({ required: true }),
      firstName: t.arg.string({ required: true }),
      lastName: t.arg.string({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      const existing = await ctx.db
        .select({ id: users.id })
        .from(users)
        .where(eq(users.email, args.email.toLowerCase()))
        .limit(1);

      if (existing.length > 0) throw new GraphQLError("Email already registered");

      const passwordHash = await hash(args.password);
      const artistName = `${args.firstName} ${args.lastName}`;
      const baseSlug = slugify(artistName);
      const verifyToken = nanoid(32);

      // Ensure unique slug
      let slug = baseSlug;
      let attempt = 0;
      while (true) {
        const existing = await ctx.db
          .select({ id: users.id })
          .from(users)
          .where(eq(users.slug, slug))
          .limit(1);
        if (existing.length === 0) break;
        attempt++;
        slug = `${baseSlug}-${attempt}`;
      }

      await ctx.db.insert(users).values({
        email: args.email.toLowerCase(),
        password_hash: passwordHash,
        first_name: args.firstName,
        last_name: args.lastName,
        artist_name: artistName,
        slug,
        role: "viewer",
        email_verify_token: verifyToken,
        email_verified: false,
      });

      try {
        await enqueueVerification(args.email, verifyToken);
      } catch (e) {
        console.warn("Failed to enqueue verification email:", (e as Error).message);
      }
      return true;
    },
  }),
);

builder.mutationField("verifyEmail", (t) =>
  t.field({
    type: "Boolean",
    args: {
      token: t.arg.string({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      const user = await ctx.db
        .select()
        .from(users)
        .where(eq(users.email_verify_token, args.token))
        .limit(1);

      if (!user[0]) throw new GraphQLError("Invalid verification token");

      await ctx.db
        .update(users)
        .set({ email_verified: true, email_verify_token: null })
        .where(eq(users.id, user[0].id));

      return true;
    },
  }),
);

builder.mutationField("requestPasswordReset", (t) =>
  t.field({
    type: "Boolean",
    args: {
      email: t.arg.string({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      const user = await ctx.db
        .select()
        .from(users)
        .where(eq(users.email, args.email.toLowerCase()))
        .limit(1);

      // Always return true to prevent email enumeration
      if (!user[0]) return true;

      const token = nanoid(32);
      const expiry = new Date(Date.now() + 60 * 60 * 1000); // 1 hour

      await ctx.db
        .update(users)
        .set({ password_reset_token: token, password_reset_expiry: expiry })
        .where(eq(users.id, user[0].id));

      try {
        await enqueuePasswordReset(args.email, token);
      } catch (e) {
        console.warn("Failed to enqueue password reset email:", (e as Error).message);
      }
      return true;
    },
  }),
);

builder.mutationField("resetPassword", (t) =>
  t.field({
    type: "Boolean",
    args: {
      token: t.arg.string({ required: true }),
      newPassword: t.arg.string({ required: true }),
    },
    resolve: async (_root, args, ctx) => {
      const user = await ctx.db
        .select()
        .from(users)
        .where(eq(users.password_reset_token, args.token))
        .limit(1);

      if (!user[0]) throw new GraphQLError("Invalid or expired reset token");
      if (user[0].password_reset_expiry && user[0].password_reset_expiry < new Date()) {
        throw new GraphQLError("Reset token expired");
      }

      const passwordHash = await hash(args.newPassword);

      await ctx.db
        .update(users)
        .set({
          password_hash: passwordHash,
          password_reset_token: null,
          password_reset_expiry: null,
        })
        .where(eq(users.id, user[0].id));

      return true;
    },
  }),
);
