"use server";

import { supabase } from "@/lib/supabase";
import { createClient } from "@/lib/supabase-server";
import type {
  NotificationCategory,
  Campaign,
  CampaignVariant,
  CampaignStatus,
  CampaignWithStats,
  CampaignDeliveryStats,
  CreateCampaignInput,
  SegmentRules,
} from "@/types/campaigns";
import { z } from "zod";
import { headers } from "next/headers";
import { checkRateLimitAsync } from "@/lib/rateLimit";

/**
 * Verify the current user is an active admin
 */
async function verifyAdminAccess(): Promise<{
  isAdmin: boolean;
  adminId?: string;
  error?: string;
}> {
  const supabaseServer = await createClient();

  // Get current user
  const {
    data: { user },
    error: authError,
  } = await supabaseServer.auth.getUser();

  if (authError || !user) {
    return { isAdmin: false, error: "Not authenticated" };
  }

  // Check if user is an active admin
  const { data: adminUser, error: adminError } = await supabaseServer
    .from("admin_users")
    .select("id, is_active")
    .eq("auth_id", user.id)
    .single();

  if (adminError || !adminUser) {
    return { isAdmin: false, error: "Not an admin user" };
  }

  if (!adminUser.is_active) {
    return { isAdmin: false, error: "Admin account is inactive" };
  }

  return { isAdmin: true, adminId: adminUser.id };
}

// Security event logging
function logSecurityEvent(
  event: string,
  details: Record<string, unknown>
): void {
  const logEntry = {
    timestamp: new Date().toISOString(),
    event,
    ...details,
  };

  // In production, this should go to a proper logging service
  if (process.env.NODE_ENV === "production") {
    console.log("[SECURITY]", JSON.stringify(logEntry));
  } else {
    console.log("[SECURITY]", event, details);
  }
}

// CSRF validation helper with strict localhost matching
async function validateOrigin(): Promise<boolean> {
  const headersList = await headers();
  const origin = headersList.get("origin");
  const host = headersList.get("host");
  const referer = headersList.get("referer");

  // For same-origin requests without Origin header, check Referer
  if (!origin && referer) {
    try {
      const refererUrl = new URL(referer);
      if (refererUrl.host === host) return true;
    } catch {
      return false;
    }
  }

  if (!origin || !host) {
    return false;
  }

  try {
    const originUrl = new URL(origin);
    const originHost = originUrl.host;

    // Exact match
    if (originHost === host) {
      return true;
    }

    // Strict localhost matching for development only
    if (process.env.NODE_ENV === "development") {
      const localhostPatterns = [
        /^localhost(:\d+)?$/,
        /^127\.0\.0\.1(:\d+)?$/,
        /^\[::1\](:\d+)?$/,
      ];
      const isOriginLocalhost = localhostPatterns.some((p) => p.test(originHost));
      const isHostLocalhost = localhostPatterns.some((p) => p.test(host));
      return isOriginLocalhost && isHostLocalhost;
    }

    return false;
  } catch {
    return false;
  }
}

// Get client identifier for rate limiting with better fallback handling
async function getClientIdentifier(): Promise<string> {
  const headersList = await headers();

  // Check for Vercel deployment (trusted proxy)
  const vercelId = headersList.get("x-vercel-id");
  if (vercelId) {
    const forwardedFor = headersList.get("x-forwarded-for");
    if (forwardedFor) {
      return forwardedFor.split(",")[0].trim();
    }
  }

  // Try multiple headers for IP identification
  const realIp = headersList.get("x-real-ip");
  const cfConnectingIp = headersList.get("cf-connecting-ip");
  const forwardedFor = headersList.get("x-forwarded-for");

  const identifier = realIp || cfConnectingIp || forwardedFor?.split(",")[0].trim();

  if (!identifier) {
    // Log when we can't identify the client
    logSecurityEvent("unidentified_client", {
      headers: {
        hasVercelId: !!vercelId,
        hasRealIp: !!realIp,
        hasCfIp: !!cfConnectingIp,
        hasForwardedFor: !!forwardedFor,
      },
    });
    return "unidentified";
  }

  return identifier;
}

