import { usePrisma } from "../utils/prisma";

// ─── Types ────────────────────────────────────────────────────────────────────

export type ProtectionSource =
  | "vpn"
  | "mdm"
  | "cooldown_disable"
  | "client"
  | "system";

// ─── Ingestion ────────────────────────────────────────────────────────────────

/**
 * Returns the most recent ProtectionStateLog entry for a user, or null if
 * no events have been recorded yet.
 */
export async function getLastProtectionEvent(userId: string) {
  const db = usePrisma();
  return db.protectionStateLog.findFirst({
    where: { userId },
    orderBy: { occurredAt: "desc" },
  });
}

/**
 * Appends a new protection-state transition for a user.
 * Caller is responsible for deduplication (check getLastProtectionEvent first).
 */
export async function appendProtectionEvent(
  userId: string,
  active: boolean,
  source: ProtectionSource,
  occurredAt?: Date,
) {
  const db = usePrisma();
  return db.protectionStateLog.create({
    data: {
      userId,
      active,
      source,
      occurredAt: occurredAt ?? new Date(),
    },
  });
}

/**
 * Writes a protection-state transition only if it differs from the last
 * known state for this user. Returns the new row if written, null if deduped.
 */
export async function appendProtectionEventDeduped(
  userId: string,
  active: boolean,
  source: ProtectionSource,
  occurredAt?: Date,
): Promise<{ id: string } | null> {
  const last = await getLastProtectionEvent(userId);
  if (last && last.active === active) {
    // No state change — skip write.
    return null;
  }
  return appendProtectionEvent(userId, active, source, occurredAt);
}

// ─── Coverage Compute ─────────────────────────────────────────────────────────

export interface ProtectionCoverage {
  firstProtectionAt: string | null; // ISO-8601 or null
  protectedDays: number;
  unprotectedDays: number;
  currentStreakDays: number;
  longestStreakDays: number;
}

/**
 * Reads all protection events for a user and computes the coverage/streak
 * metrics at read-time (no cron needed).
 *
 * Algorithm summary:
 *   1. Fetch all events ordered by occurredAt asc.
 *   2. firstProtectionAt = occurredAt of first active:true event.
 *      If none → return all-zero response.
 *   3. Build intervals: [event[i].occurredAt, event[i+1].occurredAt) for each
 *      segment, last segment ends at now().
 *   4. For each UTC calendar day in [firstProtectionAt..today], compute total
 *      unprotected minutes. If > 6h (360min) → day counts as UNPROTECTED.
 *   5. currentStreakDays = consecutive protected days running up to today.
 *   6. longestStreakDays = longest such run in the full history.
 */
export async function computeProtectionCoverage(
  userId: string,
): Promise<ProtectionCoverage> {
  const db = usePrisma();

  const events = await db.protectionStateLog.findMany({
    where: { userId },
    orderBy: { occurredAt: "asc" },
    select: { active: true, occurredAt: true },
  });

  // Find first active:true event.
  const firstActiveEvent = events.find((e) => e.active);
  if (!firstActiveEvent) {
    return {
      firstProtectionAt: null,
      protectedDays: 0,
      unprotectedDays: 0,
      currentStreakDays: 0,
      longestStreakDays: 0,
    };
  }

  const firstProtectionAt = firstActiveEvent.occurredAt;
  const now = new Date();

  // ─── Build timeline of (state, from, to) intervals ───────────────────────
  // Each event transitions the state FROM that moment forward.
  // We start from the first active event (ignore any earlier inactive events
  // before the user ever activated protection — they don't count in the window).
  //
  // The state at firstProtectionAt is "active:true" (that's the first event).
  // We replay all events from that point on.

  // Find the index of the first active event.
  const startIdx = events.indexOf(firstActiveEvent);
  const relevantEvents = events.slice(startIdx);

  interface Interval {
    active: boolean;
    from: Date;
    to: Date;
  }

  const intervals: Interval[] = [];
  for (let i = 0; i < relevantEvents.length; i++) {
    const from = relevantEvents[i].occurredAt;
    const to =
      i + 1 < relevantEvents.length ? relevantEvents[i + 1].occurredAt : now;
    intervals.push({ active: relevantEvents[i].active, from, to });
  }

  // ─── Enumerate UTC calendar days from firstProtectionAt to today ─────────
  // Normalise to UTC date boundaries.
  const startDay = utcDayStart(firstProtectionAt);
  const todayDay = utcDayStart(now);

  // Map each day to total unprotected minutes within that day.
  const days: boolean[] = []; // true = protected, false = unprotected

  for (
    let day = new Date(startDay);
    day <= todayDay;
    day = new Date(day.getTime() + 86400_000)
  ) {
    const dayEnd = new Date(day.getTime() + 86400_000);
    let unprotectedMs = 0;

    for (const iv of intervals) {
      if (iv.active) continue; // Protected interval, doesn't add unprotected time.

      // Overlap of this interval with [day, dayEnd)
      const overlapStart = Math.max(iv.from.getTime(), day.getTime());
      const overlapEnd = Math.min(iv.to.getTime(), dayEnd.getTime());
      if (overlapEnd > overlapStart) {
        unprotectedMs += overlapEnd - overlapStart;
      }
    }

    const unprotectedMinutes = unprotectedMs / 60_000;
    // Day is UNPROTECTED only if total unprotected time > 6h (360 min).
    days.push(unprotectedMinutes <= 360);
  }

  // ─── Compute metrics ─────────────────────────────────────────────────────
  const protectedDays = days.filter(Boolean).length;
  const totalDays = days.length;
  const unprotectedDays = totalDays - protectedDays;

  // currentStreakDays: consecutive protected days from the END of the array.
  let currentStreakDays = 0;
  for (let i = days.length - 1; i >= 0; i--) {
    if (days[i]) {
      currentStreakDays++;
    } else {
      break;
    }
  }

  // longestStreakDays: longest consecutive run of protected days.
  let longestStreakDays = 0;
  let runLength = 0;
  for (const d of days) {
    if (d) {
      runLength++;
      if (runLength > longestStreakDays) longestStreakDays = runLength;
    } else {
      runLength = 0;
    }
  }

  return {
    firstProtectionAt: firstProtectionAt.toISOString(),
    protectedDays,
    unprotectedDays,
    currentStreakDays,
    longestStreakDays,
  };
}

// ─── Helpers ──────────────────────────────────────────────────────────────────

/** Returns midnight UTC for the given date (floor to day boundary). */
function utcDayStart(date: Date): Date {
  return new Date(
    Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()),
  );
}
