/**
 * CANONICAL METRICS — SINGLE SOURCE OF TRUTH
 *
 * EVERY page, tab, and API route MUST use these functions for metric
 * queries. Do NOT write ad-hoc Prisma queries for sessions, page views,
 * conversions, forms, phone, bookings, CWV, events, GSC, or GA4 data
 * in API routes.
 *
 * If you need a new metric, add it HERE and import it.
 *
 * Table → Function mapping:
 *   TrackedSession              → getSessionCount()
 *   TrackedEvent page_view      → getPageViewCount()
 *   SiteConversion              → getConversionMetrics()
 *   SiteConversion form_*       → getFormMetrics()
 *   SiteConversion phone_*      → getPhoneMetrics()
 *   SiteConversion booking_*    → getBookingMetrics()
 *   TrackedEvent outbound_click → getOutboundClickCount()
 *   TrackedEvent (all)          → getEventCounts()
 *   RealUserMetric              → getCoreWebVitals()
 *   SiteEvent                   → getUXSignals()
 *   GscPage                     → getGSCMetrics()
 *   TrackedSite                 → getSiteTagInstalledAt()
 *
 * Production audit (2026-04-17):
 *   Sessions: TrackedSession = 16,571, TrackedEvent session_start =
 *     15,067 → USE TrackedSession
 *   Conversions: SiteConversion = 627 raw → USE SiteConversion with
 *     dedup
 *   Phone: 3 types (phone_number_click + phone_call + click_to_call)
 *     → COLLAPSE into one bucket
 *   CWV: Use p75 not mean, filter outliers > 30s
 */

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

// ─── Internal helpers ───────────────────────────────────────────

async function resolveTrackedSiteId(brandId: string): Promise<string | null> {
  const site = await prisma.trackedSite
    .findUnique({ where: { brandId }, select: { id: true } })
    .catch(() => null);
  return site?.id ?? null;
}

// ─── SESSIONS ───────────────────────────────────────────────────

export async function getSessionCount(
  brandId: string,
  since: Date,
  opts: { trackedSiteId?: string } = {},
): Promise<number> {
  const siteId = opts.trackedSiteId ?? (await resolveTrackedSiteId(brandId));
  if (!siteId) return 0;
  return prisma.trackedSession
    .count({ where: { trackedSiteId: siteId, startedAt: { gte: since } } })
    .catch(() => 0);
}

// ─── PAGE VIEWS ─────────────────────────────────────────────────

export async function getPageViewCount(
  brandId: string,
  since: Date,
  opts: { trackedSiteId?: string } = {},
): Promise<number> {
  const siteId = opts.trackedSiteId ?? (await resolveTrackedSiteId(brandId));
  if (!siteId) return 0;
  return prisma.trackedEvent
    .count({ where: { trackedSiteId: siteId, eventType: "page_view", timestamp: { gte: since } } })
    .catch(() => 0);
}

// ─── PAGES PER SESSION ──────────────────────────────────────────

export async function getPagesPerSession(
  brandId: string,
  since: Date,
  opts: { trackedSiteId?: string } = {},
): Promise<number> {
  const siteId = opts.trackedSiteId ?? (await resolveTrackedSiteId(brandId));
  const o = { trackedSiteId: siteId ?? undefined };
  const [pv, sessions] = await Promise.all([
    getPageViewCount(brandId, since, o),
    getSessionCount(brandId, since, o),
  ]);
  return sessions > 0 ? Math.round((pv / sessions) * 10) / 10 : 0;
}

// ─── CONVERSIONS ────────────────────────────────────────────────

export { getConversionMetrics, summariseForLog } from "./conversion-metrics";
export type { ConversionMetrics } from "./conversion-metrics";

// ─── FORM METRICS ───────────────────────────────────────────────

export interface FormMetrics {
  starts: number;
  submits: number;
  completionRate: number;
}

const FORM_SUBMIT_TYPES = [
  "form_submit", "form_submitted", "form_submission",
];

export async function getFormMetrics(
  brandId: string,
  since: Date,
  opts: { trackedSiteId?: string } = {},
): Promise<FormMetrics> {
  const siteId = opts.trackedSiteId ?? (await resolveTrackedSiteId(brandId));
  const starts = siteId
    ? await prisma.trackedEvent
        .count({ where: { trackedSiteId: siteId, eventType: "form_start", timestamp: { gte: since } } })
        .catch(() => 0)
    : 0;
  const submits = await prisma.siteConversion
    .count({ where: { brandId, conversionType: { in: FORM_SUBMIT_TYPES }, timestamp: { gte: since } } })
    .catch(() => 0);
  const completionRate = starts > 0 ? Math.round((submits / starts) * 1000) / 10 : 0;
  return { starts, submits, completionRate };
}

