import { NextResponse } from 'next/server';
import bcrypt from 'bcryptjs';
import { eq } from 'drizzle-orm';
import { db, users } from '@server/db/drizzle';
import { sql } from 'drizzle-orm';
import { initDatabase } from '@server/db/init';
import { withErrorHandler } from '@server/middleware/withErrorHandler';
import { withValidation, ParsedRequest } from '@server/middleware/withValidation';
import { signUpSchema, SignUpInput } from '@server/validators/auth.validator';
import { ConflictError } from '@server/errors';
import { issueOtp } from '@server/services/email/otp.service';
import { withRateLimit, getClientIp, checkRateLimit } from '@server/middleware/withRateLimit';

const PENDING_TTL_MIN = 30;

const signUp = withErrorHandler(
  withRateLimit(
    [
      {
        scope: 'auth:sign-up:ip-min',
        limit: 3,
        windowMs: 60_000,
        keyOf: (req) => getClientIp(req),
      },
      {
        scope: 'auth:sign-up:ip-hour',
        limit: 10,
        windowMs: 60 * 60_000,
        keyOf: (req) => getClientIp(req),
      },
    ],
    withValidation(signUpSchema, async (req: ParsedRequest<SignUpInput>) => {
      await initDatabase();
      const { email, password, ownerName, restaurantName, plan } = req.parsedBody;

      const normalizedEmail = email.toLowerCase().trim();

      // Per-email rate limit (post-validation since we need the parsed email).
      // Caps unlimited OTP sends to a single address even when the IP rotates.
      const emailLimit = checkRateLimit('auth:sign-up:email-hour', normalizedEmail, 3, 60 * 60_000);
      if (!emailLimit.allowed) {
        return NextResponse.json(
          {
            error: 'Too many sign-up attempts for this email. Please try again later.',
            code: 'RATE_LIMITED',
            retryAfter: emailLimit.retryAfterSec,
          },
          { status: 429, headers: { 'Retry-After': String(emailLimit.retryAfterSec ?? 3600) } }
        );
      }

      const passwordHash = await bcrypt.hash(password, 12);
      const name = ownerName || normalizedEmail.split('@')[0];
      const restName = restaurantName || 'My Restaurant';
      const planSlug = plan || 'starter';

      // Existing verified user → conflict
      const existing = await db
        .select({ id: users.id })
        .from(users)
        .where(eq(users.email, normalizedEmail));
      if (existing.length > 0) throw new ConflictError('An account with this email already exists');

      // Upsert pending signup
      const payload = { passwordHash, name, restaurantName: restName, plan: planSlug };
      await db.execute(sql`
        INSERT INTO pending_signups (email, payload, expires_at)
        VALUES (${normalizedEmail}, ${JSON.stringify(payload)}::jsonb, NOW() + (${PENDING_TTL_MIN} || ' minutes')::interval)
        ON CONFLICT (email) DO UPDATE SET payload = EXCLUDED.payload, expires_at = EXCLUDED.expires_at, created_at = NOW()
      `);

      await issueOtp(normalizedEmail, 'signup_verify', { name });

      return NextResponse.json({ requiresOtp: true, email: normalizedEmail });
    })
  )
);

export const POST = signUp;