// Zod validation schemas
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

const segmentRuleSchema = z.object({
  field: z.string().max(50),
  op: z.enum(["eq", "neq", "gt", "gte", "lt", "lte", "in", "is"]),
  value: z.union([z.string().max(200), z.array(z.string().max(200)).max(20), z.number()]),
});

const segmentRulesSchema = z.object({
  operator: z.enum(["AND", "OR"]).optional(),
  rules: z.array(segmentRuleSchema).max(10).optional(),
});

const createCampaignSchema = z.object({
  name: z.string().trim().min(1, "Name required").max(200, "Name too long"),
  category_id: z.string().regex(UUID_REGEX, "Invalid category ID"),
  segment_rules: segmentRulesSchema.optional().default({}),
  variant_title: z.string().trim().min(1, "Title required").max(65, "Title max 65 chars"),
  variant_body: z.string().trim().min(1, "Body required").max(240, "Body max 240 chars"),
  variant_deep_link: z.string().max(500).refine(
    (v) => !v || v.startsWith("/") || v.startsWith("https://"),
    "Deep link must start with / or https://"
  ).optional(),
});

const campaignIdSchema = z.string().regex(UUID_REGEX, "Invalid campaign ID");

const VALID_CAMPAIGN_STATUSES: CampaignStatus[] = ["draft", "scheduled", "processing", "sent", "active", "paused", "completed", "cancelled"];

/**
 * Fetch all non-system notification categories
 */
export async function fetchCategories(): Promise<NotificationCategory[]> {
  const { isAdmin, error: authError } = await verifyAdminAccess();
  if (!isAdmin) {
    console.error("Unauthorized access to fetchCategories:", authError);
    throw new Error("Unauthorized: Admin access required");
  }

  const clientId = await getClientIdentifier();
  const rl = await checkRateLimitAsync(`campaigns:read:${clientId}`, "read");
  if (!rl.success) throw new Error(`Rate limit exceeded. Wait ${Math.ceil(rl.resetIn / 1000)}s.`);

  const { data, error } = await supabase
    .from("notification_categories")
    .select("id, slug, name, description, is_system")
    .eq("is_system", false)
    .order("name", { ascending: true });

  if (error) {
    console.error("Error fetching categories:", error);
    throw new Error("Failed to fetch categories");
  }

  return data as NotificationCategory[];
}

/**
 * Fetch paginated campaigns with category info
 */
