import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";
import { compare } from "bcryptjs";
import { z } from "zod";

import { prisma } from "@/lib/prisma";

// =====================================================================
// NextAuth.js (Auth.js v5) · Credentials-Provider mit JWT-Strategie
// ---------------------------------------------------------------------
// Mandantenfähigkeit: `id`, `role`, `kitaId`, `familyId` werden über die JWT-/Session-
// Callbacks aus der DB in jede Session durchgeschleift, damit jede
// Server Action / API-Route den Tenant-Filter setzen kann.
// =====================================================================

const credentialsSchema = z.object({
  email: z.string().email().toLowerCase().trim(),
  password: z.string().min(1),
});

// Brute-Force-Schutz auf Account-Ebene (planung.md §5.5).
// Globales Rate-Limiting ergänzen wir später per Middleware.
const MAX_FAILED_ATTEMPTS = 10;
const LOCKOUT_MINUTES = 15;

export const { handlers, auth, signIn, signOut } = NextAuth({
  session: { strategy: "jwt" },
  pages: {
    signIn: "/login",
  },
  providers: [
    Credentials({
      name: "Credentials",
      credentials: {
        email: { label: "E-Mail", type: "email" },
        password: { label: "Passwort", type: "password" },
      },
      async authorize(rawCredentials) {
        const parsed = credentialsSchema.safeParse(rawCredentials);
        if (!parsed.success) return null;
        const { email, password } = parsed.data;

        const user = await prisma.user.findUnique({ where: { email } });
        if (!user) return null;

        // Account temporär gesperrt → Login pauschal ablehnen.
        if (user.lockedUntil && user.lockedUntil > new Date()) {
          return null;
        }

        const passwordOk = await compare(password, user.passwordHash);
        if (!passwordOk) {
          const nextAttempts = user.failedLoginAttempts + 1;
          await prisma.user.update({
            where: { id: user.id },
            data: {
              failedLoginAttempts: nextAttempts,
              lockedUntil:
                nextAttempts >= MAX_FAILED_ATTEMPTS
                  ? new Date(Date.now() + LOCKOUT_MINUTES * 60_000)
                  : user.lockedUntil,
            },
          });
          return null;
        }

        // Erfolgreicher Login → Counter zurücksetzen, lastLogin aktualisieren.
        await prisma.user.update({
          where: { id: user.id },
          data: {
            failedLoginAttempts: 0,
            lockedUntil: null,
            lastLoginAt: new Date(),
          },
        });

        return {
          id: user.id,
          email: user.email,
          name: `${user.firstName} ${user.lastName}`.trim(),
          role: user.role,
          kitaId: user.kitaId,
          familyId: user.familyId,
        };
      },
    }),
  ],
  callbacks: {
    /**
     * Wird beim Login (mit `user`) und bei jedem Token-Refresh (ohne `user`)
     * aufgerufen. Beim Login schreiben wir die mandantenrelevanten Felder
     * einmalig in den Token; bei jedem weiteren Aufruf re-validieren wir
     * `role` und `kitaId` gegen die DB, damit z.B. ein gerade entzogener
     * Admin-Rang sofort greift (kein 30-Tage-Token mit veralteter Rolle).
     */
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id;
        token.role = user.role;
        token.kitaId = user.kitaId;
        token.familyId = user.familyId;
        return token;
      }

      const tokenUserId = token.id ?? token.sub;
      if (tokenUserId) {
        const fresh = await prisma.user.findUnique({
          where: { id: tokenUserId },
          select: { role: true, kitaId: true, familyId: true },
        });
        if (!fresh) {
          // User wurde gelöscht → Token entwerten.
          // (Auth.js erkennt den fehlenden `sub`/`id` und meldet ab.)
          delete (token as Partial<typeof token>).id;
          delete (token as Partial<typeof token>).sub;
          return token;
        }
        token.id = tokenUserId;
        token.role = fresh.role;
        token.kitaId = fresh.kitaId;
        token.familyId = fresh.familyId;
      }

      return token;
    },

    /**
     * Wird bei jedem `auth()` /  `useSession()`-Aufruf ausgeführt.
     * Hier projizieren wir die JWT-Felder in das Session-Objekt,
     * damit Server Components / Client Components sie typsicher lesen können.
     */
    async session({ session, token }) {
      if (token && session.user) {
        session.user.id = token.id ?? token.sub;
        session.user.role = token.role;
        session.user.kitaId = token.kitaId;
        session.user.familyId = token.familyId;
      }
      return session;
    },
  },
});
