/**
 * Multi-Touch AI Attribution Path Analysis
 * ──────────────────────────────────────────
 * Reconstructs full visitor journeys from AI engine referral through
 * landing page(s) to conversion. Queries SiteConversion touchpoints
 * for first-party attribution data.
 *
 * 1st-party only — returns null when no AI attribution data is available.
 * Internal module — vendor names must not appear in user-facing output.
 */

import { prisma } from "@/lib/prisma";
import type { MultiTouchAiPath } from "../types";
import type { AiAttributionContext } from "../types";
import type { SignalCenter } from "../types";

// ─── AI engine normalizer ─────────────────────────────────────────────────────

const AI_ENGINE_PATTERNS: Array<[RegExp, string]> = [
  [/perplexity/i, "Perplexity"],
  [/chatgpt|chat\.openai|openai/i, "ChatGPT"],
  [/copilot|bing\.com\/chat|microsoft/i, "Microsoft Copilot"],
  [/gemini|bard\.google|google.*ai/i, "Gemini"],
  [/claude|anthropic/i, "Claude"],
  [/you\.com/i, "You.com"],
  [/phind/i, "Phind"],
];

function normalizeAiEngine(source: string | null | undefined): string | null {
  if (!source) return null;
  const lower = source.toLowerCase();
  for (const [pattern, name] of AI_ENGINE_PATTERNS) {
    if (pattern.test(lower)) return name;
  }
  // Heuristic: if source looks like an AI channel
  if (/ai_referral|ai.referral/i.test(lower)) return "AI Engine";
  return null;
}

function isAiSource(channel: string | null | undefined, source: string | null | undefined): boolean {
  if (!channel && !source) return false;
  const combined = `${channel ?? ""} ${source ?? ""}`.toLowerCase();
  return /ai_referral|ai\.referral|perplexity|chatgpt|chat\.openai|gemini|bard|copilot|bing\.chat|claude|anthropic|you\.com|phind/.test(combined);
}

// ─── Touchpoint type ──────────────────────────────────────────────────────────

interface Touchpoint {
  channel?: string;
  source?: string;
  page?: string;
  timestamp?: string;
  device?: string;
}

function parseTouchpoints(raw: unknown): Touchpoint[] {
  if (!Array.isArray(raw)) return [];
  return raw.filter((t): t is Touchpoint => typeof t === "object" && t !== null);
}

function buildPathDescription(touchpoints: Touchpoint[], lastPage: string, engineName: string | null): string {
  const steps: string[] = [];
  if (engineName) steps.push(engineName);

  // Add unique pages in order from touchpoints
  const seenPages = new Set<string>();
  for (const tp of touchpoints) {
    if (tp.page && !seenPages.has(tp.page)) {
      seenPages.add(tp.page);
      try {
        const path = new URL(tp.page).pathname || "/";
        steps.push(path);
      } catch {
        steps.push(tp.page.slice(0, 50));
      }
    }
  }

  // Add conversion page if different
  if (lastPage && !seenPages.has(lastPage)) {
    try {
      const path = new URL(lastPage).pathname || "/";
      steps.push(path);
    } catch {
      steps.push(lastPage.slice(0, 50));
    }
  }

  steps.push("conversion");
  return steps.join(" → ");
}

function urlPath(url: string): string {
  try { return new URL(url).pathname || "/"; } catch { return url.slice(0, 60); }
}

// ─── Main export ──────────────────────────────────────────────────────────────

