/**
 * Viewing Follow-Ups Cron Endpoint
 *
 * Scheduled job that runs every 30 minutes to:
 * 1. Send post-viewing follow-up emails (4 hours after viewing completion)
 * 2. Auto-detect no-shows (2 hours after scheduled time with no status update)
 *
 * 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-follow-ups with Authorization header
 * - n8n: Can be triggered via n8n workflow
 */

import { NextResponse } from "next/server";
import { prisma } from "@/lib/db";
import { sendPostViewingFollowUp } 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";

export const dynamic = "force-dynamic";

// Verify authorization
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;
}

/**
 * Combine a date-only DateTime and an "HH:MM" time string into a full Date.
 */
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;
}

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

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

  const followUpResults = { processed: 0, sent: 0, skipped: 0 };
  const noShowResults = { detected: 0 };

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

        const now = Date.now();
        const fourHoursAgo = now - 4 * 60 * 60 * 1000;
        const fortyEightHoursAgo = now - 48 * 60 * 60 * 1000;

        // Get viewings needing follow-ups from Convex
        const allViewings = await convexQuery(
          "real_estate/queries:getViewingReminders",
          {
            windowStart: fortyEightHoursAgo,
            windowEnd: now,
          },
        );

        const convexFollowUpResults = { processed: 0, sent: 0, skipped: 0 };
        const convexNoShowResults = { detected: 0 };

        if (allViewings && Array.isArray(allViewings)) {
          for (const v of allViewings) {
            const viewing = v as {
              _id: string;
              status: string;
              viewingDate: number;
              viewingTime: string;
            };
            if (viewing.status === "VIEW_COMPLETED") {
              convexFollowUpResults.processed++;
              try {
                await sendPostViewingFollowUp(viewing._id);
                convexFollowUpResults.sent++;
              } catch {
                convexFollowUpResults.skipped++;
              }
            }
          }
        }

        const duration = Date.now() - startTime;
        return NextResponse.json({
          success: true,
          duration: `${duration}ms`,
          followUps: convexFollowUpResults,
          noShows: convexNoShowResults,
        });
      } catch {
        // Fall through to Prisma
      }
    }

    // ─── Part 1: Post-viewing follow-ups (4 hours after completion) ───

    const now = new Date();
    const fourHoursAgo = new Date(now.getTime() - 4 * 60 * 60 * 1000);
    const fortyEightHoursAgo = new Date(now.getTime() - 48 * 60 * 60 * 1000);

    const completedViewings = await prisma.propertyViewing.findMany({
      where: {
        status: "VIEW_COMPLETED",
        followUpSentAt: null,
        updatedAt: {
          lt: fourHoursAgo,
          gt: fortyEightHoursAgo,
        },
      },
      select: {
        id: true,
      },
    });

    logger.info(`Found ${completedViewings.length} viewings needing follow-up`);

    for (const viewing of completedViewings) {
      followUpResults.processed++;
      try {
        await sendPostViewingFollowUp(viewing.id);
        followUpResults.sent++;
        logger.info(`Sent post-viewing follow-up for viewing ${viewing.id}`);
      } catch (error) {
        followUpResults.skipped++;
        logger.error(
          `Failed to send follow-up for viewing ${viewing.id}`,
          error,
        );
      }
    }

    // ─── Part 2: No-show detection (2 hours after scheduled time) ───

    const twentyFourHoursAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);

    const confirmedViewings = await prisma.propertyViewing.findMany({
      where: {
        status: "CONFIRMED",
        viewingDate: {
          gte: twentyFourHoursAgo,
          lte: now,
        },
      },
      select: {
        id: true,
        viewingDate: true,
        viewingTime: true,
      },
    });

    logger.info(
      `Found ${confirmedViewings.length} confirmed viewings to check for no-shows`,
    );

    const twoHoursAgo = new Date(now.getTime() - 2 * 60 * 60 * 1000);

    for (const viewing of confirmedViewings) {
      const scheduledDateTime = combineDateTime(
        viewing.viewingDate,
        viewing.viewingTime,
      );

      if (scheduledDateTime <= twoHoursAgo) {
        await prisma.propertyViewing.update({
          where: { id: viewing.id },
          data: { status: "NO_SHOW" },
        });
        noShowResults.detected++;
        logger.info(
          `Auto-detected no-show for viewing ${viewing.id} (scheduled: ${scheduledDateTime.toISOString()})`,
        );
      }
    }

    const duration = Date.now() - startTime;

    logger.info("Viewing follow-ups cron completed", {
      duration,
      followUps: followUpResults,
      noShows: noShowResults,
    });

    return NextResponse.json({
      success: true,
      duration: `${duration}ms`,
      followUps: followUpResults,
      noShows: noShowResults,
    });
  } catch (error) {
    const errorMessage = getErrorMessage(error);
    logger.error("Viewing follow-ups 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);
}
