/**
 * Release gate evaluator (spec §17).
 * Computes go/no-go status from product_analytics_events.
 */
import { sql } from "drizzle-orm";
import type { Database } from "./client.js";
import { schema } from "./index.js";
import {
  BUILT_IN_RELEASE_GATES,
  RELEASE_GATE_STATUSES,
  type ReleaseGateComparison,
  type ReleaseGateStatus,
} from "@app/shared-types";

export interface GateEvaluation {
  gateKey: string;
  category: string;
  nameSv: string;
  nameEn: string;
  targetValue: number;
  comparison: ReleaseGateComparison;
  value: number | null;
  status: ReleaseGateStatus;
  lastEvaluatedAt: string;
  blocking: boolean;
  notes?: string;
}

function compare(value: number, target: number, comparison: ReleaseGateComparison): boolean {
  if (comparison === "gte") return value >= target;
  if (comparison === "lte") return value <= target;
  return Math.abs(value - target) < Number.EPSILON;
}

function decideStatus(
  value: number | null,
  target: number,
  comparison: ReleaseGateComparison,
  measurable = true,
): ReleaseGateStatus {
  if (!measurable || value === null || Number.isNaN(value)) return "not_measurable";
  return compare(value, target, comparison) ? "passed" : "failed";
}

/**
 * Seed the built-in release gates from §17. Idempotent – existing rows are
 * left untouched so admin tuning is preserved.
 */
export async function seedReleaseGates(db: Database): Promise<void> {
  for (const gate of BUILT_IN_RELEASE_GATES) {
    await db
      .insert(schema.releaseGates)
      .values({
        gateKey: gate.gateKey,
        category: gate.category,
        nameSv: gate.nameSv,
        nameEn: gate.nameEn,
        descriptionSv: gate.descriptionSv,
        descriptionEn: gate.descriptionEn,
        targetValue: gate.targetValue,
        comparison: gate.comparison,
        evaluationWindowDays: gate.evaluationWindowDays,
        blocking: gate.blocking,
        status: "pending",
      })
      .onConflictDoNothing({ target: schema.releaseGates.gateKey });
  }
}

/**
 * Evaluate all release gates and persist results.
 */
export async function evaluateReleaseGates(db: Database): Promise<GateEvaluation[]> {
  const gates = await db.select().from(schema.releaseGates).orderBy(schema.releaseGates.gateKey);
  const now = new Date();
  const results: GateEvaluation[] = [];

  for (const gate of gates) {
    const value = await evaluateGate(db, gate.gateKey, gate.evaluationWindowDays);
    const status = decideStatus(value, gate.targetValue, gate.comparison, value !== null);
    const updated = await db
      .update(schema.releaseGates)
      .set({
        lastValue: value,
        lastEvaluatedAt: now,
        status,
        updatedAt: now,
      })
      .where(sql`${schema.releaseGates.id} = ${gate.id}`)
      .returning();
    const row = updated[0] ?? gate;
    results.push({
      gateKey: row.gateKey,
      category: row.category,
      nameSv: row.nameSv,
      nameEn: row.nameEn,
      targetValue: row.targetValue,
      comparison: row.comparison,
      value: row.lastValue,
      status: row.status,
      lastEvaluatedAt: row.lastEvaluatedAt?.toISOString() ?? now.toISOString(),
      blocking: row.blocking,
      notes: row.notes ?? undefined,
    });
  }

  return results;
}

/**
 * Evaluate a single gate by key. Returns null if not measurable yet.
 */
