import { betterAuth } from 'better-auth';
import { hash as argon2Hash, verify as argon2Verify } from '@node-rs/argon2';
import { kyselyAdapter } from '@better-auth/kysely-adapter';
import { Kysely, PostgresDialect } from 'kysely';
import { Pool } from 'pg';
import { SignJWT, importPKCS8 } from 'jose';
import { readFileSync } from 'fs';
import { resolve } from 'path';
import { sendEmail, verificationEmailHtml, resetPasswordEmailHtml } from './email.js';

/**
 * Generate Apple's "client secret" — actually a JWT signed with the .p8
 * private key from the Apple Developer portal. Apple caps the validity at
 * 180 days. We regenerate at server boot and again every ~150 days while
 * the process is running, so neither restarts nor uptime can let it expire.
 *
 * Required env vars (web sign-in only — iOS native flow doesn't need any):
 *   APPLE_TEAM_ID            — 10-char Team ID from developer.apple.com
 *   APPLE_KEY_ID             — 10-char Key ID from the Sign in with Apple key
 *   APPLE_CLIENT_ID          — Services ID, e.g. com.wahwa.labwise.web
 *   APPLE_PRIVATE_KEY_PATH   — Path to the AuthKey_XXXXXXXXXX.p8 file,
 *                              relative to the server/ directory.
 *                              e.g. APPLE_PRIVATE_KEY_PATH=AuthKey_ABCD123456.p8
 */
async function generateAppleClientSecret(): Promise<string> {
  const teamId = process.env.APPLE_TEAM_ID;
  const keyId = process.env.APPLE_KEY_ID;
  const clientId = process.env.APPLE_CLIENT_ID;
  const keyPath = process.env.APPLE_PRIVATE_KEY_PATH;
  if (!teamId || !keyId || !clientId || !keyPath) return '';

  // Resolve relative to the server/ directory (where the process runs from).
  const absolutePath = resolve(process.cwd(), keyPath);
  let pem: string;
  try {
    pem = readFileSync(absolutePath, 'utf8');
  } catch {
    console.warn(`[auth] Apple private key not found at ${absolutePath}. Web Sign in with Apple disabled.`);
    return '';
  }
  const privateKey = await importPKCS8(pem, 'ES256');

  return await new SignJWT({})
    .setProtectedHeader({ alg: 'ES256', kid: keyId })
    .setIssuedAt()
    .setIssuer(teamId)
    .setAudience('https://appleid.apple.com')
    .setSubject(clientId)
    // 180 days is the maximum Apple allows.
    .setExpirationTime('180d')
    .sign(privateKey);
}

// Apple's `clientSecret` lives on this object. Better Auth captures the
// reference at startup (see @better-auth/core context init) and reads
// `options.clientSecret` lazily inside createAuthorizationCodeRequest, so
// mutating this property in-place is enough to rotate the JWT — no auth
// reinitialisation required.
const appleProviderConfig = {
  clientId: process.env.APPLE_CLIENT_ID || '',
  clientSecret: await generateAppleClientSecret(),
  appBundleIdentifier: process.env.APPLE_APP_BUNDLE_ID || '',
  audience: [
    process.env.APPLE_CLIENT_ID,
    process.env.APPLE_APP_BUNDLE_ID,
  ].filter((v): v is string => !!v),
};

if (process.env.APPLE_CLIENT_ID && !appleProviderConfig.clientSecret) {
  console.warn(
    '[auth] APPLE_CLIENT_ID is set but APPLE_TEAM_ID / APPLE_KEY_ID / APPLE_PRIVATE_KEY_PATH is missing. ' +
      'Web Sign in with Apple will not work until all four are provided. (iOS native sign-in works without them.)'
  );
}

// Self-rotating refresher: check once per day and regenerate the JWT once
// it crosses ~150 days old. Apple's hard cap is 180 days, so this leaves a
// month of head-room. The check is cheap (a single ECDSA sign) and only
// does real work on the day a rotation is due.
//
// Node's setInterval max delay is ~24.8 days (32-bit signed int) so we
// can't schedule a single 150-day timer; a daily check is the simplest
// way to stay under that limit.
if (appleProviderConfig.clientSecret) {
  const ONE_DAY_MS = 24 * 60 * 60 * 1000;
  const REFRESH_AFTER_MS = 150 * ONE_DAY_MS;
  let lastRotatedAt = Date.now();

  const timer = setInterval(async () => {
    if (Date.now() - lastRotatedAt < REFRESH_AFTER_MS) return;
    try {
      const fresh = await generateAppleClientSecret();
      if (fresh) {
        appleProviderConfig.clientSecret = fresh;
        lastRotatedAt = Date.now();
        console.log('[auth] Apple client secret rotated (150-day refresh).');
      }
    } catch (err) {
      console.error('[auth] Failed to rotate Apple client secret:', err);
    }
  }, ONE_DAY_MS);
  // Don't keep the event loop alive solely for this timer.
  timer.unref();
}

const db = new Kysely({
  dialect: new PostgresDialect({
    pool: new Pool({
      connectionString:
        process.env.DATABASE_URL ||
        'postgresql://labwise:labwise_dev_pw@localhost:5432/labwise_db',
    }),
  }),
});

export const auth = betterAuth({
  database: kyselyAdapter(db, { type: 'postgres' }),

  baseURL: process.env.BETTER_AUTH_URL || 'http://localhost:3001',
  secret:
    process.env.BETTER_AUTH_SECRET ||
    'dev-secret-change-in-production-min32chars!!',

  rateLimit: {
    enabled: false, // TODO: re-enable in production
  },

  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    password: {
      hash: (password) => argon2Hash(password, { memoryCost: 65536, timeCost: 3, parallelism: 4, algorithm: 2 }),
      verify: ({ hash, password }) => argon2Verify(hash, password),
    },
    sendResetPassword: async ({ user, url }) => {
      await sendEmail({
        to: user.email,
        subject: 'Reset your LabWise password',
        html: resetPasswordEmailHtml(url),
      });
    },
  },

  emailVerification: {
    sendVerificationEmail: async ({ user, url }) => {
      await sendEmail({
        to: user.email,
        subject: 'Verify your LabWise email',
        html: verificationEmailHtml(url),
      });
    },
  },

  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID || '',
      clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
    },
    // Sign in with Apple — see appleProviderConfig above for the rotating
    // clientSecret. Both flows (web redirect, iOS native id-token) hit the
    // same provider; `audience` lists both the Services ID and the iOS
    // bundle ID so whichever token a client presents is accepted.
    apple: appleProviderConfig,
  },

  trustedOrigins: [
    'http://localhost:5173',
    'https://labwise.wahwa.com',
    // Apple sends the OAuth code via form_post, so the POST to
    // /api/auth/callback/apple arrives with Origin: appleid.apple.com.
    'https://appleid.apple.com',
    // iOS native app callback — allows Better Auth to honour the
    // https://labwise.wahwa.com/api/ios-callback callbackURL
    'labwise://',
  ],
});
