/**
 * Interview Booking API
 *
 * POST /api/careers/interview/book
 *
 * Creates interview booking after validating token and input data.
 * Public endpoint (no auth required) - access controlled by token.
 *
 * Security:
 * - Rate limiting (3 bookings per hour per IP)
 * - CSRF protection
 * - Token validation (must be valid, not expired, not used)
 * - Input validation and sanitization
 * - SQL injection prevention (Prisma parameterized queries)
 * - XSS prevention (DOMPurify sanitization)
 * - Audit logging
 *
 * Flow:
 * 1. Validate token (must be valid, not expired, not used, not revoked)
 * 2. Validate input data (date, time, timezone, notes)
 * 3. Check for duplicate bookings
 * 4. Create booking in transaction (mark token as used + create booking)
 * 5. Send confirmation email to applicant
 * 7. Create staff alert
 * 8. Log audit event
 *
 * @module app/api/careers/interview/book
 */

import { NextRequest, NextResponse } from "next/server";
import { withRateLimit } from "@/lib/rate-limiter";
import { withCSRF } from "@/lib/csrf";
import { interviewTokenService } from "@/lib/services/interview-tokens.service";
import {
  createBooking,
  checkDuplicateBooking,
} from "@/lib/services/interview-bookings.service";
import { validateBookingRequest } from "@/lib/validators/interview-booking.validator";
import { sendConfirmation } from "@/lib/services/interview-emails.service";
import { prisma } from "@/lib/db";
import { logger } from "@/lib/logger";
import { ApiResponse } from "@/lib/api/response-helpers";
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
import { getClientIdentifier } from "@/lib/rate-limiter";
import { hashEmail } from "@/lib/utils/privacy";
import { getDataSource } from "@/lib/data-source";

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

/**
 * Create rate limiter for booking creation
 * 3 bookings per hour per IP
 */
const redis =
  process.env.UPSTASH_REDIS_REST_URL && process.env.UPSTASH_REDIS_REST_TOKEN
    ? new Redis({
        url: process.env.UPSTASH_REDIS_REST_URL,
        token: process.env.UPSTASH_REDIS_REST_TOKEN,
      })
    : null;

const bookingLimiter = redis
  ? new Ratelimit({
      redis,
      limiter: Ratelimit.slidingWindow(3, "1 h"),
      analytics: true,
      prefix: "ratelimit:interview_booking",
    })
  : {
      limit: async () => ({
        success: true,
        limit: 3,
        reset: Date.now() + 3600000,
        remaining: 2,
      }),
    };

/**
 * Interview booking handler
 */