export async function fetchCampaigns(
  page: number = 1,
  perPage: number = 10,
  statusFilter?: CampaignStatus | ""
): Promise<{ campaigns: Campaign[]; totalCount: number; totalPages: number }> {
  const { isAdmin, error: authError } = await verifyAdminAccess();
  if (!isAdmin) {
    console.error("Unauthorized access to fetchCampaigns:", authError);
    throw new Error("Unauthorized: Admin access required");
  }

  const clientId = await getClientIdentifier();
  const rl = await checkRateLimitAsync(`campaigns:read:${clientId}`, "read");
  if (!rl.success) throw new Error(`Rate limit exceeded. Wait ${Math.ceil(rl.resetIn / 1000)}s.`);

  if (statusFilter && !VALID_CAMPAIGN_STATUSES.includes(statusFilter as CampaignStatus)) {
    throw new Error("Invalid status filter");
  }

  const validatedPage = Math.max(1, Math.floor(Number(page) || 1));
  const validatedPerPage = Math.min(100, Math.max(1, Math.floor(Number(perPage) || 10)));

  let query = supabase
    .from("campaigns")
    .select(
      `
      id,
      name,
      category_id,
      segment_rules,
      status,
      send_mode,
      scheduled_at,
      audience_count,
      processed_count,
      total_count,
      created_by,
      created_at,
      updated_at,
      notification_categories(slug, name)
    `,
      { count: "exact" }
    );

  if (statusFilter) {
    query = query.eq("status", statusFilter);
  }

  query = query.order("created_at", { ascending: false });

  const from = (validatedPage - 1) * validatedPerPage;
  const to = from + validatedPerPage - 1;
  query = query.range(from, to);

  const { data, error, count } = await query;

  if (error) {
    console.error("Error fetching campaigns:", error);
    return { campaigns: [], totalCount: 0, totalPages: 0 };
  }

  const campaigns: Campaign[] = (data || []).map((c) => {
    const category = c.notification_categories as unknown as {
      slug: string;
      name: string;
    } | null;

    return {
      id: c.id,
      name: c.name,
      category_id: c.category_id,
      category_slug: category?.slug,
      category_name: category?.name,
      segment_rules: c.segment_rules as unknown as Campaign["segment_rules"],
      status: c.status as CampaignStatus,
      send_mode: c.send_mode as Campaign["send_mode"],
      scheduled_at: c.scheduled_at,
      audience_count: c.audience_count,
      processed_count: c.processed_count,
      total_count: c.total_count,
      created_by: c.created_by,
      created_at: c.created_at,
      updated_at: c.updated_at,
    };
  });

  const totalCount = count || 0;
  const totalPages = Math.ceil(totalCount / validatedPerPage);

  return { campaigns, totalCount, totalPages };
}

/**
 * Fetch delivery stats for a campaign from campaign_deliveries
 */
async function fetchDeliveryStats(
  campaignId: string
): Promise<CampaignDeliveryStats> {
  const [total, queued, sent, delivered, failed, tapped, converted] =
    await Promise.all([
      supabase
        .from("campaign_deliveries")
        .select("*", { count: "exact", head: true })
        .eq("campaign_id", campaignId),
      supabase
        .from("campaign_deliveries")
        .select("*", { count: "exact", head: true })
        .eq("campaign_id", campaignId)
        .eq("status", "queued"),
      supabase
        .from("campaign_deliveries")
        .select("*", { count: "exact", head: true })
        .eq("campaign_id", campaignId)
        .eq("status", "sent"),
      supabase
        .from("campaign_deliveries")
        .select("*", { count: "exact", head: true })
        .eq("campaign_id", campaignId)
        .eq("status", "delivered"),
      supabase
        .from("campaign_deliveries")
        .select("*", { count: "exact", head: true })
        .eq("campaign_id", campaignId)
        .eq("status", "failed"),
      supabase
        .from("campaign_deliveries")
        .select("*", { count: "exact", head: true })
        .eq("campaign_id", campaignId)
        .not("tapped_at", "is", null),
      supabase
        .from("campaign_deliveries")
        .select("*", { count: "exact", head: true })
        .eq("campaign_id", campaignId)
        .not("converted_at", "is", null),
    ]);

  return {
    total: total.count || 0,
    queued: queued.count || 0,
    sent: sent.count || 0,
    delivered: delivered.count || 0,
    failed: failed.count || 0,
    tapped: tapped.count || 0,
    converted: converted.count || 0,
  };
}

/**
 * Fetch a single campaign with its variant and delivery stats
 */
