/**
 * Real Estate Viewing Reminders API
 *
 * GET  - Cron-triggered: find upcoming viewings and send 24h / 2h reminders
 * POST - Staff-triggered: manually send a reminder for a specific viewing
 *
 * The GET handler is designed to be called by an external cron job every ~15 minutes.
 * It scans for confirmed/requested viewings whose reminder hasn't been sent yet and
 * dispatches notifications via the real-estate notification service.
 */

import { NextRequest } from "next/server";
import { prisma } from "@/lib/db";
import { logger } from "@/lib/logger";
import { ApiResponses } from "@/lib/utils/api-response.util";
import { getErrorMessage } from "@/lib/errors";
import { sendViewingReminder } from "@/lib/services/real-estate/notification.service";
import { auth } from "@/lib/auth";
import { getAuthenticatedEmail } from "@/lib/utils/auth-helpers.util";
import { getDataSource } from "@/lib/data-source";

export const dynamic = "force-dynamic";

// ── Helpers ──────────────────────────────────────────────────────────────────

/**
 * Combine a viewingDate (Date, date-only) and viewingTime (HH:MM string)
 * into a single Date representing the actual viewing moment.
 */
function combineDateTime(viewingDate: Date, viewingTime: string): Date {
  const [hours, minutes] = viewingTime.split(":").map(Number);
  const combined = new Date(viewingDate);
  combined.setHours(hours, minutes, 0, 0);
  return combined;
}

// ── GET: Cron-triggered reminder dispatch ────────────────────────────────────

/**
 * GET /api/real-estate/viewings/reminders
 *
 * Called by an external cron job (e.g. every 15 minutes).
 * Requires `Authorization: Bearer <CRON_SECRET>` header.
 *
 * 1. Finds viewings with status CONFIRMED or REQUESTED that are upcoming.
 * 2. For 24h reminders: viewing is 20-26 hours away AND reminder24hSentAt is null.
 * 3. For 2h reminders:  viewing is 1-4 hours away  AND reminder2hSentAt is null.
 * 4. Sends reminders via the notification service.
 * 5. Returns a summary of how many reminders were sent.
 */
export async function GET(request: NextRequest): Promise<Response> {
  try {
    // ── Auth: verify cron secret ──
    const authHeader = request.headers.get("authorization");
    const cronSecret = process.env.CRON_SECRET;

    if (!cronSecret) {
      logger.error("CRON_SECRET environment variable is not configured");
      return ApiResponses.serverError("Server misconfiguration");
    }

    if (!authHeader || authHeader !== `Bearer ${cronSecret}`) {
      logger.warn("Unauthorized cron reminder request", {
        hasHeader: !!authHeader,
      });
      return ApiResponses.unauthorized("Invalid cron secret");
    }

    const now = new Date();

    // Convex branch
    if (getDataSource("real_estate") === "convex") {
      try {
        const { convexQuery } = await import("@/lib/convex-client");
        const windowStart = now.getTime() + 1 * 60 * 60 * 1000;
        const windowEnd = now.getTime() + 26 * 60 * 60 * 1000;
        const reminders = await convexQuery(
          "real_estate/queries:getViewingReminders",
          { windowStart, windowEnd },
        );
        // Even with Convex data, we still use sendViewingReminder
        // which interacts with the notification service (stays as-is).
        // Fall through to Prisma path for actual reminder dispatching.
        logger.info("[Convex] Reminder query returned", {
          count: reminders ? (reminders as unknown[]).length : 0,
        });
      } catch (error) {
        console.error("[Convex fallback] real_estate reminders GET:", error);
      }
    }

    // ── Time windows ──
    // 24h reminders: viewings 20-26 hours from now
    const reminder24hWindowStart = new Date(
      now.getTime() + 20 * 60 * 60 * 1000,
    );
    const reminder24hWindowEnd = new Date(now.getTime() + 26 * 60 * 60 * 1000);

    // 2h reminders: viewings 1-4 hours from now
    const reminder2hWindowStart = new Date(now.getTime() + 1 * 60 * 60 * 1000);
    const reminder2hWindowEnd = new Date(now.getTime() + 4 * 60 * 60 * 1000);

    // ── Determine the broadest date range we need to query ──
    // We query viewingDate (date-only) broadly, then filter precisely using
    // the combined viewingDate + viewingTime in application code.
    const earliestDate = new Date(reminder2hWindowStart);
    earliestDate.setHours(0, 0, 0, 0);

    const latestDate = new Date(reminder24hWindowEnd);
    latestDate.setHours(23, 59, 59, 999);

    // ── Fetch candidate viewings ──
    // We fetch all viewings in the date range that still need at least one reminder.
    const candidateViewings = await prisma.propertyViewing.findMany({
      where: {
        status: { in: ["CONFIRMED", "REQUESTED"] },
        viewingDate: {
          gte: earliestDate,
          lte: latestDate,
        },
        OR: [{ reminder24hSentAt: null }, { reminder2hSentAt: null }],
      },
      select: {
        id: true,
        viewingDate: true,
        viewingTime: true,
        reminder24hSentAt: true,
        reminder2hSentAt: true,
        clientName: true,
      },
    });

    logger.info("Cron reminder scan started", {
      candidateCount: candidateViewings.length,
      now: now.toISOString(),
    });

    // ── Classify viewings into reminder buckets ──
    const reminders24h: string[] = [];
    const reminders2h: string[] = [];

    for (const viewing of candidateViewings) {
      const viewingDateTime = combineDateTime(
        viewing.viewingDate,
        viewing.viewingTime,
      );

      // 24h reminder: viewing is 20-26 hours away and not yet sent
      if (
        !viewing.reminder24hSentAt &&
        viewingDateTime >= reminder24hWindowStart &&
        viewingDateTime <= reminder24hWindowEnd
      ) {
        reminders24h.push(viewing.id);
      }

      // 2h reminder: viewing is 1-4 hours away and not yet sent
      if (
        !viewing.reminder2hSentAt &&
        viewingDateTime >= reminder2hWindowStart &&
        viewingDateTime <= reminder2hWindowEnd
      ) {
        reminders2h.push(viewing.id);
      }
    }

    // ── Dispatch reminders concurrently ──
    const results = {
      scanned: candidateViewings.length,
      reminders24h: {
        attempted: reminders24h.length,
        sent: 0,
        failed: 0,
        errors: [] as string[],
      },
      reminders2h: {
        attempted: reminders2h.length,
        sent: 0,
        failed: 0,
        errors: [] as string[],
      },
    };

    // Send 24h reminders
    const promises24h = reminders24h.map(async (viewingId) => {
      try {
        const result = await sendViewingReminder(viewingId, "24h");
        if (result.sent) {
          results.reminders24h.sent++;
        } else {
          results.reminders24h.failed++;
          if (result.reason) {
            results.reminders24h.errors.push(`${viewingId}: ${result.reason}`);
          }
        }
      } catch (err) {
        results.reminders24h.failed++;
        results.reminders24h.errors.push(
          `${viewingId}: ${getErrorMessage(err)}`,
        );
        logger.error("Failed to send 24h reminder", err, { viewingId });
      }
    });

    // Send 2h reminders
    const promises2h = reminders2h.map(async (viewingId) => {
      try {
        const result = await sendViewingReminder(viewingId, "2h");
        if (result.sent) {
          results.reminders2h.sent++;
        } else {
          results.reminders2h.failed++;
          if (result.reason) {
            results.reminders2h.errors.push(`${viewingId}: ${result.reason}`);
          }
        }
      } catch (err) {
        results.reminders2h.failed++;
        results.reminders2h.errors.push(
          `${viewingId}: ${getErrorMessage(err)}`,
        );
        logger.error("Failed to send 2h reminder", err, { viewingId });
      }
    });

    await Promise.all([...promises24h, ...promises2h]);

    const totalSent = results.reminders24h.sent + results.reminders2h.sent;
    const totalFailed =
      results.reminders24h.failed + results.reminders2h.failed;

    logger.info("Cron reminder scan completed", {
      scanned: results.scanned,
      totalSent,
      totalFailed,
      sent24h: results.reminders24h.sent,
      sent2h: results.reminders2h.sent,
    });

    return ApiResponses.success({
      success: true,
      summary: {
        scanned: results.scanned,
        totalSent,
        totalFailed,
      },
      reminders24h: {
        attempted: results.reminders24h.attempted,
        sent: results.reminders24h.sent,
        failed: results.reminders24h.failed,
        ...(results.reminders24h.errors.length > 0 && {
          errors: results.reminders24h.errors,
        }),
      },
      reminders2h: {
        attempted: results.reminders2h.attempted,
        sent: results.reminders2h.sent,
        failed: results.reminders2h.failed,
        ...(results.reminders2h.errors.length > 0 && {
          errors: results.reminders2h.errors,
        }),
      },
    });
  } catch (error) {
    logger.error("Cron reminder dispatch failed", error, {
      error: getErrorMessage(error),
    });
    return ApiResponses.serverError("Failed to process viewing reminders");
  }
}

