import { NextResponse } from 'next/server';
import { sql, asc, or, eq } from 'drizzle-orm';
import { db, users, restaurants, branches, aiAgentConfigs, plans, subscriptions } from '@server/db/drizzle';
import { initDatabase } from '@server/db/init';
import { withErrorHandler } from '@server/middleware/withErrorHandler';
import { withValidation, ParsedRequest } from '@server/middleware/withValidation';
import { z } from 'zod';
import { ConflictError, ValidationError } from '@server/errors';
import { verifyOtp } from '@server/services/email/otp.service';
import { generateDisplayId, retryOnDisplayIdCollision } from '@server/services/admin.service';
import { createSessionToken, COOKIE_NAME } from '@server/auth';
import { enqueueEmail } from '@server/services/email/outbox.service';

const schema = z.object({
  email: z.string().email(),
  code: z.string().regex(/^\d{6}$/, 'Verification code must be 6 digits'),
});

export const POST = withErrorHandler(
  withValidation(schema, async (req: ParsedRequest<z.infer<typeof schema>>) => {
    await initDatabase();
    const email = req.parsedBody.email.toLowerCase().trim();
    const ok = await verifyOtp(email, req.parsedBody.code, 'signup_verify');
    if (!ok) throw new ValidationError('Invalid or expired verification code');

    const { rows } = await db.execute(sql`SELECT payload, expires_at FROM pending_signups WHERE email = ${email} AND expires_at > NOW() LIMIT 1`);
    const pending = rows[0] as { payload: { passwordHash: string; name: string; restaurantName: string; plan: string }; expires_at: string } | undefined;
    if (!pending) {
      // Cleanup any expired row
      await db.execute(sql`DELETE FROM pending_signups WHERE email = ${email} AND expires_at <= NOW()`);
      throw new ValidationError('Your signup session has expired — please register again');
    }

    const { passwordHash, name, restaurantName, plan: planSlug } = pending.payload;

    // Re-check email isn't taken
    const existing = await db.select({ id: users.id }).from(users).where(eq(users.email, email));
    if (existing.length > 0) throw new ConflictError('An account with this email already exists');

    const planRows = await db
      .select({ id: plans.id })
      .from(plans)
      .where(or(eq(sql`LOWER(${plans.name})`, planSlug.toLowerCase()), eq(sql`LOWER(${plans.slug})`, planSlug.toLowerCase())))
      .limit(1);
    let resolvedPlanId = planRows[0]?.id ?? null;
    if (!resolvedPlanId) {
      const fb = await db.select({ id: plans.id }).from(plans).orderBy(asc(plans.priceMonthly)).limit(1);
      resolvedPlanId = fb[0]?.id ?? null;
    }

    const created = await retryOnDisplayIdCollision(() => db.transaction(async (tx) => {
      const userRows = await tx.insert(users).values({
        restaurantId: null, branchId: null, role: 'owner',
        name, email, passwordHash, isActive: true,
      }).returning();
      const user = userRows[0];
      await tx.execute(sql`UPDATE users SET email_verified_at = NOW() WHERE id = ${user.id}`);

      const displayId = generateDisplayId();

      const restRows = await tx.insert(restaurants).values({
        name: restaurantName, ownerId: user.id, plan: planSlug, status: 'active', displayId,
      }).returning();
      const restaurant = restRows[0];

      // Auto-generate a unique storefront slug for the new restaurant
      await tx.execute(sql`
        UPDATE restaurants r SET slug = (
          SELECT CASE
            WHEN NOT EXISTS (SELECT 1 FROM restaurants WHERE slug = base AND id <> r.id) THEN base
            ELSE base || '-' || substr(r.id::text, 1, 6)
          END
          FROM (SELECT public._storefront_slugify(${restaurantName}) AS base) s
        )
        WHERE r.id = ${restaurant.id} AND (r.slug IS NULL OR r.slug = '')
      `);

      const branchRows = await tx.insert(branches).values({
        restaurantId: restaurant.id, name: restaurantName + ' — Main Branch', isActive: true,
      }).returning();
      const branch = branchRows[0];

      // Auto-generate a unique branch slug (unique within restaurant)
      await tx.execute(sql`
        UPDATE branches b SET slug = (
          SELECT CASE
            WHEN NOT EXISTS (SELECT 1 FROM branches WHERE slug = base AND restaurant_id = b.restaurant_id AND id <> b.id) THEN base
            ELSE base || '-' || substr(b.id::text, 1, 6)
          END
          FROM (SELECT public._storefront_slugify(${restaurantName + ' Main Branch'}) AS base) s
        )
        WHERE b.id = ${branch.id} AND (b.slug IS NULL OR b.slug = '')
      `);

      const updated = await tx.update(users)
        .set({ restaurantId: restaurant.id, branchId: branch.id })
        .where(eq(users.id, user.id))
        .returning();

      await tx.insert(aiAgentConfigs).values({
        restaurantId: restaurant.id, branchId: branch.id,
        voiceEnabled: true, chatEnabled: true, model: 'gpt-4o',
      });

      await tx.insert(subscriptions).values({
        restaurantId: restaurant.id, planId: resolvedPlanId, status: 'trial',
        trialStart: sql`NOW()`, trialEnd: sql`NOW() + INTERVAL '14 days'`,
      }).onConflictDoUpdate({
        target: subscriptions.restaurantId,
        set: { planId: sql`EXCLUDED.plan_id`, status: 'trial', trialStart: sql`EXCLUDED.trial_start`, trialEnd: sql`EXCLUDED.trial_end` },
      });

      return { user: updated[0] ?? user, restaurant, branch };
    }));

    // Cleanup pending row
    await db.execute(sql`DELETE FROM pending_signups WHERE email = ${email}`);

    // Send welcome email (immediate)
    try {
      await enqueueEmail({
        to: email,
        templateKey: 'welcome',
        channel: 'immediate',
        kind: 'welcome',
        restaurantId: created.restaurant.id,
        vars: { name },
      });
    } catch { /* non-fatal */ }

    const { user, restaurant, branch } = created;
    const token = await createSessionToken({
      userId: user.id, email: user.email, role: user.role!,
      restaurantId: user.restaurantId!, branchId: user.branchId!,
    });

    const response = NextResponse.json({
      user: {
        id: user.id, email: user.email, name: user.name, role: user.role,
        restaurant_id: user.restaurantId, branch_id: user.branchId,
        restaurants: { id: restaurant.id, name: restaurant.name },
        branches: { id: branch.id, name: branch.name },
      },
    });
    response.cookies.set(COOKIE_NAME, token, {
      httpOnly: true, secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax', maxAge: 7 * 24 * 60 * 60, path: '/',
    });
    return response;
  })
);