export async function fetchCampaignDetail(
  campaignId: string
): Promise<CampaignWithStats> {
  const { isAdmin, error: authError } = await verifyAdminAccess();
  if (!isAdmin) {
    console.error("Unauthorized access to fetchCampaignDetail:", authError);
    throw new Error("Unauthorized: Admin access required");
  }

  const idValidation = campaignIdSchema.safeParse(campaignId);
  if (!idValidation.success) throw new Error("Invalid campaign ID format");

  const clientId = await getClientIdentifier();
  const rl = await checkRateLimitAsync(`campaigns:read:${clientId}`, "read");
  if (!rl.success) throw new Error(`Rate limit exceeded. Wait ${Math.ceil(rl.resetIn / 1000)}s.`);

  // Fetch campaign with category
  const { data: campaignData, error: campaignError } = await supabase
    .from("campaigns")
    .select(
      `
      id,
      name,
      category_id,
      segment_rules,
      status,
      send_mode,
      scheduled_at,
      audience_count,
      processed_count,
      total_count,
      created_by,
      created_at,
      updated_at,
      notification_categories(slug, name)
    `
    )
    .eq("id", campaignId)
    .single();

  if (campaignError || !campaignData) {
    console.error("Error fetching campaign detail:", campaignError);
    throw new Error("Campaign not found");
  }

  const category = campaignData.notification_categories as unknown as {
    slug: string;
    name: string;
  } | null;

  // Fetch the primary variant (first non-control variant)
  const { data: variantData } = await supabase
    .from("campaign_variants")
    .select("id, campaign_id, name, title, body, deep_link, image_url, traffic_pct, is_control")
    .eq("campaign_id", campaignId)
    .eq("is_control", false)
    .limit(1)
    .single();

  // Fetch delivery stats
  const stats = await fetchDeliveryStats(campaignId);

  const campaign: CampaignWithStats = {
    id: campaignData.id,
    name: campaignData.name,
    category_id: campaignData.category_id,
    category_slug: category?.slug,
    category_name: category?.name,
    segment_rules: campaignData.segment_rules as unknown as Campaign["segment_rules"],
    status: campaignData.status as CampaignStatus,
    send_mode: campaignData.send_mode as Campaign["send_mode"],
    scheduled_at: campaignData.scheduled_at,
    audience_count: campaignData.audience_count,
    processed_count: campaignData.processed_count,
    total_count: campaignData.total_count,
    created_by: campaignData.created_by,
    created_at: campaignData.created_at,
    updated_at: campaignData.updated_at,
    variant: variantData as CampaignVariant | null,
    stats,
  };

  return campaign;
}

/**
 * Create a new campaign with a default variant
 */
export async function createCampaign(
  input: CreateCampaignInput
): Promise<Campaign> {
  const { isAdmin, adminId, error: authError } = await verifyAdminAccess();
  if (!isAdmin || !adminId) {
    console.error("Unauthorized access to createCampaign:", authError);
    throw new Error("Unauthorized: Admin access required");
  }

  const isValidOrigin = await validateOrigin();
  if (!isValidOrigin) {
    logSecurityEvent("csrf_validation_failed", { action: "createCampaign" });
    throw new Error("Invalid request origin");
  }
  const clientId = await getClientIdentifier();
  const rl = await checkRateLimitAsync(`campaigns:write:${clientId}`, "write");
  if (!rl.success) throw new Error(`Rate limit exceeded. Wait ${Math.ceil(rl.resetIn / 1000)}s.`);

  const validation = createCampaignSchema.safeParse(input);
  if (!validation.success) {
    const firstError = validation.error.issues[0]?.message || "Invalid input";
    throw new Error(firstError);
  }
  const validated = validation.data;

  // Create the campaign
  const { data: campaignData, error: campaignError } = await supabase
    .from("campaigns")
    .insert({
      name: validated.name,
      category_id: validated.category_id,
      segment_rules: validated.segment_rules,
      status: "draft",
      send_mode: "immediate",
      created_by: adminId,
    })
    .select(
      `
      id,
      name,
      category_id,
      segment_rules,
      status,
      send_mode,
      scheduled_at,
      audience_count,
      processed_count,
      total_count,
      created_by,
      created_at,
      updated_at,
      notification_categories(slug, name)
    `
    )
    .single();

  if (campaignError || !campaignData) {
    console.error("Error creating campaign:", campaignError);
    throw new Error("Failed to create campaign");
  }

  // Create the default variant
  const { error: variantError } = await supabase
    .from("campaign_variants")
    .insert({
      campaign_id: campaignData.id,
      name: "Default",
      title: validated.variant_title,
      body: validated.variant_body,
      deep_link: validated.variant_deep_link || null,
      traffic_pct: 100,
      is_control: false,
    });

  if (variantError) {
    console.error("Error creating campaign variant:", variantError);
    // Clean up the campaign if variant creation fails
    await supabase.from("campaigns").delete().eq("id", campaignData.id);
    throw new Error("Failed to create campaign variant");
  }

  const category = campaignData.notification_categories as unknown as {
    slug: string;
    name: string;
  } | null;

  logSecurityEvent("campaign_created", { adminId, campaignId: campaignData.id });

  return {
    id: campaignData.id,
    name: campaignData.name,
    category_id: campaignData.category_id,
    category_slug: category?.slug,
    category_name: category?.name,
    segment_rules: campaignData.segment_rules as unknown as Campaign["segment_rules"],
    status: campaignData.status as CampaignStatus,
    send_mode: campaignData.send_mode as Campaign["send_mode"],
    scheduled_at: campaignData.scheduled_at,
    audience_count: campaignData.audience_count,
    processed_count: campaignData.processed_count,
    total_count: campaignData.total_count,
    created_by: campaignData.created_by,
    created_at: campaignData.created_at,
    updated_at: campaignData.updated_at,
  };
}

