/**
 * Demo Booking Endpoint
 *
 * Security features:
 * - Rate limiting (5 bookings per hour per IP)
 * - CSRF protection
 * - Input validation and sanitization
 * - Convex database persistence (PRIMARY)
 * - n8n webhook integration with retry logic
 * - Audit logging
 */

import { NextRequest } from "next/server";
import { withRateLimit, rateLimiters } from "@/lib/rate-limiter";
import { withCSRF } from "@/lib/csrf";
import {
  validateEmail,
  validatePhone,
  validateName,
  sanitizeInput,
} from "@/lib/input-validation";
import { logger } from "@/lib/logger";
import { queueDemoBookingWebhook } from "@/lib/queue/webhook-queue";
import { sendDemoBookingReceivedEmail } from "@/lib/email";
import { verifyRecaptcha, isRecaptchaConfigured } from "@/lib/recaptcha";
import { ApiResponse } from "@/lib/api/response-helpers";
import { hashEmail } from "@/lib/utils/privacy";
import { ALLOWED_DURATIONS, BOOKING_RULES } from "@/lib/config/constants";
import validator from "validator";
import { FORM_SECURITY } from "@/lib/constants/form-security";
import { convexMutation } from "@/lib/convex-client";

export const dynamic = "force-dynamic";
export const runtime = "nodejs";

/**
 * Validate appointment date (must be in the future)
 */
function validateAppointmentDate(dateString: string): {
  valid: boolean;
  error?: string;
  date?: Date;
} {
  if (!dateString) {
    return { valid: false, error: "Appointment date is required" };
  }

  // Validate ISO date format
  if (!validator.isISO8601(dateString)) {
    return { valid: false, error: "Invalid date format" };
  }

  const appointmentDate = new Date(dateString);
  const now = new Date();

  // Reset time to start of day for comparison
  now.setHours(0, 0, 0, 0);
  appointmentDate.setHours(0, 0, 0, 0);

  if (appointmentDate < now) {
    return { valid: false, error: "Appointment date must be in the future" };
  }

  // Validate not too far in future
  const maxDate = new Date();
  maxDate.setDate(maxDate.getDate() + BOOKING_RULES.MAX_ADVANCE_DAYS);

  if (appointmentDate > maxDate) {
    return {
      valid: false,
      error: `Appointment date cannot be more than ${BOOKING_RULES.MAX_ADVANCE_DAYS} days in the future`,
    };
  }

  return { valid: true, date: new Date(dateString) };
}

/**
 * Validate appointment time format
 */
function validateAppointmentTime(timeString: string): {
  valid: boolean;
  error?: string;
} {
  if (!timeString) {
    return { valid: false, error: "Appointment time is required" };
  }

  const sanitized = sanitizeInput(timeString);

  // Validate HH:MM format (24-hour)
  if (!/^([01]\d|2[0-3]):([0-5]\d)$/.test(sanitized)) {
    return { valid: false, error: "Invalid time format (use HH:MM)" };
  }

  return { valid: true };
}

/**
 * Validate duration
 */
function validateDuration(duration: string): {
  valid: boolean;
  error?: string;
} {
  if (!duration) {
    return { valid: false, error: "Duration is required" };
  }

  const sanitized = sanitizeInput(duration);

  if (!ALLOWED_DURATIONS.includes(sanitized)) {
    return {
      valid: false,
      error: `Duration must be one of: ${ALLOWED_DURATIONS.join(", ")}`,
    };
  }

  return { valid: true };
}