export async function evaluateGate(
  db: Database,
  gateKey: string,
  windowDays: number,
): Promise<number | null> {
  const end = new Date();
  const start = new Date();
  start.setUTCDate(start.getUTCDate() - windowDays);
  const startIso = start.toISOString();
  const endIso = end.toISOString();

  switch (gateKey) {
    case "first_scan_rate":
      return userConversion(db, "account_created", "scan_completed", startIso, endIso);

    case "recipe_view_rate":
      return userConversion(db, "account_created", "recommendations_viewed", startIso, endIso);

    case "recipe_save_or_cook_day0":
      return sameDayConversion(db, ["recipe_saved", "cooking_session_started"], startIso, endIso);

    case "second_inventory_event_week1":
      return repeatedEventRate(
        db,
        ["inventory_item_added", "inventory_item_consumed", "inventory_item_discarded"],
        2,
        startIso,
        endIso,
      );

    case "household_collaboration_rate":
      return householdCollaborationRate(db, startIso, endIso);

    case "weekly_trusted_meal_week2":
      return weeklyTrustedMealWeek2(db, startIso, endIso);

    case "critical_allergy_errors":
      // No dedicated event yet; report not_measurable until §5 allergy trust is built.
      return null;

    case "correction_rate":
      return correctionRate(db, startIso, endIso);

    case "sync_conflict_resolution_rate":
      return conflictResolutionRate(db, startIso, endIso);

    case "retention_d1":
      return cohortRetention(db, 1, startIso, endIso);

    case "retention_d7":
      return cohortRetention(db, 7, startIso, endIso);

    case "retention_d30":
      return cohortRetention(db, 30, startIso, endIso);

    case "retention_week4_household":
      return householdWeekRetention(db, 4, startIso, endIso);

    case "paid_month2_retention":
      // Requires subscription lifecycle data not yet collected in analytics events.
      return null;

    case "trial_to_paid_rate":
      return userConversion(db, "trial_started", "subscription_started", startIso, endIso);

    case "refund_rate":
      // Requires refund events from billing provider.
      return null;

    case "monthly_churn":
      // Requires subscription status history.
      return null;

    default:
      return null;
  }
}

// ---------------------------------------------------------------------------
// Metric helpers
// ---------------------------------------------------------------------------

async function userConversion(
  db: Database,
  fromEvent: string,
  toEvent: string,
  startIso: string,
  endIso: string,
): Promise<number | null> {
  const raw = await db.execute(sql`
    WITH base AS (
      SELECT DISTINCT user_id
      FROM product_analytics_events
      WHERE event_name = ${fromEvent}
        AND occurred_at >= ${startIso}
        AND occurred_at < ${endIso}
        AND user_id IS NOT NULL
    ),
    converted AS (
      SELECT DISTINCT b.user_id
      FROM base b
      INNER JOIN product_analytics_events e ON e.user_id = b.user_id
      WHERE e.event_name = ${toEvent}
        AND e.occurred_at >= ${startIso}
        AND e.occurred_at < ${endIso}
    )
    SELECT
      (SELECT count(*) FROM base) AS total,
      (SELECT count(*) FROM converted) AS converted
  `);
  const row = (Array.isArray(raw) ? raw[0] : raw.rows[0]) as {
    total: number;
    converted: number;
  } | undefined;
  if (!row || Number(row.total) === 0) return null;
  return Number(row.converted) / Number(row.total);
}

function inClause(values: string[]) {
  return sql`(${sql.join(values.map((v) => sql`${v}`), sql`, `)})`;
}

async function sameDayConversion(
  db: Database,
  toEvents: string[],
  startIso: string,
  endIso: string,
): Promise<number | null> {
  const raw = await db.execute(sql`
    WITH base AS (
      SELECT user_id, (occurred_at AT TIME ZONE 'UTC')::date AS cohort_date
      FROM product_analytics_events
      WHERE event_name = 'account_created'
        AND occurred_at >= ${startIso}
        AND occurred_at < ${endIso}
        AND user_id IS NOT NULL
    ),
    converted AS (
      SELECT DISTINCT b.user_id
      FROM base b
      INNER JOIN product_analytics_events e ON e.user_id = b.user_id
      WHERE e.event_name IN ${inClause(toEvents)}
        AND e.occurred_at >= ${startIso}
        AND e.occurred_at < ${endIso}
        AND (e.occurred_at AT TIME ZONE 'UTC')::date = b.cohort_date
    )
    SELECT
      (SELECT count(DISTINCT user_id) FROM base) AS total,
      (SELECT count(*) FROM converted) AS converted
  `);
  const row = (Array.isArray(raw) ? raw[0] : raw.rows[0]) as {
    total: number;
    converted: number;
  } | undefined;
  if (!row || Number(row.total) === 0) return null;
  return Number(row.converted) / Number(row.total);
}