// ─── PHONE METRICS ──────────────────────────────────────────────

export interface PhoneMetrics {
  total: number;
}

const PHONE_TYPES = [
  "phone_number_click", "phone_call", "click_to_call",
];

export async function getPhoneMetrics(
  brandId: string,
  since: Date,
): Promise<PhoneMetrics> {
  const total = await prisma.siteConversion
    .count({ where: { brandId, conversionType: { in: PHONE_TYPES }, timestamp: { gte: since } } })
    .catch(() => 0);
  return { total };
}

// ─── BOOKING METRICS ────────────────────────────────────────────

export interface BookingMetrics {
  intents: number;
  opened: number;
  steps: number;
  submitted: number;
  confirmed: number;
}

const BOOKING_SUBMIT_TYPES = ["booking_attempt", "appointment_attempted"];
const BOOKING_CONFIRM_TYPES = ["appointment_booked", "booking_confirmed"];

export async function getBookingMetrics(
  brandId: string,
  since: Date,
  opts: { trackedSiteId?: string } = {},
): Promise<BookingMetrics> {
  const siteId = opts.trackedSiteId ?? (await resolveTrackedSiteId(brandId));
  const [intents, opened, steps, submitted, confirmed] = await Promise.all([
    siteId
      ? prisma.trackedEvent.count({ where: { trackedSiteId: siteId, eventType: "appointment_intent", timestamp: { gte: since } } }).catch(() => 0)
      : Promise.resolve(0),
    siteId
      ? prisma.trackedEvent.count({ where: { trackedSiteId: siteId, eventType: "booking_widget_opened", timestamp: { gte: since } } }).catch(() => 0)
      : Promise.resolve(0),
    siteId
      ? prisma.trackedEvent.count({ where: { trackedSiteId: siteId, eventType: "booking_step", timestamp: { gte: since } } }).catch(() => 0)
      : Promise.resolve(0),
    // intentionally unfiltered: internal service; uses raw counts intentionally
    prisma.siteConversion.count({ where: { brandId, conversionType: { in: BOOKING_SUBMIT_TYPES }, timestamp: { gte: since } } }).catch(() => 0),
    // intentionally unfiltered: internal service; uses raw counts intentionally
    prisma.siteConversion.count({ where: { brandId, conversionType: { in: BOOKING_CONFIRM_TYPES }, timestamp: { gte: since } } }).catch(() => 0),
  ]);
  return { intents, opened, steps, submitted, confirmed };
}

// ─── OUTBOUND CLICKS ────────────────────────────────────────────

export async function getOutboundClickCount(
  brandId: string,
  since: Date,
  opts: { trackedSiteId?: string } = {},
): Promise<number> {
  const siteId = opts.trackedSiteId ?? (await resolveTrackedSiteId(brandId));
  if (!siteId) return 0;
  return prisma.trackedEvent
    .count({ where: { trackedSiteId: siteId, eventType: "outbound_click", timestamp: { gte: since } } })
    .catch(() => 0);
}

// ─── EVENT COUNTS ───────────────────────────────────────────────

export interface EventCounts {
  total: number;
  today: number;
  distinctTypes: number;
}

export async function getEventCounts(
  brandId: string,
  since: Date,
  opts: { trackedSiteId?: string } = {},
): Promise<EventCounts> {
  const siteId = opts.trackedSiteId ?? (await resolveTrackedSiteId(brandId));
  if (!siteId) return { total: 0, today: 0, distinctTypes: 0 };
  const todayStart = new Date();
  todayStart.setUTCHours(0, 0, 0, 0);
  const [total, today, types] = await Promise.all([
    prisma.trackedEvent.count({ where: { trackedSiteId: siteId, timestamp: { gte: since } } }).catch(() => 0),
    prisma.trackedEvent.count({ where: { trackedSiteId: siteId, timestamp: { gte: todayStart } } }).catch(() => 0),
    prisma.trackedEvent.groupBy({ by: ["eventType"], where: { trackedSiteId: siteId, timestamp: { gte: since } } }).catch(() => []),
  ]);
  return { total, today, distinctTypes: types.length };
}

// ─── CORE WEB VITALS (P75, NOT MEAN) ───────────────────────────

