import { prisma } from "@capakraken/db";
import { authRateLimiter } from "@capakraken/api/middleware/rate-limit";
import { createAuditEntry } from "@capakraken/api/lib/audit";
import { logger } from "@capakraken/api/lib/logger";
import NextAuth, { type NextAuthConfig } from "next-auth";
import Credentials from "next-auth/providers/credentials";
import { verify } from "@node-rs/argon2";
import { z } from "zod";

const LoginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(1),
  totp: z.string().optional(),
});

const authConfig = {
  trustHost: true,
  providers: [
    Credentials({
      name: "credentials",
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" },
        totp: { label: "TOTP", type: "text" },
      },
      async authorize(credentials) {
        const parsed = LoginSchema.safeParse(credentials);
        if (!parsed.success) return null;

        const { email, password, totp } = parsed.data;
        const isE2eTestMode = process.env["E2E_TEST_MODE"] === "true";

        // Rate limit: 5 login attempts per 15 minutes per email
        const rateLimitResult = isE2eTestMode
          ? { allowed: true }
          : authRateLimiter(email.toLowerCase());
        if (!rateLimitResult.allowed) {
          // Audit failed login (rate limited)
          void createAuditEntry({
            db: prisma,
            entityType: "Auth",
            entityId: email.toLowerCase(),
            entityName: email,
            action: "CREATE",
            summary: "Login blocked — rate limit exceeded",
            source: "ui",
          });
          throw new Error("Too many login attempts. Please try again later.");
        }

        const user = await prisma.user.findUnique({ where: { email } });
        if (!user?.passwordHash) {
          logger.warn({ email, reason: "user_not_found" }, "Failed login attempt");
          // Audit failed login (unknown user)
          void createAuditEntry({
            db: prisma,
            entityType: "Auth",
            entityId: email.toLowerCase(),
            entityName: email,
            action: "CREATE",
            summary: "Login failed — user not found",
            source: "ui",
          });
          return null;
        }

        const isValid = await verify(user.passwordHash, password);
        if (!isValid) {
          logger.warn({ email, reason: "invalid_password" }, "Failed login attempt");
          // Audit failed login (bad password)
          void createAuditEntry({
            db: prisma,
            entityType: "Auth",
            entityId: user.id,
            entityName: user.email,
            action: "CREATE",
            userId: user.id,
            summary: "Login failed — invalid password",
            source: "ui",
          });
          return null;
        }

        // MFA check: if TOTP is enabled, require the token
        if (user.totpEnabled && user.totpSecret) {
          if (!totp) {
            // Signal to the client that MFA is required (include userId for re-submission)
            throw new Error("MFA_REQUIRED:" + user.id);
          }

          const { TOTP, Secret } = await import("otpauth");
          const totpInstance = new TOTP({
            issuer: "CapaKraken",
            label: user.email,
            algorithm: "SHA1",
            digits: 6,
            period: 30,
            secret: Secret.fromBase32(user.totpSecret),
          });

          const delta = totpInstance.validate({ token: totp, window: 1 });
          if (delta === null) {
            logger.warn({ email, reason: "invalid_totp" }, "Failed MFA verification");
            void createAuditEntry({
              db: prisma,
              entityType: "Auth",
              entityId: user.id,
              entityName: user.email,
              action: "CREATE",
              userId: user.id,
              summary: "Login failed — invalid TOTP token",
              source: "ui",
            });
            throw new Error("INVALID_TOTP");
          }
        }

        // Track last login time
        await prisma.user.update({
          where: { id: user.id },
          data: { lastLoginAt: new Date() },
        });

        logger.info({ email, userId: user.id }, "Successful login");
        // Audit successful login
        void createAuditEntry({
          db: prisma,
          entityType: "Auth",
          entityId: user.id,
          entityName: user.email,
          action: "CREATE",
          userId: user.id,
          summary: "User logged in",
          source: "ui",
        });

        return {
          id: user.id,
          email: user.email,
          name: user.name,
          role: user.systemRole,
        };
      },
    }),
  ],
  callbacks: {
    async session({ session, token }) {
      if (token.sub) {
        session.user.id = token.sub;
      }
      if (token.role) {
        (session.user as typeof session.user & { role: string }).role = token.role as string;
      }
      return session;
    },
    async jwt({ token, user }) {
      if (user) {
        token.role = (user as typeof user & { role: string }).role;

        // Generate a unique JWT ID for session tracking
        const jti = crypto.randomUUID();
        token.jti = jti;

        // Enforce concurrent session limit (kick-oldest strategy)
        try {
          const settings = await prisma.systemSettings.findUnique({
            where: { id: "singleton" },
            select: { maxConcurrentSessions: true },
          });
          const maxSessions = settings?.maxConcurrentSessions ?? 3;

          // Register this new session
          await prisma.activeSession.create({
            data: { userId: user.id!, jti },
          });

          // Count active sessions and delete the oldest if over the limit
          const activeSessions = await prisma.activeSession.findMany({
            where: { userId: user.id! },
            orderBy: { createdAt: "asc" },
            select: { id: true },
          });

          if (activeSessions.length > maxSessions) {
            const toDelete = activeSessions.slice(0, activeSessions.length - maxSessions);
            await prisma.activeSession.deleteMany({
              where: { id: { in: toDelete.map((s) => s.id) } },
            });
            logger.info({ userId: user.id, kicked: toDelete.length, maxSessions }, "Kicked oldest sessions");
          }
        } catch (err) {
          // Non-blocking: don't prevent login if session tracking fails
          logger.error({ err }, "Failed to enforce concurrent session limit");
        }
      }
      return token;
    },
  },
  events: {
    async signOut(message) {
      // Auth.js fires this event on sign-out; extract userId from the JWT token
      const token = "token" in message ? message.token : null;
      const userId = token?.sub ?? null;
      const email = token?.email ?? "unknown";
      const jti = token?.jti as string | undefined;

      // Remove from active session registry
      if (jti) {
        void prisma.activeSession.delete({ where: { jti } }).catch(() => { /* already gone */ });
      }

      void createAuditEntry({
        db: prisma,
        entityType: "Auth",
        entityId: userId ?? email,
        entityName: email,
        action: "DELETE",
        ...(userId ? { userId } : {}),
        summary: "User logged out",
        source: "ui",
      });
    },
  },
  cookies: {
    sessionToken: {
      name: "authjs.session-token",
      options: {
        httpOnly: true,
        sameSite: "strict" as const,
        path: "/",
        secure: process.env.NODE_ENV === "production",
      },
    },
    callbackUrl: {
      name: "authjs.callback-url",
      options: {
        httpOnly: true,
        sameSite: "strict" as const,
        path: "/",
        secure: process.env.NODE_ENV === "production",
      },
    },
    csrfToken: {
      name: "authjs.csrf-token",
      options: {
        httpOnly: true,
        sameSite: "strict" as const,
        path: "/",
        secure: process.env.NODE_ENV === "production",
      },
    },
  },
  pages: {
    signIn: "/auth/signin",
  },
  session: {
    strategy: "jwt",
    maxAge: 28800,     // 8 hours absolute timeout
    updateAge: 1800,   // Refresh token every 30 minutes (idle timeout)
  },
} satisfies NextAuthConfig;

export const { handlers, auth } = NextAuth(authConfig);