/**
 * Schedule a campaign for immediate sending
 */
export async function scheduleCampaign(
  campaignId: string,
  scheduledAt?: string
): Promise<{ success: boolean }> {
  const { isAdmin, adminId, error: authError } = await verifyAdminAccess();
  if (!isAdmin) {
    console.error("Unauthorized access to scheduleCampaign:", authError);
    throw new Error("Unauthorized: Admin access required");
  }

  const idValidation = campaignIdSchema.safeParse(campaignId);
  if (!idValidation.success) throw new Error("Invalid campaign ID format");

  const isValidOrigin = await validateOrigin();
  if (!isValidOrigin) {
    logSecurityEvent("csrf_validation_failed", { action: "scheduleCampaign" });
    throw new Error("Invalid request origin");
  }
  const clientId = await getClientIdentifier();
  const rl = await checkRateLimitAsync(`campaigns:write:${clientId}`, "write");
  if (!rl.success) throw new Error(`Rate limit exceeded. Wait ${Math.ceil(rl.resetIn / 1000)}s.`);

  // Validate scheduledAt if provided — must be in the future
  let sendTime = new Date().toISOString();
  if (scheduledAt) {
    const parsed = new Date(scheduledAt);
    if (isNaN(parsed.getTime())) throw new Error("Invalid schedule date");
    if (parsed.getTime() < Date.now() - 60000) throw new Error("Schedule date must be in the future");
    sendTime = parsed.toISOString();
  }

  // Atomic update — status guard in WHERE prevents TOCTOU race
  const { data, error: updateError } = await supabase
    .from("campaigns")
    .update({
      status: "scheduled",
      scheduled_at: sendTime,
      send_mode: scheduledAt ? "scheduled" : "immediate",
      updated_at: new Date().toISOString(),
    })
    .eq("id", campaignId)
    .eq("status", "draft")
    .select("id")
    .maybeSingle();

  if (updateError) {
    console.error("Error scheduling campaign:", updateError);
    throw new Error("Failed to schedule campaign");
  }

  if (!data) {
    throw new Error("Campaign not found or is not in draft state");
  }

  logSecurityEvent("campaign_scheduled", { adminId, campaignId });

  return { success: true };
}

/**
 * Cancel a draft or scheduled campaign
 */