// ── POST: Manual reminder trigger (staff use) ────────────────────────────────

/**
 * POST /api/real-estate/viewings/reminders
 *
 * Manually trigger a reminder for a specific viewing.
 * Requires authentication (NextAuth session or auth_token cookie).
 *
 * Body: { viewingId: string, type: '24h' | '2h' }
 */
export async function POST(request: NextRequest): Promise<Response> {
  try {
    // ── Auth: verify staff/user session ──
    const userEmail = await getAuthenticatedEmail(request, auth);

    if (!userEmail) {
      logger.warn("Unauthorized manual reminder request");
      return ApiResponses.unauthorized();
    }

    // ── Parse and validate body ──
    let body: { viewingId?: string; type?: string };
    try {
      body = await request.json();
    } catch {
      return ApiResponses.badRequest("Invalid JSON body");
    }

    const { viewingId, type } = body;

    if (!viewingId || typeof viewingId !== "string") {
      return ApiResponses.badRequest(
        "viewingId is required and must be a string",
      );
    }

    if (type !== "24h" && type !== "2h") {
      return ApiResponses.badRequest("type must be '24h' or '2h'");
    }

    // ── Verify the viewing exists ──
    const viewing = await prisma.propertyViewing.findUnique({
      where: { id: viewingId },
      select: {
        id: true,
        status: true,
        viewingDate: true,
        viewingTime: true,
        clientName: true,
      },
    });

    if (!viewing) {
      return ApiResponses.notFound("Viewing not found");
    }

    // ── Send the reminder ──
    logger.info("Manual reminder triggered", {
      viewingId,
      type,
      triggeredBy: userEmail,
      clientName: viewing.clientName,
    });

    const result = await sendViewingReminder(viewingId, type);

    if (!result.sent) {
      return ApiResponses.success({
        success: false,
        viewingId,
        type,
        reason: result.reason || "Reminder was not sent",
      });
    }

    return ApiResponses.success({
      success: true,
      viewingId,
      type,
      message: `${type} reminder sent successfully`,
    });
  } catch (error) {
    logger.error("Manual reminder dispatch failed", error, {
      error: getErrorMessage(error),
    });
    return ApiResponses.serverError("Failed to send viewing reminder");
  }
}