async function demoBookingHandler(request: NextRequest) {
  try {
    const body = await request.json();
    const {
      fullName,
      email,
      telephone,
      appointmentDate,
      appointmentTime,
      duration,
      specialRequirements,
      language,
      timezone,
      recaptchaToken,
      honeypot,
      formStartedAt,
      timeOnPageMs,
      wasRestored, // Flag indicating session was restored from localStorage
    } = body;

    const honeypotValue = typeof honeypot === "string" ? honeypot.trim() : "";
    const parsedStart =
      typeof formStartedAt === "number" ? formStartedAt : Number(formStartedAt);
    const parsedDuration =
      typeof timeOnPageMs === "number" ? timeOnPageMs : Number(timeOnPageMs);
    const calculatedDuration = Number.isNaN(parsedDuration)
      ? Number.isNaN(parsedStart)
        ? NaN
        : Math.max(0, Date.now() - parsedStart)
      : parsedDuration;

    if (honeypotValue) {
      logger.warn("Demo booking honeypot triggered", { email });
      return ApiResponse.forbidden("Submission blocked. Please try again.");
    }

    const hasTimingMetadata = !Number.isNaN(calculatedDuration);
    const isRestoredSession = wasRestored === true;

    if (!hasTimingMetadata) {
      logger.warn("Demo booking missing timing metadata", { email });
    } else {
      // Skip timing validation for restored sessions (user returned to complete booking)
      // This prevents false positives when page refreshes or users navigate back
      if (isRestoredSession) {
        logger.info(
          "Demo booking from restored session - skipping timing check",
          {
            email,
            timeOnPageMs: calculatedDuration,
          },
        );
      } else if (calculatedDuration < FORM_SECURITY.MIN_DURATION_MS) {
        logger.warn("Demo booking submitted too quickly", {
          email,
          timeOnPageMs: calculatedDuration,
        });
        return ApiResponse.forbidden("Submission blocked. Please try again.");
      } else if (calculatedDuration < FORM_SECURITY.SUSPICIOUS_DURATION_MS) {
        logger.info("Fast demo booking submission detected", {
          email,
          timeOnPageMs: calculatedDuration,
        });
      }
    }

    // Verify reCAPTCHA if configured (bot protection)
    if (isRecaptchaConfigured()) {
      if (!recaptchaToken) {
        return ApiResponse.badRequest("Bot verification required");
      }

      const recaptchaResult = await verifyRecaptcha(
        recaptchaToken,
        "demo_booking",
      );
      if (!recaptchaResult.success) {
        logger.warn("reCAPTCHA verification failed", {
          score: recaptchaResult.score,
          error: recaptchaResult.error,
        });
        return ApiResponse.forbidden(
          "Bot verification failed. Please try again.",
        );
      }

      logger.info("reCAPTCHA verified", { score: recaptchaResult.score });
    }

    // Validate required fields
    if (
      !fullName ||
      !email ||
      !telephone ||
      !appointmentDate ||
      !appointmentTime ||
      !duration
    ) {
      return ApiResponse.badRequest("Missing required fields");
    }

    // Validate language
    if (!language || !["ar", "en"].includes(language)) {
      return ApiResponse.badRequest('Invalid language. Must be "ar" or "en"');
    }

    // Validate and sanitize email
    const emailValidation = validateEmail(email as string);
    if (!emailValidation.valid) {
      return ApiResponse.badRequest(emailValidation.error || "Invalid email");
    }

    // Validate and sanitize name
    const nameValidation = validateName(fullName as string);
    if (!nameValidation.valid) {
      return ApiResponse.badRequest(nameValidation.error || "Invalid name");
    }

    // Validate and sanitize phone
    const phoneValidation = validatePhone(telephone as string);
    if (!phoneValidation.valid) {
      return ApiResponse.badRequest(phoneValidation.error || "Invalid phone");
    }

    // Validate appointment date
    const dateValidation = validateAppointmentDate(appointmentDate as string);
    if (!dateValidation.valid) {
      return ApiResponse.badRequest(dateValidation.error || "Invalid date");
    }

    // Extract date-only string (YYYY-MM-DD) to prevent timezone issues
    // User sees: Oct 27 → Database stores: 2025-10-27 (not Oct 26 due to UTC conversion)
    const appointmentDateOnly = appointmentDate.split("T")[0]; // "2025-10-27"

    // Validate appointment time
    const timeValidation = validateAppointmentTime(appointmentTime as string);
    if (!timeValidation.valid) {
      return ApiResponse.badRequest(timeValidation.error || "Invalid time");
    }

    // Validate duration
    const durationValidation = validateDuration(duration as string);
    if (!durationValidation.valid) {
      return ApiResponse.badRequest(
        durationValidation.error || "Invalid duration",
      );
    }

    // Validate timezone (optional, defaults to Asia/Qatar)
    const VALID_TIMEZONES = [
      "Asia/Qatar",
      "Asia/Dubai",
      "Asia/Riyadh",
      "Asia/Kuwait",
      "Asia/Bahrain",
      "Asia/Muscat",
      "Europe/London",
      "America/New_York",
      "UTC",
    ];
    const sanitizedTimezone =
      timezone && typeof timezone === "string"
        ? sanitizeInput(timezone)
        : "Asia/Qatar";

    if (!VALID_TIMEZONES.includes(sanitizedTimezone)) {
      logger.warn("Invalid timezone provided, using default", {
        provided: timezone,
      });
    }

    // Sanitize optional fields
    const sanitizedEmail = sanitizeInput(email).toLowerCase();
    const sanitizedName = nameValidation.sanitized!;
    const sanitizedPhone = phoneValidation.sanitized!;
    const sanitizedRequirements = specialRequirements
      ? sanitizeInput(specialRequirements).substring(
          0,
          BOOKING_RULES.SPECIAL_REQUIREMENTS_MAX_LENGTH,
        )
      : null;

    // Generate a unique ID for the booking
    const bookingId = crypto.randomUUID();

    // Save to Convex
    let booking: {
      id: string;
      fullName: string;
      email: string;
      telephone: string;
      appointmentDate: Date;
      appointmentTime: string;
      duration: string;
      language: string;
      status: string;
      specialRequirements: string | null;
      customerTimezone: string | null;
    };
    let convexDemoBookingId: string | null = null;
    try {
      const appointmentDateMs = new Date(
        `${appointmentDateOnly}T00:00:00.000Z`,
      ).getTime();
      const convexId = await convexMutation(
        "bookings/mutations:createDemoBooking",
        {
          fullName: sanitizedName,
          email: sanitizedEmail,
          telephone: sanitizedPhone,
          appointmentDate: appointmentDateMs,
          appointmentTime: sanitizeInput(appointmentTime),
          duration: sanitizeInput(duration),
          specialRequirements: sanitizedRequirements ?? undefined,
          language: sanitizeInput(language),
          customerTimezone: sanitizedTimezone,
          status: "pending",
          webhookStatus: "pending",
          webhookRetryCount: 0,
        },
      );
      convexDemoBookingId = convexId as string;
      // Shape a compatible booking object for downstream code
      booking = {
        id: bookingId, // use the pre-generated UUID as display/reference ID
        fullName: sanitizedName,
        email: sanitizedEmail,
        telephone: sanitizedPhone,
        appointmentDate: new Date(`${appointmentDateOnly}T00:00:00.000Z`),
        appointmentTime: sanitizeInput(appointmentTime),
        duration: sanitizeInput(duration),
        language: sanitizeInput(language),
        status: "pending",
        specialRequirements: sanitizedRequirements,
        customerTimezone: sanitizedTimezone,
      };
    } catch (dbError) {
      const message =
        dbError instanceof Error ? dbError.message : "Unknown error";
      logger.error("Failed to create demo booking", {
        error: message,
      });

      // Handle unique constraint violation (Prisma error)
      if (
        dbError &&
        typeof dbError === "object" &&
        "code" in dbError &&
        (dbError as { code?: string }).code === "P2002"
      ) {
        return ApiResponse.conflict(
          "Unable to create booking. Please try a different time slot or contact support.",
        );
      }

      return ApiResponse.serverError();
    }

    // Create Google Calendar event with Meet link (non-blocking)
    // Import the helper at the top: import { createDemoBookingEvent } from '@/lib/google/calendar';
    let meetingLink: string | null = null;
    let googleCalendarEventId: string | null = null;
    let googleConferenceId: string | null = null;

    try {
      const { createDemoBookingEvent } = await import("@/lib/google/calendar");

      const calendarResult = await createDemoBookingEvent({
        id: booking.id,
        fullName: booking.fullName,
        email: booking.email,
        telephone: booking.telephone,
        appointmentDate: booking.appointmentDate,
        appointmentTime: booking.appointmentTime,
        duration: booking.duration,
        language: booking.language as "ar" | "en",
        timezone: booking.customerTimezone || "Asia/Qatar",
        specialRequirements: booking.specialRequirements || undefined,
      });

      if (calendarResult.success && calendarResult.hangoutLink) {
        meetingLink = calendarResult.hangoutLink;
        googleCalendarEventId = calendarResult.eventId || null;
        googleConferenceId = calendarResult.conferenceId || null;

        logger.info("Google Calendar event created successfully", {
          bookingId: booking.id,
          eventId: googleCalendarEventId,
          hasLink: !!meetingLink,
        });

        // Update booking with Google Calendar metadata
        try {
          // For Convex, use the Convex mutation with the Convex document ID
          // convexDemoBookingId is the Convex Id<"demo_bookings">
          await convexMutation(
            "bookings/mutations:updateDemoBookingCalendarData",
            {
              id: convexDemoBookingId as `${string}`,
              googleCalendarEventId: googleCalendarEventId ?? undefined,
              googleConferenceId: googleConferenceId ?? undefined,
              googleJoinUrl: meetingLink ?? undefined,
              meetingLink: meetingLink ?? undefined,
            },
          );
        } catch (updateError) {
          const updateMessage =
            updateError instanceof Error
              ? updateError.message
              : "Unknown error";
          logger.error(
            "Failed to update booking with Google Calendar metadata",
            {
              bookingId: booking.id,
              error: updateMessage,
            },
          );
        }
      } else {
        logger.warn(
          "Google Calendar event creation failed or returned no link",
          {
            bookingId: booking.id,
            error: calendarResult.error,
          },
        );
      }
    } catch (calendarError) {
      // Log error but don't block the booking response
      const calendarMessage =
        calendarError instanceof Error
          ? calendarError.message
          : "Unknown error";
      logger.error("Google Calendar integration error", {
        bookingId: booking.id,
        error: calendarMessage,
      });
    }

    // Log successful booking (email hashed for privacy compliance)
    logger.info("Demo booking created successfully", {
      bookingId: booking.id,
      emailHash: hashEmail(sanitizedEmail), // GDPR-compliant
      date: booking.appointmentDate,
      hasMeetLink: !!meetingLink,
    });

    // Format date for email display
    const bookingDateObj = new Date(booking.appointmentDate);
    const formattedDate = bookingDateObj.toLocaleDateString(
      language === "ar" ? "ar-SA" : "en-US",
      { weekday: "long", year: "numeric", month: "long", day: "numeric" },
    );

    // Send immediate confirmation email to customer with Meet link
    sendDemoBookingReceivedEmail(
      booking.email,
      booking.fullName,
      formattedDate,
      booking.appointmentTime,
      booking.duration,
      language as "ar" | "en",
      meetingLink || undefined, // Pass Meet link if available
    )
      .then((emailSent) => {
        if (emailSent) {
          logger.info("Demo booking confirmation email sent", {
            bookingId: booking.id,
            withMeetLink: !!meetingLink,
          });
        } else {
          logger.error("Failed to send demo booking email", {
            bookingId: booking.id,
          });
        }
      })
      .catch((error) => {
        const emailMessage =
          error instanceof Error ? error.message : "Unknown error";
        logger.error("Email send error", {
          bookingId: booking.id,
          error: emailMessage,
        });
      });

    // Queue n8n webhook for background processing (non-blocking, with retries)
    // We don't await this so user gets immediate response
    queueDemoBookingWebhook(booking.id, {
      fullName: booking.fullName,
      email: booking.email,
      telephone: booking.telephone,
      date:
        booking.appointmentDate instanceof Date
          ? booking.appointmentDate.toISOString().split("T")[0]
          : String(booking.appointmentDate).split("T")[0],
      time: booking.appointmentTime,
      duration: booking.duration,
      specialRequirements: booking.specialRequirements,
      language: booking.language,
      status: booking.status,
      // Google Calendar metadata
      timezone: booking.customerTimezone || "Asia/Qatar",
      meetingLink: meetingLink || undefined,
      googleCalendarEventId: googleCalendarEventId || undefined,
    }).catch((error) => {
      // Log webhook queue errors but don't fail the request
      logger.error("Webhook queue error", { bookingId: booking.id, error });
    });

    // Create staff alert for new demo booking (non-blocking)
    convexMutation("notifications/mutations:createStaffAlert", {
      type: "NEW_DEMO",
      title: "New Demo Booking",
      message: `${sanitizedName} requested a demo appointment`,
      severity: "INFO",
      metadata: {
        bookingId: booking.id,
        email: sanitizedEmail,
        name: sanitizedName,
        appointmentDate: appointmentDateOnly,
        appointmentTime: sanitizeInput(appointmentTime),
      },
    }).catch((error) => {
      logger.error("Failed to create staff alert", { error });
    });

    return ApiResponse.created({
      success: true,
      message: "Demo booking created successfully",
      bookingId: booking.id,
      meetingLink: meetingLink || undefined, // Include Google Meet link if available
    });
  } catch (error) {
    const message = error instanceof Error ? error.message : "Unknown error";
    logger.error("Demo booking API error", { error: message });
    return ApiResponse.serverError();
  }
}

// Apply rate limiting (5 bookings per hour) and CSRF protection
export const POST = withCSRF(
  withRateLimit(demoBookingHandler, rateLimiters.demoBooking),
);