export async function cancelCampaign(
  campaignId: string
): Promise<{ success: boolean }> {
  const { isAdmin, adminId, error: authError } = await verifyAdminAccess();
  if (!isAdmin) {
    console.error("Unauthorized access to cancelCampaign:", authError);
    throw new Error("Unauthorized: Admin access required");
  }

  const idValidation = campaignIdSchema.safeParse(campaignId);
  if (!idValidation.success) throw new Error("Invalid campaign ID format");

  const isValidOrigin = await validateOrigin();
  if (!isValidOrigin) {
    logSecurityEvent("csrf_validation_failed", { action: "cancelCampaign" });
    throw new Error("Invalid request origin");
  }
  const clientId = await getClientIdentifier();
  const rl = await checkRateLimitAsync(`campaigns:write:${clientId}`, "write");
  if (!rl.success) throw new Error(`Rate limit exceeded. Wait ${Math.ceil(rl.resetIn / 1000)}s.`);

  // Atomic update — status guard in WHERE prevents TOCTOU race
  const { data, error: updateError } = await supabase
    .from("campaigns")
    .update({
      status: "cancelled",
      updated_at: new Date().toISOString(),
    })
    .eq("id", campaignId)
    .in("status", ["draft", "scheduled"])
    .select("id")
    .maybeSingle();

  if (updateError) {
    console.error("Error cancelling campaign:", updateError);
    throw new Error("Failed to cancel campaign");
  }

  if (!data) {
    throw new Error("Campaign not found or cannot be cancelled in its current state");
  }

  logSecurityEvent("campaign_cancelled", { adminId, campaignId });

  return { success: true };
}

/**
 * Delete a campaign permanently (draft, sent, or cancelled)
 * Cannot delete campaigns that are currently processing or scheduled
 */
export async function deleteCampaign(
  campaignId: string
): Promise<{ success: boolean }> {
  const { isAdmin, adminId, error: authError } = await verifyAdminAccess();
  if (!isAdmin) {
    console.error("Unauthorized access to deleteCampaign:", authError);
    throw new Error("Unauthorized: Admin access required");
  }

  const idValidation = campaignIdSchema.safeParse(campaignId);
  if (!idValidation.success) throw new Error("Invalid campaign ID format");

  const isValidOrigin = await validateOrigin();
  if (!isValidOrigin) {
    logSecurityEvent("csrf_validation_failed", { action: "deleteCampaign" });
    throw new Error("Invalid request origin");
  }
  const clientId = await getClientIdentifier();
  const rl = await checkRateLimitAsync(`campaigns:write:${clientId}`, "write");
  if (!rl.success) throw new Error(`Rate limit exceeded. Wait ${Math.ceil(rl.resetIn / 1000)}s.`);

  // Delete deliveries first (no CASCADE on this FK)
  await supabase
    .from("campaign_deliveries")
    .delete()
    .eq("campaign_id", campaignId);

  // Delete the campaign — allow draft, sent, cancelled, completed
  const { data, error: deleteError } = await supabase
    .from("campaigns")
    .delete()
    .eq("id", campaignId)
    .in("status", ["draft", "sent", "cancelled", "completed"])
    .select("id")
    .maybeSingle();

  if (deleteError) {
    console.error("Error deleting campaign:", deleteError);
    throw new Error("Failed to delete campaign");
  }

  if (!data) {
    throw new Error("Campaign not found or cannot be deleted in its current state");
  }

  logSecurityEvent("campaign_deleted", { adminId, campaignId });

  return { success: true };
}

/**
 * Duplicate a campaign as a new draft
 */
