/**
 * Viewing Reminders Cron Endpoint
 *
 * Scheduled job that runs hourly to send 24-hour and 2-hour reminders
 * for upcoming property viewings.
 *
 * Security:
 * - Requires CRON_SECRET authorization header (for Vercel Cron)
 * - Also accepts N8N_WEBHOOK_AUTH_TOKEN (for n8n triggering)
 *
 * Usage:
 * - Vercel Cron: Automatically called based on vercel.json cron config
 * - Manual: GET /api/cron/viewing-reminders with Authorization header
 * - n8n: Can be triggered via n8n workflow
 *
 * Reminder windows (designed for hourly execution):
 * - 24h reminder: viewingDate is 22-26 hours from now
 * - 2h reminder: viewingDate + viewingTime is 1.5-2.5 hours from now
 */

import { NextResponse } from "next/server";
import { prisma } from "@/lib/db";
import { sendViewingReminder } from "@/lib/services/real-estate/notification.service";
import { systemSettingsService } from "@/lib/services/system-settings.service";
import { getErrorMessage } from "@/lib/errors";
import { logger } from "@/lib/logger";
import { getDataSource } from "@/lib/data-source";

// ── Auth ────────────────────────────────────────────────────────────────────

async function isAuthorized(request: Request): Promise<boolean> {
  const cronSecret = process.env.CRON_SECRET;

  if (!cronSecret) {
    return false;
  }

  const authHeader = request.headers.get("authorization");

  if (!authHeader) {
    return false;
  }

  // Check for Vercel Cron secret
  if (cronSecret && authHeader === `Bearer ${cronSecret}`) {
    return true;
  }

  // Check for n8n webhook token (from database or env fallback)
  const providedToken = authHeader.replace("Bearer ", "");
  const isValidN8NToken =
    await systemSettingsService.validateN8NToken(providedToken);
  if (isValidN8NToken) {
    return true;
  }

  return false;
}

// ── Types ───────────────────────────────────────────────────────────────────

interface ReminderStats {
  sent: number;
  skipped: number;
}

interface CronResult {
  success: boolean;
  duration: string;
  reminders24h: ReminderStats;
  reminders2h: ReminderStats;
  errors?: string[];
}

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

/**
 * Combine a date-only viewingDate with a "HH:MM" viewingTime string
 * into a full UTC datetime.
 *
 * viewingDate is stored as a DateTime in Prisma but only has date precision.
 * viewingTime is a "HH:MM" string (24-hour format).
 */
function combineDateTime(viewingDate: Date, viewingTime: string): Date {
  const [hours, minutes] = viewingTime.split(":").map(Number);

  const combined = new Date(viewingDate);
  combined.setUTCHours(hours, minutes, 0, 0);

  return combined;
}

// ── Core logic ──────────────────────────────────────────────────────────────