async function repeatedEventRate(
  db: Database,
  events: string[],
  minOccurrences: number,
  startIso: string,
  endIso: string,
): Promise<number | null> {
  const raw = await db.execute(sql`
    WITH base AS (
      SELECT DISTINCT user_id
      FROM product_analytics_events
      WHERE event_name = 'account_created'
        AND occurred_at >= ${startIso}
        AND occurred_at < ${endIso}
        AND user_id IS NOT NULL
    ),
    event_counts AS (
      SELECT b.user_id, count(*) AS n
      FROM base b
      INNER JOIN product_analytics_events e ON e.user_id = b.user_id
      WHERE e.event_name IN ${inClause(events)}
        AND e.occurred_at >= ${startIso}
        AND e.occurred_at < ${endIso}
      GROUP BY b.user_id
      HAVING count(*) >= ${minOccurrences}
    )
    SELECT
      (SELECT count(*) FROM base) AS total,
      (SELECT count(*) FROM event_counts) AS converted
  `);
  const row = (Array.isArray(raw) ? raw[0] : raw.rows[0]) as {
    total: number;
    converted: number;
  } | undefined;
  if (!row || Number(row.total) === 0) return null;
  return Number(row.converted) / Number(row.total);
}

async function householdCollaborationRate(
  db: Database,
  startIso: string,
  endIso: string,
): Promise<number | null> {
  const raw = await db.execute(sql`
    WITH base AS (
      SELECT DISTINCT household_id
      FROM product_analytics_events
      WHERE event_name = 'household_created'
        AND occurred_at >= ${startIso}
        AND occurred_at < ${endIso}
        AND household_id IS NOT NULL
    ),
    collaborative AS (
      SELECT DISTINCT b.household_id
      FROM base b
      INNER JOIN product_analytics_events e ON e.household_id = b.household_id
      WHERE e.event_name IN ('household_invite_sent', 'household_invite_accepted', 'second_member_first_action')
        AND e.occurred_at >= ${startIso}
        AND e.occurred_at < ${endIso}
    )
    SELECT
      (SELECT count(*) FROM base) AS total,
      (SELECT count(*) FROM collaborative) AS converted
  `);
  const row = (Array.isArray(raw) ? raw[0] : raw.rows[0]) as {
    total: number;
    converted: number;
  } | undefined;
  if (!row || Number(row.total) === 0) return null;
  return Number(row.converted) / Number(row.total);
}

async function weeklyTrustedMealWeek2(
  db: Database,
  startIso: string,
  endIso: string,
): Promise<number | null> {
  const raw = await db.execute(sql`
    WITH activated AS (
      SELECT DISTINCT household_id,
             min(occurred_at) AS first_activated_at
      FROM product_analytics_events
      WHERE event_name = 'cooking_session_completed'
        AND household_id IS NOT NULL
        AND occurred_at >= ${startIso}
        AND occurred_at < ${endIso}
      GROUP BY household_id
    ),
    week2_cooks AS (
      SELECT DISTINCT a.household_id
      FROM activated a
      INNER JOIN product_analytics_events e
        ON e.household_id = a.household_id
       AND e.event_name = 'cooking_session_completed'
       AND e.occurred_at >= a.first_activated_at + INTERVAL '7 days'
       AND e.occurred_at < a.first_activated_at + INTERVAL '14 days'
    )
    SELECT
      (SELECT count(*) FROM activated) AS total,
      (SELECT count(*) FROM week2_cooks) AS converted
  `);
  const row = (Array.isArray(raw) ? raw[0] : raw.rows[0]) as {
    total: number;
    converted: number;
  } | undefined;
  if (!row || Number(row.total) === 0) return null;
  return Number(row.converted) / Number(row.total);
}

async function correctionRate(
  db: Database,
  startIso: string,
  endIso: string,
): Promise<number | null> {
  const raw = await db.execute(sql`
    SELECT
      count(*) FILTER (WHERE event_name = 'scan_item_corrected') AS corrected,
      count(*) FILTER (WHERE event_name IN ('scan_item_confirmed', 'scan_item_corrected')) AS total
    FROM product_analytics_events
    WHERE event_name IN ('scan_item_confirmed', 'scan_item_corrected')
      AND occurred_at >= ${startIso}
      AND occurred_at < ${endIso}
  `);
  const row = (Array.isArray(raw) ? raw[0] : raw.rows[0]) as {
    corrected: number;
    total: number;
  } | undefined;
  if (!row || Number(row.total) === 0) return null;
  return Number(row.corrected) / Number(row.total);
}