export async function duplicateCampaign(
  campaignId: string
): Promise<Campaign> {
  const { isAdmin, adminId, error: authError } = await verifyAdminAccess();
  if (!isAdmin || !adminId) {
    console.error("Unauthorized access to duplicateCampaign:", authError);
    throw new Error("Unauthorized: Admin access required");
  }

  const idValidation = campaignIdSchema.safeParse(campaignId);
  if (!idValidation.success) throw new Error("Invalid campaign ID format");

  const isValidOrigin = await validateOrigin();
  if (!isValidOrigin) {
    logSecurityEvent("csrf_validation_failed", { action: "duplicateCampaign" });
    throw new Error("Invalid request origin");
  }
  const clientId = await getClientIdentifier();
  const rl = await checkRateLimitAsync(`campaigns:write:${clientId}`, "write");
  if (!rl.success) throw new Error(`Rate limit exceeded. Wait ${Math.ceil(rl.resetIn / 1000)}s.`);

  // Fetch the original campaign
  const { data: original, error: fetchError } = await supabase
    .from("campaigns")
    .select(
      `
      name,
      category_id,
      segment_rules,
      send_mode,
      notification_categories(slug, name)
    `
    )
    .eq("id", campaignId)
    .single();

  if (fetchError || !original) {
    throw new Error("Campaign not found");
  }

  // Fetch the original variant
  const { data: originalVariant } = await supabase
    .from("campaign_variants")
    .select("name, title, body, deep_link, image_url, traffic_pct, is_control")
    .eq("campaign_id", campaignId)
    .eq("is_control", false)
    .limit(1)
    .single();

  // Create the duplicate campaign
  const { data: newCampaign, error: createError } = await supabase
    .from("campaigns")
    .insert({
      name: `${original.name} (Copy)`,
      category_id: original.category_id,
      segment_rules: original.segment_rules,
      status: "draft",
      send_mode: original.send_mode,
      created_by: adminId,
    })
    .select(
      `
      id,
      name,
      category_id,
      segment_rules,
      status,
      send_mode,
      scheduled_at,
      audience_count,
      processed_count,
      total_count,
      created_by,
      created_at,
      updated_at,
      notification_categories(slug, name)
    `
    )
    .single();

  if (createError || !newCampaign) {
    console.error("Error duplicating campaign:", createError);
    throw new Error("Failed to duplicate campaign");
  }

  // Duplicate the variant if one existed
  if (originalVariant) {
    const { error: variantError } = await supabase
      .from("campaign_variants")
      .insert({
        campaign_id: newCampaign.id,
        name: originalVariant.name,
        title: originalVariant.title,
        body: originalVariant.body,
        deep_link: originalVariant.deep_link,
        image_url: originalVariant.image_url,
        traffic_pct: originalVariant.traffic_pct,
        is_control: originalVariant.is_control,
      });

    if (variantError) {
      console.error("Error duplicating variant:", variantError);
      // Clean up if variant duplication fails
      await supabase.from("campaigns").delete().eq("id", newCampaign.id);
      throw new Error("Failed to duplicate campaign variant");
    }
  }

  const category = newCampaign.notification_categories as unknown as {
    slug: string;
    name: string;
  } | null;

  logSecurityEvent("campaign_duplicated", { adminId, sourceCampaignId: campaignId });

  return {
    id: newCampaign.id,
    name: newCampaign.name,
    category_id: newCampaign.category_id,
    category_slug: category?.slug,
    category_name: category?.name,
    segment_rules: newCampaign.segment_rules as unknown as Campaign["segment_rules"],
    status: newCampaign.status as CampaignStatus,
    send_mode: newCampaign.send_mode as Campaign["send_mode"],
    scheduled_at: newCampaign.scheduled_at,
    audience_count: newCampaign.audience_count,
    processed_count: newCampaign.processed_count,
    total_count: newCampaign.total_count,
    created_by: newCampaign.created_by,
    created_at: newCampaign.created_at,
    updated_at: newCampaign.updated_at,
  };
}

/**
 * Fetch aggregate campaign metrics for the campaigns list page
 */
