import bcrypt from "bcryptjs";
import crypto from "crypto";
import type { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";

type AdminUser = {
  id: string;
  email: string;
  role: "admin";
};

type JwtWithAdmin = {
  role?: "admin";
  email?: string;
};

function authDebug(message: string, meta?: Record<string, unknown>) {
  // Avoid leaking auth details in production logs.
  if (process.env.NODE_ENV === "production") return;
  if (process.env.AUTH_DEBUG !== "1") return;
  // Keep output single-line-ish and intentionally do not log passwords/hashes.
  try {
    // eslint-disable-next-line no-console
    console.warn(`[auth][credentials] ${message}`, meta ?? {});
  } catch {
    // ignore
  }
}

function env(name: string): string | undefined {
  const v0 = process.env[name];
  if (!v0) return undefined;
  const v = v0.trim();
  if (v.length === 0) return undefined;
  // Be tolerant of accidental quoting in .env files.
  if (
    (v.startsWith('"') && v.endsWith('"') && v.length >= 2) ||
    (v.startsWith("'") && v.endsWith("'") && v.length >= 2)
  ) {
    return v.slice(1, -1).trim() || undefined;
  }
  return v;
}

function safeEqual(a: string, b: string): boolean {
  const aBuf = Buffer.from(a);
  const bBuf = Buffer.from(b);
  if (aBuf.length !== bBuf.length) return false;
  return crypto.timingSafeEqual(aBuf, bBuf);
}

function looksLikeBcryptHash(v: string): boolean {
  // bcrypt: $2a$10$ + 53 chars = 60 total
  return /^\$2[aby]\$\d{2}\$[./A-Za-z0-9]{53}$/.test(v);
}

function decodeB64(v: string): string | null {
  try {
    const s = Buffer.from(v, "base64").toString("utf8").trim();
    return s.length > 0 ? s : null;
  } catch {
    return null;
  }
}

export const authOptions: NextAuthOptions = {
  session: { strategy: "jwt" },
  pages: { signIn: "/admin/login" },
  providers: [
    CredentialsProvider({
      name: "Credentials",
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" },
      },
      async authorize(credentials): Promise<AdminUser | null> {
        const adminEmail = env("ADMIN_EMAIL");
        const adminPasswordHashRaw = env("ADMIN_PASSWORD_HASH");
        const adminPasswordHashBase64 = env("ADMIN_PASSWORD_HASH_BASE64") ?? env("ADMIN_PASSWORD_HASH_B64");
        const adminPassword = env("ADMIN_PASSWORD"); // fallback (not recommended)

        if (!adminEmail) {
          authDebug("missing ADMIN_EMAIL");
          return null;
        }

        let adminPasswordHash: string | undefined;
        if (adminPasswordHashBase64) {
          const decoded = decodeB64(adminPasswordHashBase64);
          if (!decoded) {
            authDebug("ADMIN_PASSWORD_HASH_BASE64 is not valid base64");
            return null;
          }
          adminPasswordHash = decoded;
        } else {
          adminPasswordHash = adminPasswordHashRaw;
        }

        if (adminPasswordHash && !looksLikeBcryptHash(adminPasswordHash)) {
          authDebug("ADMIN_PASSWORD_HASH does not look like bcrypt (likely mangled by env expansion)", {
            hashPrefix: adminPasswordHash.slice(0, 4),
            hashLen: adminPasswordHash.length,
            fix: "Next.js env expansion can mangle bcrypt ($...). Use ADMIN_PASSWORD_HASH_BASE64 instead.",
          });
          return null;
        }
        const email = credentials?.email?.trim();
        const password = credentials?.password;
        if (!email || !password) {
          authDebug("missing credential fields", {
            hasEmail: !!email,
            hasPassword: !!password,
          });
          return null;
        }

        // Email match
        const emailMatches = safeEqual(email.toLowerCase(), adminEmail.toLowerCase());
        if (!emailMatches) {
          authDebug("email mismatch", {
            providedEmailLen: email.length,
            providedEmailDomain: email.includes("@") ? email.split("@").slice(-1)[0] : null,
          });
          return null;
        }

        // Password match
        if (adminPasswordHash) {
          const ok = await bcrypt.compare(password, adminPasswordHash);
          if (!ok) {
            authDebug("password mismatch (bcrypt)", {
              hashPrefix: adminPasswordHash.slice(0, 4),
              hashLen: adminPasswordHash.length,
            });
            return null;
          }
        } else if (adminPassword) {
          if (!safeEqual(password, adminPassword)) {
            authDebug("password mismatch (plain)");
            return null;
          }
        } else {
          authDebug("missing ADMIN_PASSWORD_HASH and ADMIN_PASSWORD");
          return null;
        }

        authDebug("success");
        return { id: "admin", email: adminEmail, role: "admin" };
      },
    }),
  ],
  callbacks: {
    async jwt({ token, user }) {
      // Persist admin identity onto the JWT so middleware can enforce authorization.
      const u = user as AdminUser | undefined;
      if (u?.role === "admin") {
        (token as JwtWithAdmin).role = "admin";
        (token as JwtWithAdmin).email = u.email;
      }
      return token;
    },
    async session({ session, token }) {
      if (session.user) {
        (session.user as any).role = (token as JwtWithAdmin).role;
        (session.user as any).email = (token as JwtWithAdmin).email ?? session.user.email;
      }
      return session;
    },
  },
};