async function bookingHandler(request: NextRequest) {
  const clientIp = getClientIdentifier(request);

  try {
    // Parse request body
    const body = await request.json();
    const { token, preferredDate, preferredTime, timezone, additionalNotes } =
      body;

    // Validate input
    const validation = validateBookingRequest({
      token,
      preferredDate,
      preferredTime,
      timezone,
      additionalNotes,
    });

    if (!validation.valid) {
      logger.warn("[InterviewBooking] Validation failed", {
        errors: validation.errors,
        ip: clientIp,
      });

      return ApiResponse.badRequest("Invalid input data", {
        errors: validation.errors,
      });
    }

    const sanitized = validation.sanitized!;

    // ── Convex data-source branch ──────────────────────────────────────
    if (getDataSource("careers") === "convex") {
      try {
        const { convexMutation, convexQuery } =
          await import("@/lib/convex-client");

        // Validate token via Convex
        const tokenResult = (await convexQuery(
          "careers/queries:validateInterviewToken",
          {
            token: sanitized.token,
          },
        )) as {
          valid: boolean;
          errorCode?: string;
          error?: string;
          tokenRecord?: {
            id: string;
            expiresAt: number;
            usedAt: unknown;
            isRevoked: boolean;
          };
          payload?: { applicationId: string; jobId: string | null };
          application?: {
            id: string;
            applicantName: string;
            applicantEmail: string;
            applicantPhone: string;
            status: string;
          } | null;
          job?: { id: string; title: string; titleAr: string } | null;
        } | null;

        if (tokenResult && !tokenResult.valid) {
          switch (tokenResult.errorCode) {
            case "EXPIRED":
              return NextResponse.json(
                { error: "This invitation link has expired" },
                { status: 410 },
              );
            case "USED":
              return ApiResponse.conflict(
                "This invitation has already been used",
              );
            case "REVOKED":
              return ApiResponse.forbidden("This invitation has been revoked");
            default:
              return ApiResponse.badRequest("Invalid invitation link");
          }
        }

        if (
          tokenResult?.valid &&
          tokenResult.payload &&
          tokenResult.application &&
          tokenResult.job
        ) {
          // Book via Convex mutation
          const bookingId = await convexMutation(
            "careers/mutations:bookInterview",
            {
              applicationId: tokenResult.payload.applicationId,
              jobId: tokenResult.payload.jobId ?? tokenResult.job.id,
              tokenId: sanitized.token,
              applicantName: tokenResult.application.applicantName,
              applicantEmail: tokenResult.application.applicantEmail,
              applicantPhone:
                tokenResult.application.applicantPhone || undefined,
              preferredDate: sanitized.preferredDate,
              preferredTime: sanitized.preferredTime,
              timezone: sanitized.timezone,
              durationMinutes: 60,
              applicantNotes: sanitized.additionalNotes || undefined,
            },
          );

          if (bookingId) {
            // Send confirmation email (non-blocking, stays as-is)
            const language: "en" | "ar" = "en";
            const dateObj = new Date(sanitized.preferredDate);
            const formattedDate = dateObj.toLocaleDateString(
              language === "ar" ? "ar-SA" : "en-US",
              {
                weekday: "long",
                year: "numeric",
                month: "long",
                day: "numeric",
              },
            );

            sendConfirmation({
              email: tokenResult.application.applicantEmail,
              details: {
                applicantName: tokenResult.application.applicantName,
                jobTitle: tokenResult.job.title,
                jobTitleAr: tokenResult.job.titleAr,
                confirmedDate: formattedDate,
                confirmedTime: sanitized.preferredTime,
                timezone: sanitized.timezone,
                language,
              },
            }).catch((error) => {
              logger.error("[InterviewBooking][Convex] Email send exception", {
                error,
              });
            });

            logger.info(
              "[InterviewBooking][Convex] Booking created successfully",
              {
                bookingId: String(bookingId),
                applicationId: tokenResult.payload.applicationId,
              },
            );

            return ApiResponse.created({
              success: true,
              message: "Interview booking created successfully",
              bookingId: String(bookingId),
              booking: {
                preferredDate: sanitized.preferredDate,
                preferredTime: sanitized.preferredTime,
                timezone: sanitized.timezone,
                status: "pending",
              },
            });
          }
        }
      } catch (error) {
        const msg = error instanceof Error ? error.message : String(error);
        if (msg === "DUPLICATE_BOOKING") {
          return ApiResponse.conflict(
            "You already have an active interview booking. Please contact us to reschedule.",
          );
        }
        console.error("[Convex fallback] careers/interview/book:", error);
        // Fall through to Prisma
      }
    }

    // Validate token
    const tokenValidation = await interviewTokenService.validateInterviewToken(
      sanitized.token,
    );

    if (!tokenValidation.valid) {
      logger.warn("[InterviewBooking] Token validation failed", {
        error: tokenValidation.error,
        errorCode: tokenValidation.errorCode,
        ip: clientIp,
      });

      // Map error codes to appropriate responses
      switch (tokenValidation.errorCode) {
        case "EXPIRED":
          return ApiResponse.gone("This invitation link has expired");
        case "USED":
          return ApiResponse.conflict("This invitation has already been used");
        case "REVOKED":
          return ApiResponse.forbidden("This invitation has been revoked");
        case "INVALID":
        case "NOT_FOUND":
        default:
          return ApiResponse.badRequest("Invalid invitation link");
      }
    }

    const { payload, tokenRecord } = tokenValidation;

    if (!payload || !tokenRecord) {
      return ApiResponse.badRequest("Invalid token data");
    }

    // Check for duplicate booking
    const isDuplicate = await checkDuplicateBooking(payload.applicationId);
    if (isDuplicate) {
      logger.warn("[InterviewBooking] Duplicate booking attempt", {
        applicationId: payload.applicationId,
        ip: clientIp,
      });

      return ApiResponse.conflict(
        "You already have an active interview booking. Please contact us to reschedule.",
      );
    }

    // Fetch application and job details
    const [application, job] = await Promise.all([
      prisma.job_applications.findUnique({
        where: { id: payload.applicationId },
        select: {
          applicantName: true,
          applicantEmail: true,
          applicantPhone: true,
          preferredLanguage: true,
        },
      }),
      prisma.jobs.findUnique({
        where: { id: payload.jobId },
        select: {
          title: true,
          titleAr: true,
        },
      }),
    ]);

    if (!application || !job) {
      logger.error("[InterviewBooking] Application or job not found", {
        applicationId: payload.applicationId,
        jobId: payload.jobId,
      });

      return ApiResponse.notFound("Application or job not found");
    }

    // Create booking
    const bookingResult = await createBooking({
      applicationId: payload.applicationId,
      jobId: payload.jobId,
      tokenId: tokenRecord.id,
      applicantName: application.applicantName,
      applicantEmail: application.applicantEmail,
      applicantPhone: application.applicantPhone || undefined,
      preferredDate: sanitized.preferredDate,
      preferredTime: sanitized.preferredTime,
      timezone: sanitized.timezone,
      durationMinutes: 60, // Default 1 hour
      applicantNotes: sanitized.additionalNotes || undefined,
    });

    if (!bookingResult.success) {
      logger.error("[InterviewBooking] Booking creation failed", {
        error: bookingResult.error,
        errorCode: bookingResult.errorCode,
        applicationId: payload.applicationId,
      });

      return ApiResponse.serverError(
        bookingResult.error || "Failed to create booking",
      );
    }

    const bookingId = bookingResult.bookingId!;

    // Format date for email
    const language = (application.preferredLanguage as "ar" | "en") || "en";
    const dateObj = new Date(sanitized.preferredDate);
    const formattedDate = dateObj.toLocaleDateString(
      language === "ar" ? "ar-SA" : "en-US",
      { weekday: "long", year: "numeric", month: "long", day: "numeric" },
    );

    // Send confirmation email (non-blocking)
    sendConfirmation({
      email: application.applicantEmail,
      details: {
        applicantName: application.applicantName,
        jobTitle: job.title,
        jobTitleAr: job.titleAr,
        confirmedDate: formattedDate,
        confirmedTime: sanitized.preferredTime,
        timezone: sanitized.timezone,
        language,
      },
    })
      .then((result) => {
        if (result.success) {
          logger.info("[InterviewBooking] Confirmation email sent", {
            bookingId,
            email: hashEmail(application.applicantEmail),
          });
        } else {
          logger.error("[InterviewBooking] Failed to send confirmation email", {
            bookingId,
            error: result.error,
          });
        }
      })
      .catch((error) => {
        logger.error("[InterviewBooking] Email send exception", {
          bookingId,
          error,
        });
      });

    // Create staff alert (non-blocking)
    const now = new Date();
    prisma.staff_alerts
      .create({
        data: {
          type: "NEW_INTERVIEW_BOOKING",
          title: "New Interview Booking",
          message: `${application.applicantName} scheduled interview for ${job.title}`,
          severity: "INFO",
          metadata: {
            bookingId,
            applicationId: payload.applicationId,
            jobId: payload.jobId,
            applicantName: application.applicantName,
            applicantEmail: application.applicantEmail,
            preferredDate: sanitized.preferredDate,
            preferredTime: sanitized.preferredTime,
            timezone: sanitized.timezone,
          },
          createdAt: now,
          updatedAt: now,
        },
      })
      .catch((error) => {
        logger.error("[InterviewBooking] Failed to create staff alert", {
          bookingId,
          error,
        });
      });

    // Log audit event (non-blocking)
    prisma.audit_logs
      .create({
        data: {
          type: "INTERVIEW_BOOKING",
          action: "CREATE",
          success: true,
          ip: clientIp,
          userAgent: request.headers.get("user-agent") || undefined,
          metadata: {
            bookingId,
            applicationId: payload.applicationId,
            jobId: payload.jobId,
            tokenId: tokenRecord.id,
            preferredDate: sanitized.preferredDate,
            preferredTime: sanitized.preferredTime,
            emailHash: hashEmail(application.applicantEmail),
          },
        },
      })
      .catch((error) => {
        logger.error("[InterviewBooking] Failed to create audit log", {
          bookingId,
          error,
        });
      });

    logger.info("[InterviewBooking] Booking created successfully", {
      bookingId,
      applicationId: payload.applicationId,
      jobId: payload.jobId,
      emailHash: hashEmail(application.applicantEmail),
    });

    // Return success response
    return ApiResponse.created({
      success: true,
      message: "Interview booking created successfully",
      bookingId,
      booking: {
        preferredDate: sanitized.preferredDate,
        preferredTime: sanitized.preferredTime,
        timezone: sanitized.timezone,
        status: "pending",
      },
    });
  } catch (error) {
    logger.error("[InterviewBooking] Booking error:", error);

    // Log failed audit event
    prisma.audit_logs
      .create({
        data: {
          type: "INTERVIEW_BOOKING",
          action: "CREATE",
          success: false,
          ip: clientIp,
          userAgent: request.headers.get("user-agent") || undefined,
          metadata: {
            error: error instanceof Error ? error.message : "Unknown error",
          },
        },
      })
      .catch(() => {
        // Ignore audit log errors in error path
      });

    return ApiResponse.serverError(
      "Failed to create booking. Please try again.",
    );
  }
}

// Apply CSRF protection and rate limiting
export const POST = withCSRF(withRateLimit(bookingHandler, bookingLimiter));