export async function fetchCampaignMetrics(): Promise<{
  totalCampaigns: number;
  sentCampaigns: number;
  totalDelivered: number;
  avgTapRate: number;
}> {
  const { isAdmin, error: authError } = await verifyAdminAccess();
  if (!isAdmin) {
    console.error("Unauthorized access to fetchCampaignMetrics:", authError);
    throw new Error("Unauthorized: Admin access required");
  }

  const clientId = await getClientIdentifier();
  const rl = await checkRateLimitAsync(`campaigns:read:${clientId}`, "read");
  if (!rl.success) throw new Error(`Rate limit exceeded. Wait ${Math.ceil(rl.resetIn / 1000)}s.`);

  // Total campaigns
  const { count: totalCampaigns } = await supabase
    .from("campaigns")
    .select("*", { count: "exact", head: true });

  // Sent campaigns (status = sent, active, paused, completed)
  const { count: sentCampaigns } = await supabase
    .from("campaigns")
    .select("*", { count: "exact", head: true })
    .in("status", ["sent", "active", "paused", "completed"]);

  // Total delivered from campaign_deliveries
  const { count: totalDelivered } = await supabase
    .from("campaign_deliveries")
    .select("*", { count: "exact", head: true })
    .eq("status", "delivered");

  // Total tapped (for avg tap rate)
  const { count: totalTapped } = await supabase
    .from("campaign_deliveries")
    .select("*", { count: "exact", head: true })
    .not("tapped_at", "is", null);

  const delivered = totalDelivered || 0;
  const tapped = totalTapped || 0;
  const avgTapRate = delivered > 0 ? Math.round((tapped / delivered) * 100 * 10) / 10 : 0;

  return {
    totalCampaigns: totalCampaigns || 0,
    sentCampaigns: sentCampaigns || 0,
    totalDelivered: delivered,
    avgTapRate,
  };
}

/**
 * Preview how many push subscriptions match the given segment rules
 */
export async function previewSegmentCount(
  segmentRules: SegmentRules
): Promise<{ count: number }> {
  const { isAdmin, error: authError } = await verifyAdminAccess();
  if (!isAdmin) {
    console.error("Unauthorized access to previewSegmentCount:", authError);
    throw new Error("Unauthorized: Admin access required");
  }

  const clientId = await getClientIdentifier();
  const rl = await checkRateLimitAsync(`campaigns:search:${clientId}`, "search");
  if (!rl.success) throw new Error(`Rate limit exceeded. Wait ${Math.ceil(rl.resetIn / 1000)}s.`);

  const rulesValidation = segmentRulesSchema.safeParse(segmentRules);
  if (!rulesValidation.success) throw new Error("Invalid segment rules");

  // Build query on push_subscriptions — fetch user_ids to deduplicate
  let query = supabase
    .from("push_subscriptions")
    .select("user_id");

  const ALLOWED_SEGMENT_FIELDS = new Set(["platform", "timezone", "device_locale"]);

  // Handle plan-based filtering (plan is on users table, not push_subscriptions)
  if (segmentRules.rules && segmentRules.rules.length > 0) {
    const planRule = segmentRules.rules.find(r => r.field === "plan");
    if (planRule && planRule.op === "in" && Array.isArray(planRule.value)) {
      const { data: plans } = await supabase
        .from("subscription_plans")
        .select("id")
        .in("name", planRule.value as string[]);

      if (plans && plans.length > 0) {
        const { data: matchingUsers } = await supabase
          .from("users")
          .select("id")
          .in("plan_id", plans.map(p => p.id));

        if (matchingUsers && matchingUsers.length > 0) {
          query = query.in("user_id", matchingUsers.map(u => u.id));
        } else {
          return { count: 0 };
        }
      } else {
        return { count: 0 };
      }
    }

    // Apply other segment filters (platform, timezone, device_locale)
    for (const rule of segmentRules.rules) {
      if (!ALLOWED_SEGMENT_FIELDS.has(rule.field)) continue;

      switch (rule.op) {
        case "eq":
          query = query.eq(rule.field, rule.value);
          break;
        case "neq":
          query = query.neq(rule.field, rule.value);
          break;
        case "in":
          if (Array.isArray(rule.value)) {
            query = query.in(rule.field, rule.value);
          }
          break;
        default:
          break;
      }
    }
  }

  // Fetch user_ids and deduplicate (one notification per user)
  const { data: subs, error } = await query.limit(50000);

  if (error) {
    console.error("Error previewing segment count:", error);
    throw new Error("Failed to preview segment count");
  }

  const uniqueUsers = new Set((subs || []).map((s: { user_id: string }) => s.user_id));
  return { count: uniqueUsers.size };
}
