/**
 * OTT / CTV Cross-Device Attribution
 * ────────────────────────────────────
 * Matches OTT ad impressions (delivered to smart TVs) to subsequent
 * web visits from the same household. The connection uses the IP
 * /24 subnet + city matching already proven in HouseholdCluster.
 *
 * Flow:
 *   1. Ad platform (The Trade Desk, Roku, etc.) provides impression
 *      data including device ID + household IP.
 *   2. Impressions are stored in OttImpression with the /24 subnet
 *      extracted from the IP.
 *   3. This matcher scans unmatched impressions and looks for web
 *      sessions (TrackedSession) from the same subnet + city that
 *      started AFTER the impression.
 *   4. When found, stamps matched=true + the visitor/session IDs so
 *      the attribution path builder can prepend the OTT touchpoint.
 */

import { prisma } from "@/lib/prisma";

export interface OttMatchResult {
  processed: number;
  matched: number;
  errors: number;
}

export async function matchOttImpressions(
  brandId: string,
  opts: { windowDays?: number } = {},
): Promise<OttMatchResult> {
  const windowDays = opts.windowDays ?? 14;
  const since = new Date(Date.now() - windowDays * 86_400_000);

  const unmatched = await prisma.ottImpression.findMany({
    where: { brandId, matched: false, impressionAt: { gte: since } },
    select: { id: true, ipSubnet: true, city: true, impressionAt: true },
    take: 1000,
  }).catch(() => []);

  const site = await prisma.trackedSite.findUnique({
    where: { brandId },
    select: { id: true },
  });
  if (!site) return { processed: unmatched.length, matched: 0, errors: 0 };

  let matched = 0;
  let errors = 0;

  for (const imp of unmatched) {
    if (!imp.ipSubnet) continue;
    try {
      const session = await prisma.trackedSession.findFirst({
        where: {
          trackedSiteId: site.id,
          ipAddress: { startsWith: imp.ipSubnet },
          ...(imp.city ? { city: imp.city } : {}),
          startedAt: {
            gte: imp.impressionAt,
            lte: new Date(imp.impressionAt.getTime() + windowDays * 86_400_000),
          },
        },
        select: { id: true, sessionId: true },
        orderBy: { startedAt: "asc" },
      });

      if (session) {
        await prisma.ottImpression.update({
          where: { id: imp.id },
          data: {
            matched: true,
            matchedSessionId: session.sessionId,
            matchedVisitorId: session.sessionId,
          },
        });
        matched++;
      }
    } catch {
      errors++;
    }
  }

  if (matched > 0) {
    console.info("[ott-attribution] matching complete", { brandId, processed: unmatched.length, matched, errors });
  }

  return { processed: unmatched.length, matched, errors };
}

export async function getOttSummary(brandId: string, since: Date) {
  const [total, matchedCount, byPlatform] = await Promise.all([
    prisma.ottImpression.count({ where: { brandId, impressionAt: { gte: since } } }).catch(() => 0),
    prisma.ottImpression.count({ where: { brandId, matched: true, impressionAt: { gte: since } } }).catch(() => 0),
    prisma.ottImpression.groupBy({
      by: ["platform"],
      where: { brandId, impressionAt: { gte: since } },
      _count: true,
    }).catch(() => []),
  ]);

  return {
    totalImpressions: total,
    matchedImpressions: matchedCount,
    matchRate: total > 0 ? Math.round((matchedCount / total) * 1000) / 10 : 0,
    byPlatform: byPlatform.map((r) => ({
      platform: r.platform,
      impressions: typeof r._count === "number" ? r._count : 0,
    })),
  };
}
