import { PrismaAdapter } from "@next-auth/prisma-adapter";
import bcrypt from "bcryptjs";
import type { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { prisma } from "./prisma";
import { checkRateLimit, getRateLimitConfig } from "./rate-limit";

const MAX_LOGIN_ATTEMPTS = 5;
const LOGIN_WINDOW_MINUTES = 15;
const LOGIN_LOCK_MINUTES = 15;

const getClientIp = (req: unknown) => {
  const headers =
    req && typeof req === "object" && "headers" in req ? (req as any).headers : null;
  if (!headers) return "unknown";
  const forwarded =
    typeof headers.get === "function"
      ? headers.get("x-forwarded-for") || headers.get("x-real-ip")
      : headers["x-forwarded-for"] || headers["x-real-ip"];
  if (!forwarded) return "unknown";
  return String(forwarded).split(",")[0].trim() || "unknown";
};

const normalizeEmail = (value?: string | null) =>
  (value || "").trim().toLowerCase();

const resetIfExpired = async (record: {
  id: string;
  attempts: number;
  lastAttempt: Date;
}) => {
  const cutoff = Date.now() - LOGIN_WINDOW_MINUTES * 60 * 1000;
  if (record.lastAttempt.getTime() < cutoff) {
    await prisma.loginAttempt.update({
      where: { id: record.id },
      data: { attempts: 0, lockedUntil: null, lastAttempt: new Date() }
    });
    return 0;
  }
  return record.attempts;
};

const recordFailure = async (email: string, ip: string, attempts: number) => {
  const nextAttempts = attempts + 1;
  const lockedUntil =
    nextAttempts >= MAX_LOGIN_ATTEMPTS
      ? new Date(Date.now() + LOGIN_LOCK_MINUTES * 60 * 1000)
      : null;

  await prisma.loginAttempt.upsert({
    where: { email_ip: { email, ip } },
    update: { attempts: nextAttempts, lastAttempt: new Date(), lockedUntil },
    create: {
      email,
      ip,
      attempts: nextAttempts,
      lastAttempt: new Date(),
      lockedUntil
    }
  });
};

export const authOptions: NextAuthOptions = {
  adapter: PrismaAdapter(prisma),
  session: { strategy: "jwt" },
  providers: [
    CredentialsProvider({
      name: "credentials",
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" }
      },
      async authorize(credentials, req) {
        if (!credentials?.email || !credentials.password) {
          return null;
        }

        const email = normalizeEmail(credentials.email);
        const ip = getClientIp(req);
        const rateConfig = getRateLimitConfig("RATE_LIMIT_LOGIN", 10);
        const rate = await checkRateLimit({
          key: `login:${email}:${ip}`,
          limit: rateConfig.limit,
          windowMs: rateConfig.windowMs
        });
        if (!rate.ok) {
          throw new Error("RATE_LIMIT");
        }
        let attempt: { id: string; attempts: number; lastAttempt: Date; lockedUntil: Date | null } | null = null;
        try {
          attempt = await prisma.loginAttempt.findUnique({
            where: { email_ip: { email, ip } }
          });
        } catch {
          attempt = null;
        }

        if (attempt?.lockedUntil && attempt.lockedUntil > new Date()) {
          throw new Error("LOCKED");
        }

        let attempts = 0;
        if (attempt) {
          try {
            attempts = await resetIfExpired(attempt);
          } catch {
            attempts = attempt.attempts;
          }
        }

        const user = await prisma.user.findUnique({
          where: { email }
        });

        if (!user) {
          try {
            await recordFailure(email, ip, attempts);
          } catch {
            // ignore
          }
          throw new Error("INVALID");
        }

        const valid = await bcrypt.compare(
          credentials.password,
          user.passwordHash
        );

        if (!valid) {
          try {
            await recordFailure(email, ip, attempts);
          } catch {
            // ignore
          }
          throw new Error("INVALID");
        }

        if (user.status !== "ACTIVE") {
          throw new Error("PENDING");
        }

        if (!user.emailVerified) {
          throw new Error("EMAIL_NOT_VERIFIED");
        }

        if (attempt) {
          try {
            await prisma.loginAttempt.delete({ where: { id: attempt.id } });
          } catch {
            // ignore
          }
        }

        return {
          id: user.id,
          email: user.email,
          name: user.name,
          role: user.role,
          status: user.status,
          emailVerified: user.emailVerified
        } as any;
      }
    })
  ],
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.id = (user as any).id;
        token.role = (user as any).role;
        token.status = (user as any).status;
        token.emailVerified = (user as any).emailVerified;
      }
      return token;
    },
    async session({ session, token }) {
      if (session.user) {
        (session.user as any).id = token.id;
        (session.user as any).role = token.role;
        (session.user as any).status = token.status;
        (session.user as any).emailVerified = token.emailVerified;
      }
      return session;
    }
  },
  pages: {
    signIn: "/login"
  }
};

export const isAdminEmail = (email?: string | null) => {
  if (!email) return false;
  const list = (process.env.ADMIN_EMAILS || "")
    .split(",")
    .map((entry) => entry.trim().toLowerCase())
    .filter(Boolean);
  return list.includes(email.toLowerCase());
};

export const isSuperAdminEmail = (email?: string | null) => {
  if (!email) return false;
  const list = (process.env.SUPERADMIN_EMAILS || "")
    .split(",")
    .map((entry) => entry.trim().toLowerCase())
    .filter(Boolean);
  return list.includes(email.toLowerCase());
};