async function processViewingReminders(): Promise<CronResult> {
  const startTime = Date.now();
  const now = new Date();
  const errors: string[] = [];

  const reminders24h: ReminderStats = { sent: 0, skipped: 0 };
  const reminders2h: ReminderStats = { sent: 0, skipped: 0 };

  // Convex data-source routing (WU-18)
  // When real_estate domain is on Convex, viewing queries are routed
  // to Convex. Notification sending stays as-is.
  if (getDataSource("real_estate") === "convex") {
    try {
      const { convexQuery } = await import("@/lib/convex-client");

      const nowMs = now.getTime();
      const window24hStart = nowMs + 22 * 60 * 60 * 1000;
      const window24hEnd = nowMs + 26 * 60 * 60 * 1000;

      const viewingReminders = await convexQuery(
        "real_estate/queries:getViewingReminders",
        {
          windowStart: window24hStart,
          windowEnd: window24hEnd,
        },
      );

      if (viewingReminders !== null) {
        const viewings = viewingReminders as Array<{
          _id: string;
          viewingDate: number;
          viewingTime: string;
          reminder24hSentAt?: number;
          reminder2hSentAt?: number;
        }>;

        // Process 24h reminders
        for (const v of viewings) {
          if (
            !v.reminder24hSentAt &&
            v.viewingDate >= window24hStart &&
            v.viewingDate <= window24hEnd
          ) {
            try {
              const result = await sendViewingReminder(v._id, "24h");
              if (result.sent) reminders24h.sent++;
              else reminders24h.skipped++;
            } catch (error) {
              reminders24h.skipped++;
              errors.push(
                `24h reminder failed for viewing ${v._id}: ${getErrorMessage(error)}`,
              );
            }
          }
        }

        // Process 2h reminders (broader query needed)
        const window2hStart = nowMs + 1.5 * 60 * 60 * 1000;
        const window2hEnd = nowMs + 2.5 * 60 * 60 * 1000;
        const viewings2h = await convexQuery(
          "real_estate/queries:getViewingReminders",
          {
            windowStart: window2hStart,
            windowEnd: window2hEnd,
          },
        );

        if (viewings2h && Array.isArray(viewings2h)) {
          for (const v of viewings2h as typeof viewings) {
            if (!v.reminder2hSentAt) {
              try {
                const result = await sendViewingReminder(v._id, "2h");
                if (result.sent) reminders2h.sent++;
                else reminders2h.skipped++;
              } catch (error) {
                reminders2h.skipped++;
                errors.push(
                  `2h reminder failed for viewing ${v._id}: ${getErrorMessage(error)}`,
                );
              }
            }
          }
        }

        const duration = Date.now() - startTime;
        const result: CronResult = {
          success: errors.length === 0,
          duration: `${duration}ms`,
          reminders24h,
          reminders2h,
        };
        if (errors.length > 0) result.errors = errors;
        return result;
      }
    } catch {
      // Fall through to Prisma
    }
  }

  // ── 24h reminders ───────────────────────────────────────────────────────
  // Find viewings where viewingDate is 22-26 hours from now
  // and reminder24hSentAt has not been set yet.

  const window24hStart = new Date(now.getTime() + 22 * 60 * 60 * 1000);
  const window24hEnd = new Date(now.getTime() + 26 * 60 * 60 * 1000);

  const viewingsFor24h = await prisma.propertyViewing.findMany({
    where: {
      status: { in: ["CONFIRMED", "REQUESTED"] },
      viewingDate: {
        gte: window24hStart,
        lte: window24hEnd,
      },
      reminder24hSentAt: null,
    },
    select: {
      id: true,
      clientName: true,
      viewingDate: true,
      viewingTime: true,
    },
  });

  logger.info(
    `Found ${viewingsFor24h.length} viewings eligible for 24h reminder`,
    {
      window: {
        start: window24hStart.toISOString(),
        end: window24hEnd.toISOString(),
      },
    },
  );

  for (const viewing of viewingsFor24h) {
    try {
      const result = await sendViewingReminder(viewing.id, "24h");
      if (result.sent) {
        reminders24h.sent++;
      } else {
        reminders24h.skipped++;
        logger.debug(
          `24h reminder skipped for ${viewing.id}: ${result.reason}`,
        );
      }
    } catch (error) {
      reminders24h.skipped++;
      const msg = `24h reminder failed for viewing ${viewing.id}: ${getErrorMessage(error)}`;
      logger.error(msg, error);
      errors.push(msg);
    }
  }

  // ── 2h reminders ────────────────────────────────────────────────────────
  // We need to combine viewingDate + viewingTime and check if the full
  // datetime is 1.5-2.5 hours from now.
  //
  // Since viewingTime is a string, we cannot do this purely in SQL.
  // Strategy: fetch all CONFIRMED/REQUESTED viewings whose viewingDate
  // is today or tomorrow (narrow window) and reminder2hSentAt is null,
  // then filter in-memory by the combined datetime.

  const startOfToday = new Date(now);
  startOfToday.setUTCHours(0, 0, 0, 0);

  const endOfTomorrow = new Date(startOfToday);
  endOfTomorrow.setUTCDate(endOfTomorrow.getUTCDate() + 2);

  const candidatesFor2h = await prisma.propertyViewing.findMany({
    where: {
      status: { in: ["CONFIRMED", "REQUESTED"] },
      viewingDate: {
        gte: startOfToday,
        lt: endOfTomorrow,
      },
      reminder2hSentAt: null,
    },
    select: {
      id: true,
      clientName: true,
      viewingDate: true,
      viewingTime: true,
    },
  });

  const window2hStart = new Date(now.getTime() + 1.5 * 60 * 60 * 1000);
  const window2hEnd = new Date(now.getTime() + 2.5 * 60 * 60 * 1000);

  const viewingsFor2h = candidatesFor2h.filter((v) => {
    const fullDatetime = combineDateTime(v.viewingDate, v.viewingTime);
    return fullDatetime >= window2hStart && fullDatetime <= window2hEnd;
  });

  logger.info(
    `Found ${viewingsFor2h.length} viewings eligible for 2h reminder (from ${candidatesFor2h.length} candidates)`,
    {
      window: {
        start: window2hStart.toISOString(),
        end: window2hEnd.toISOString(),
      },
    },
  );

  for (const viewing of viewingsFor2h) {
    try {
      const result = await sendViewingReminder(viewing.id, "2h");
      if (result.sent) {
        reminders2h.sent++;
      } else {
        reminders2h.skipped++;
        logger.debug(`2h reminder skipped for ${viewing.id}: ${result.reason}`);
      }
    } catch (error) {
      reminders2h.skipped++;
      const msg = `2h reminder failed for viewing ${viewing.id}: ${getErrorMessage(error)}`;
      logger.error(msg, error);
      errors.push(msg);
    }
  }

  // ── Result ──────────────────────────────────────────────────────────────

  const duration = Date.now() - startTime;

  const result: CronResult = {
    success: errors.length === 0,
    duration: `${duration}ms`,
    reminders24h,
    reminders2h,
  };

  if (errors.length > 0) {
    result.errors = errors;
  }

  return result;
}

// ── Route handlers ──────────────────────────────────────────────────────────

export async function GET(request: Request) {
  if (!(await isAuthorized(request))) {
    logger.warn("Unauthorized viewing-reminders cron request attempt");
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  logger.info("Starting viewing-reminders cron job");
  const startTime = Date.now();

  try {
    const result = await processViewingReminders();

    logger.info("Viewing-reminders cron completed", {
      duration: result.duration,
      reminders24h: result.reminders24h,
      reminders2h: result.reminders2h,
    });

    return NextResponse.json(result);
  } catch (error) {
    const errorMessage = getErrorMessage(error);
    logger.error("Viewing-reminders cron failed", error);

    return NextResponse.json(
      {
        success: false,
        error: errorMessage,
        duration: `${Date.now() - startTime}ms`,
      },
      { status: 500 },
    );
  }
}

// Also support POST for n8n webhook compatibility
export async function POST(request: Request) {
  return GET(request);
}

export const dynamic = "force-dynamic";