async function conflictResolutionRate(
  db: Database,
  startIso: string,
  endIso: string,
): Promise<number | null> {
  const raw = await db.execute(sql`
    SELECT
      count(*) FILTER (WHERE event_name = 'inventory_conflict_resolved') AS resolved,
      count(*) FILTER (WHERE event_name IN ('inventory_conflict_created', 'inventory_conflict_resolved')) AS total
    FROM product_analytics_events
    WHERE event_name IN ('inventory_conflict_created', 'inventory_conflict_resolved')
      AND occurred_at >= ${startIso}
      AND occurred_at < ${endIso}
  `);
  const row = (Array.isArray(raw) ? raw[0] : raw.rows[0]) as {
    resolved: number;
    total: number;
  } | undefined;
  if (!row || Number(row.total) === 0) return null;
  return Number(row.resolved) / Number(row.total);
}

async function cohortRetention(
  db: Database,
  day: number,
  startIso: string,
  endIso: string,
): Promise<number | null> {
  const raw = await db.execute(sql`
    WITH cohort AS (
      SELECT DISTINCT user_id, (occurred_at AT TIME ZONE 'UTC')::date AS cohort_date
      FROM product_analytics_events
      WHERE event_name = 'account_created'
        AND occurred_at >= ${startIso}
        AND occurred_at < ${endIso}
        AND user_id IS NOT NULL
    ),
    active AS (
      SELECT DISTINCT c.user_id
      FROM cohort c
      INNER JOIN product_analytics_events e ON e.user_id = c.user_id
      WHERE (e.occurred_at AT TIME ZONE 'UTC')::date = c.cohort_date + ${day}::integer
    )
    SELECT
      (SELECT count(DISTINCT user_id) FROM cohort) AS total,
      (SELECT count(*) FROM active) AS active
  `);
  const row = (Array.isArray(raw) ? raw[0] : raw.rows[0]) as {
    total: number;
    active: number;
  } | undefined;
  if (!row || Number(row.total) === 0) return null;
  return Number(row.active) / Number(row.total);
}

async function householdWeekRetention(
  db: Database,
  week: number,
  startIso: string,
  endIso: string,
): Promise<number | null> {
  const raw = await db.execute(sql`
    WITH cohort AS (
      SELECT DISTINCT household_id,
             (occurred_at AT TIME ZONE 'UTC')::date AS cohort_date
      FROM product_analytics_events
      WHERE event_name = 'household_created'
        AND occurred_at >= ${startIso}
        AND occurred_at < ${endIso}
        AND household_id IS NOT NULL
    ),
    active AS (
      SELECT DISTINCT c.household_id
      FROM cohort c
      INNER JOIN product_analytics_events e ON e.household_id = c.household_id
      WHERE (e.occurred_at AT TIME ZONE 'UTC')::date >= c.cohort_date + ${(week - 1) * 7}::integer
        AND (e.occurred_at AT TIME ZONE 'UTC')::date < c.cohort_date + ${week * 7}::integer
    )
    SELECT
      (SELECT count(DISTINCT household_id) FROM cohort) AS total,
      (SELECT count(*) FROM active) AS active
  `);
  const row = (Array.isArray(raw) ? raw[0] : raw.rows[0]) as {
    total: number;
    active: number;
  } | undefined;
  if (!row || Number(row.total) === 0) return null;
  return Number(row.active) / Number(row.total);
}

/**
 * Summarise the current go/no-go verdict across all gates.
 */
export function summarizeReleaseGates(gates: GateEvaluation[]): {
  overall: "go" | "no_go" | "pending";
  passed: number;
  failed: number;
  blocked: number;
  pending: number;
  notMeasurable: number;
} {
  let failed = 0;
  let blocked = 0;
  let pending = 0;
  let notMeasurable = 0;
  let passed = 0;
  for (const g of gates) {
    if (g.status === "passed") passed++;
    else if (g.status === "failed") {
      failed++;
      if (g.blocking) blocked++;
    } else if (g.status === "not_measurable") notMeasurable++;
    else pending++;
  }
  const overall = blocked > 0 ? "no_go" : failed > 0 ? "no_go" : pending > 0 ? "pending" : "go";
  return { overall, passed, failed, blocked, pending, notMeasurable };
}

export { RELEASE_GATE_STATUSES };