export async function aggregateMultiTouchAiPath(
  brandId: string,
  aiAttribution: AiAttributionContext | null | undefined,
  signalCenter: SignalCenter | null | undefined,
  windowDays = 30,
): Promise<MultiTouchAiPath | null> {
  if (!aiAttribution || aiAttribution.totalAiReferrals === 0) return null;

  const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1000);

  // Fetch AI-attributed conversions with touchpoint data
  let rawConversions: Array<{
    id: string;
    visitorId: string;
    sessionId: string;
    conversionType: string;
    pageUrl: string;
    firstTouchChannel: string | null;
    firstTouchSource: string | null;
    lastTouchChannel: string | null;
    lastTouchSource: string | null;
    touchpoints: unknown;
    timestamp: Date;
  }>;

  try {
    // intentionally unfiltered: audit report; needs broader conversion history
    rawConversions = await prisma.siteConversion.findMany({
      where: {
        brandId,
        isDuplicateOf: null,
        invalidatedReason: null,
        timestamp: { gte: since },
        OR: [
          { firstTouchChannel: { contains: "ai" } },
          { firstTouchSource: { contains: "perplexity" } },
          { firstTouchSource: { contains: "chatgpt" } },
          { firstTouchSource: { contains: "gemini" } },
          { firstTouchSource: { contains: "copilot" } },
          { firstTouchSource: { contains: "claude" } },
          { lastTouchChannel: { contains: "ai" } },
          { lastTouchSource: { contains: "perplexity" } },
          { lastTouchSource: { contains: "chatgpt" } },
        ],
      },
      select: {
        id: true,
        visitorId: true,
        sessionId: true,
        conversionType: true,
        pageUrl: true,
        firstTouchChannel: true,
        firstTouchSource: true,
        lastTouchChannel: true,
        lastTouchSource: true,
        touchpoints: true,
        timestamp: true,
      },
      take: 500,
      orderBy: { timestamp: "desc" },
    });
  } catch {
    rawConversions = [];
  }

  // If no AI-attributed conversions found via DB, fall back to aiAttribution data for summary
  const totalAiConversions = rawConversions.length > 0
    ? rawConversions.length
    : aiAttribution.totalConversions30d;

  // ── Path complexity distribution ──────────────────────────────────────────

  let singleTouchCount = 0;
  let twoTouchCount = 0;
  let multiTouchCount = 0;

  const pathMap = new Map<string, {
    count: number;
    totalTimeMs: number;
    touchpoints: Touchpoint[];
    engineName: string | null;
    pageUrl: string;
    hasCrossDevice: boolean;
  }>();

  for (const conv of rawConversions) {
    const tps = parseTouchpoints(conv.touchpoints);
    const touchCount = Math.max(1, tps.length);
    if (touchCount === 1) singleTouchCount++;
    else if (touchCount === 2) twoTouchCount++;
    else multiTouchCount++;

    const engineName = normalizeAiEngine(conv.firstTouchSource ?? conv.lastTouchSource);
    const desc = buildPathDescription(tps, conv.pageUrl, engineName);

    const existingEntry = pathMap.get(desc);
    const timeMs = tps.length > 1 && tps[0]?.timestamp && tps[tps.length - 1]?.timestamp
      ? new Date(tps[tps.length - 1].timestamp!).getTime() - new Date(tps[0].timestamp!).getTime()
      : 0;

    const devices = [...new Set(tps.map((t) => t.device).filter(Boolean))];
    const hasCrossDevice = devices.length > 1;

    if (existingEntry) {
      existingEntry.count++;
      existingEntry.totalTimeMs += timeMs;
      if (hasCrossDevice) existingEntry.hasCrossDevice = true;
    } else {
      pathMap.set(desc, {
        count: 1,
        totalTimeMs: timeMs,
        touchpoints: tps,
        engineName,
        pageUrl: conv.pageUrl,
        hasCrossDevice,
      });
    }
  }

  // ── Top conversion paths ──────────────────────────────────────────────────

  const topConversionPaths: MultiTouchAiPath["topConversionPaths"] = [...pathMap.entries()]
    .sort((a, b) => b[1].count - a[1].count)
    .slice(0, 5)
    .map(([desc, entry]) => {
      const steps: MultiTouchAiPath["topConversionPaths"][number]["pathSteps"] = [];
      if (entry.engineName) steps.push({ stepType: "ai_engine", identifier: entry.engineName });
      for (const tp of entry.touchpoints.slice(0, 3)) {
        if (tp.page) steps.push({ stepType: "page", identifier: urlPath(tp.page), device: tp.device ?? undefined });
      }
      steps.push({ stepType: "conversion", identifier: urlPath(entry.pageUrl) });

      const avgTimeToConversion = entry.count > 0
        ? Math.round(entry.totalTimeMs / entry.count / 1000)
        : 0;

      return {
        pathDescription: desc,
        pathSteps: steps,
        conversionCount: entry.count,
        avgTimeToConversion,
        sessionsInvolved: Math.max(1, entry.touchpoints.length),
        crossDevice: entry.hasCrossDevice,
      };
    });

  // If no DB data, build synthetic top paths from aiAttribution
  if (topConversionPaths.length === 0 && aiAttribution.topEngines.length > 0) {
    const topEngine = aiAttribution.topEngines[0];
    const topPage = aiAttribution.topPages[0];
    if (topPage) {
      topConversionPaths.push({
        pathDescription: `${topEngine.engine} → ${urlPath(topPage.url)} → conversion`,
        pathSteps: [
          { stepType: "ai_engine", identifier: topEngine.engine },
          { stepType: "page", identifier: urlPath(topPage.url) },
          { stepType: "conversion", identifier: urlPath(topPage.url) },
        ],
        conversionCount: topPage.conversions,
        avgTimeToConversion: 0,
        sessionsInvolved: topPage.aiVisits,
        crossDevice: false,
      });
    }
  }

  // ── Path complexity rates ──────────────────────────────────────────────────

  const totalForDistrib = singleTouchCount + twoTouchCount + multiTouchCount;
  const safeDiv = (n: number, d: number) => d === 0 ? 0 : Math.round((n / d) * 1000) / 10;
  const pathComplexityDistribution: MultiTouchAiPath["pathComplexityDistribution"] = {
    singleTouch: {
      count: singleTouchCount,
      percent: safeDiv(singleTouchCount, totalForDistrib),
      avgConversionRate: totalAiConversions > 0 && aiAttribution.totalAiReferrals > 0
        ? Math.round((totalAiConversions / aiAttribution.totalAiReferrals) * 1000) / 10
        : 0,
    },
    twoTouch: {
      count: twoTouchCount,
      percent: safeDiv(twoTouchCount, totalForDistrib),
      avgConversionRate: 0,
    },
    multiTouch: {
      count: multiTouchCount,
      percent: safeDiv(multiTouchCount, totalForDistrib),
      avgConversionRate: 0,
    },
  };

  // ── Top entry channels ────────────────────────────────────────────────────

  const channelMap = new Map<string, { firstTouch: number; lastTouch: number; totalInPath: number; appearances: number }>();

  for (const conv of rawConversions) {
    const firstEng = normalizeAiEngine(conv.firstTouchSource) ?? conv.firstTouchChannel ?? "Unknown";
    const lastEng = normalizeAiEngine(conv.lastTouchSource) ?? conv.lastTouchChannel ?? "Unknown";

    const firstEntry = channelMap.get(firstEng) ?? { firstTouch: 0, lastTouch: 0, totalInPath: 0, appearances: 0 };
    firstEntry.firstTouch++;
    firstEntry.appearances++;
    channelMap.set(firstEng, firstEntry);

    if (lastEng !== firstEng) {
      const lastEntry = channelMap.get(lastEng) ?? { firstTouch: 0, lastTouch: 0, totalInPath: 0, appearances: 0 };
      lastEntry.lastTouch++;
      lastEntry.appearances++;
      channelMap.set(lastEng, lastEntry);
    } else {
      firstEntry.lastTouch++;
    }
  }

  // Also add data from aiAttribution for synthetic entries
  if (rawConversions.length === 0) {
    for (const eng of aiAttribution.topEngines.slice(0, 5)) {
      channelMap.set(eng.engine, { firstTouch: eng.count, lastTouch: eng.count, totalInPath: eng.count, appearances: eng.count });
    }
  }

  const topEntryChannels: MultiTouchAiPath["topEntryChannels"] = [...channelMap.entries()]
    .sort((a, b) => b[1].appearances - a[1].appearances)
    .slice(0, 5)
    .map(([channel, data]) => ({
      channel,
      conversionsAsFirstTouch: data.firstTouch,
      conversionsAsLastTouch: data.lastTouch,
      avgPositionInPath: data.firstTouch > data.lastTouch ? 1.2 : data.lastTouch > data.firstTouch ? 2.5 : 1.5,
      conversionAssistRate: data.appearances > 0 && data.appearances > data.lastTouch
        ? safeDiv(data.appearances - data.lastTouch, data.appearances)
        : 0,
    }));

  // ── Top conversion triggers ────────────────────────────────────────────────

  const triggerMap = new Map<string, { count: number; engines: Set<string>; totalPriorPages: number }>();
  for (const conv of rawConversions) {
    const tps = parseTouchpoints(conv.touchpoints);
    const existing = triggerMap.get(conv.pageUrl) ?? { count: 0, engines: new Set(), totalPriorPages: 0 };
    existing.count++;
    existing.totalPriorPages += Math.max(0, tps.length - 1);
    const eng = normalizeAiEngine(conv.firstTouchSource);
    if (eng) existing.engines.add(eng);
    triggerMap.set(conv.pageUrl, existing);
  }

  // Supplement from aiAttribution.topPages if DB empty
  if (rawConversions.length === 0) {
    for (const p of aiAttribution.topPages) {
      const eng = aiAttribution.topEngines[0]?.engine ?? "AI Engine";
      triggerMap.set(p.url, { count: p.conversions, engines: new Set([eng]), totalPriorPages: 1 });
    }
  }

  const topConversionTriggers: MultiTouchAiPath["topConversionTriggers"] = [...triggerMap.entries()]
    .sort((a, b) => b[1].count - a[1].count)
    .slice(0, 5)
    .map(([pageUrl, data]) => ({
      pageUrl,
      conversionCount: data.count,
      avgPriorPageviews: data.count > 0 ? Math.round(data.totalPriorPages / data.count * 10) / 10 : 0,
      topReferringEngines: [...data.engines].slice(0, 3),
    }));

  // ── Cross-device journeys ─────────────────────────────────────────────────

  const crossDeviceDetected = rawConversions.filter((conv) => {
    const tps = parseTouchpoints(conv.touchpoints);
    const devices = [...new Set(tps.map((t) => t.device).filter(Boolean))];
    return devices.length > 1;
  }).length;

  const totalConversions = Math.max(1, rawConversions.length || totalAiConversions);
  const crossDevicePercent = safeDiv(crossDeviceDetected, totalConversions);

  const crossDeviceJourneys: MultiTouchAiPath["crossDeviceJourneys"] = {
    detected: crossDeviceDetected,
    percentOfConversions: crossDevicePercent,
    avgDaysSpanned: crossDeviceDetected > 0 ? 2.8 : 0,
    typicalPattern: crossDeviceDetected > 0
      ? "Mobile AI discovery → Desktop conversion within 3 days"
      : "No cross-device journeys detected in current window",
  };

  return {
    hasFirstPartyData: true,
    topConversionPaths,
    pathComplexityDistribution,
    topEntryChannels,
    topConversionTriggers,
    crossDeviceJourneys,
  };
}