export interface CoreWebVitals {
  lcp: number;
  cls: number;
  inp: number;
  ttfb: number;
  fcp: number;
  samples: number;
}

function p75(arr: number[]): number {
  if (arr.length === 0) return 0;
  arr.sort((a, b) => a - b);
  return Math.round(arr[Math.floor(arr.length * 0.75)] * 100) / 100;
}

const CWV_CEIL = { lcp: 60_000, cls: 10, inp: 30_000, ttfb: 60_000, fcp: 60_000 };

export async function getCoreWebVitals(
  brandId: string,
  since: Date,
): Promise<CoreWebVitals> {
  const rows = await prisma.realUserMetric
    .findMany({
      where: { brandId, timestamp: { gte: since } },
      select: { lcp: true, cls: true, inp: true, ttfb: true, fcp: true },
      orderBy: { timestamp: "desc" },
      take: 5000,
    })
    .catch(() => []);

  const lcpArr: number[] = [];
  const clsArr: number[] = [];
  const inpArr: number[] = [];
  const ttfbArr: number[] = [];
  const fcpArr: number[] = [];
  for (const m of rows) {
    if (m.lcp != null && m.lcp < CWV_CEIL.lcp)   lcpArr.push(m.lcp);
    if (m.cls != null && m.cls < CWV_CEIL.cls)     clsArr.push(m.cls);
    if (m.inp != null && m.inp < CWV_CEIL.inp)     inpArr.push(m.inp);
    if (m.ttfb != null && m.ttfb < CWV_CEIL.ttfb) ttfbArr.push(m.ttfb);
    if (m.fcp != null && m.fcp < CWV_CEIL.fcp)     fcpArr.push(m.fcp);
  }
  return {
    lcp: p75(lcpArr),
    cls: p75(clsArr),
    inp: p75(inpArr),
    ttfb: p75(ttfbArr),
    fcp: p75(fcpArr),
    samples: rows.length,
  };
}

// ─── UX SIGNALS (SCOPED, EXCLUDES NOISE) ────────────────────────

export interface UXSignals {
  rageClicks: number;
  deadClicks: number;
  hesitations: number;
  exitIntents: number;
  formAbandoned: number;
  scrollBounces: number;
  errorsShown: number;
}

export async function getUXSignals(
  brandId: string,
  since: Date,
): Promise<UXSignals> {
  const SIGNAL_TYPES = [
    "rage_click", "dead_click", "hesitation", "exit_intent",
    "form_abandoned", "scroll_bounce", "error_shown",
  ];
  const counts = await prisma.siteEvent
    .groupBy({
      by: ["eventType"],
      where: { brandId, eventType: { in: SIGNAL_TYPES }, timestamp: { gte: since } },
      _count: true,
    })
    .catch(() => [] as Array<{ eventType: string; _count: number }>);
  const map: Record<string, number> = {};
  for (const c of counts) map[c.eventType] = typeof c._count === "number" ? c._count : 0;
  return {
    rageClicks: map.rage_click ?? 0,
    deadClicks: map.dead_click ?? 0,
    hesitations: map.hesitation ?? 0,
    exitIntents: map.exit_intent ?? 0,
    formAbandoned: map.form_abandoned ?? 0,
    scrollBounces: map.scroll_bounce ?? 0,
    errorsShown: map.error_shown ?? 0,
  };
}

// ─── GSC METRICS ────────────────────────────────────────────────

export interface GSCMetrics {
  clicks: number;
  impressions: number;
  avgPosition: number;
  ctr: number;
}

export async function getGSCMetrics(
  brandId: string,
  since: Date,
  until?: Date,
): Promise<GSCMetrics> {
  const dateFilter: { gte: Date; lte?: Date } = { gte: since };
  if (until) dateFilter.lte = until;
  const result = await prisma.gscPage
    .aggregate({
      where: { brandId, date: dateFilter },
      _sum: { clicks: true, impressions: true },
      _avg: { position: true, ctr: true },
    })
    .catch(() => ({
      _sum: { clicks: null, impressions: null },
      _avg: { position: null, ctr: null },
    }));
  return {
    clicks: result._sum.clicks ?? 0,
    impressions: result._sum.impressions ?? 0,
    avgPosition: Math.round((result._avg.position ?? 0) * 10) / 10,
    ctr: Math.round((result._avg.ctr ?? 0) * 10000) / 100,
  };
}

// ─── INSTALL DATE ───────────────────────────────────────────────

export { getSiteTagInstalledAt, siteTagAgeDays } from "./site-tag";
