import { NextRequest, NextResponse } from 'next/server';
// `NextResponse.json` for the success path; the `jsonError` helper
// returns a plain `Response`, so the function's return type is the
// shared supertype (`Response`).
import { getUserFromRequest, jsonError } from '@/lib/auth-helpers';
import { getServiceRoleSupabase } from '@/lib/supabase';
import { isAdmin } from '@/lib/isAdmin';

export const dynamic = 'force-dynamic';

const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:3000';
const HEALTH_PROBE_TIMEOUT_MS = 4000;

/**
 * Admin stats endpoint (Session 18). The security boundary for the
 * /admin dashboard.
 *
 * Flow:
 *   1. Verify the bearer token via Supabase (getUserFromRequest)
 *   2. Verify the user's email is in the admin allowlist (isAdmin)
 *   3. Service-role Supabase queries for aggregates
 *   4. Best-effort probes of the Express odds endpoints for health
 *   5. Return consolidated JSON
 *
 * No data is returned — including count totals — to non-admin
 * callers. A non-admin who somehow learns this URL gets a 403, not
 * "empty stats" or "redirect to dashboard" (those leak the route's
 * existence).
 *
 * Email masking: recent-signup rows are returned with the local
 * part collapsed to `<first letter>***`. The full email is never
 * exposed via this endpoint — even to admins. Operators who need
 * the actual email can query Supabase directly.
 */

interface AdminStats {
  generated_at: string;
  users: {
    total: number;
    by_tier: Record<string, number>;
    recent_24h: Array<{ email_masked: string; tier: string; created_at: string }>;
  };
  grades: {
    total: number;
    today: number;
  };
  health: {
    sports: Array<{ sport: string; status: 'ok' | 'error' | 'empty'; quota?: number | null; props?: number; error?: string }>;
    odds_quota_remaining: number | null;
  };
  // Session 20 — per-provider quota snapshot. Pulled through the
  // internal /quota endpoint so the admin page sees the same view
  // the gateway makes routing decisions against.
  provider_quotas: Array<{
    provider: string;
    name?: string;
    used: number;
    limit: number;
    remaining: number;
    pct: number;
    period: string;
    quotaType: string;
    allowed: boolean;
    degraded?: boolean;
  }>;
  notes: string[];
}

function maskEmail(raw: string | null | undefined): string {
  if (!raw) return '***@***';
  const at = raw.indexOf('@');
  if (at < 0) return '***';
  const local = raw.slice(0, at);
  const domain = raw.slice(at + 1);
  const first = local.charAt(0) || '*';
  return `${first}***@${domain}`;
}

async function probeSport(sport: string, signal: AbortSignal): Promise<{
  sport: string;
  status: 'ok' | 'error' | 'empty';
  quota?: number | null;
  props?: number;
  error?: string;
}> {
  try {
    const res = await fetch(`${BACKEND_URL}/api/odds/${sport}`, {
      signal,
      headers: { Accept: 'application/json' },
      cache: 'no-store',
    });
    if (!res.ok) {
      const body = (await res.json().catch(() => ({}))) as { error?: string };
      return { sport, status: 'error', error: body.error || `HTTP ${res.status}` };
    }
    const body = (await res.json().catch(() => ({}))) as {
      props?: unknown[];
      quota_remaining?: number;
    };
    const propCount = Array.isArray(body.props) ? body.props.length : 0;
    return {
      sport,
      status: propCount > 0 ? 'ok' : 'empty',
      quota: typeof body.quota_remaining === 'number' ? body.quota_remaining : null,
      props: propCount,
    };
  } catch (err) {
    return { sport, status: 'error', error: err instanceof Error ? err.message : 'unknown' };
  }
}

const TODAY_START = (): string => {
  const d = new Date();
  d.setUTCHours(0, 0, 0, 0);
  return d.toISOString();
};

const HOURS_24_AGO = (): string => new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();

export async function GET(req: NextRequest): Promise<Response> {
  const user = await getUserFromRequest(req);
  if (!user) return jsonError(401, 'Authentication required');
  if (!isAdmin(user.email)) {
    // Forbidden — non-admins should not learn whether the route exists.
    return jsonError(403, 'Forbidden');
  }

  const sb = getServiceRoleSupabase();
  if (!sb) return jsonError(503, 'Service role not configured');

  const notes: string[] = [];

  // Aggregate Supabase reads. Each is wrapped so a single failed
  // query doesn't blank the entire dashboard.
  const [
    totalUsersResult,
    tierRowsResult,
    recentSignupsResult,
    totalGradesResult,
    gradesTodayResult,
  ] = await Promise.allSettled([
    sb.from('users').select('id', { count: 'exact', head: true }),
    sb.from('users').select('tier'),
    sb.from('users').select('id, email, tier, created_at')
      .gte('created_at', HOURS_24_AGO())
      .order('created_at', { ascending: false })
      .limit(20),
    sb.from('grade_history').select('id', { count: 'exact', head: true }),
    sb.from('grade_history').select('id', { count: 'exact', head: true })
      .gte('created_at', TODAY_START()),
  ]);

  const totalUsers = totalUsersResult.status === 'fulfilled'
    ? (totalUsersResult.value.count ?? 0)
    : (notes.push('users count query failed'), 0);

  const byTier: Record<string, number> = { free: 0, africa: 0, analyst: 0, desk: 0 };
  if (tierRowsResult.status === 'fulfilled' && Array.isArray(tierRowsResult.value.data)) {
    for (const row of tierRowsResult.value.data) {
      const t = String(row.tier || 'free').toLowerCase();
      byTier[t] = (byTier[t] || 0) + 1;
    }
  } else {
    notes.push('tier breakdown query failed');
  }

  const recent24h: AdminStats['users']['recent_24h'] =
    recentSignupsResult.status === 'fulfilled' && Array.isArray(recentSignupsResult.value.data)
      ? recentSignupsResult.value.data.map((row: { email?: string; tier?: string; created_at?: string }) => ({
          email_masked: maskEmail(row.email),
          tier: row.tier || 'free',
          created_at: row.created_at || '',
        }))
      : (notes.push('recent signups query failed'), []);

  const totalGrades = totalGradesResult.status === 'fulfilled'
    ? (totalGradesResult.value.count ?? 0)
    : (notes.push('grade_history total count failed'), 0);

  const gradesToday = gradesTodayResult.status === 'fulfilled'
    ? (gradesTodayResult.value.count ?? 0)
    : (notes.push('grade_history today count failed'), 0);

  // Health probes — fire all four sports in parallel with a shared
  // 4s budget so the dashboard doesn't block on a slow upstream.
  const controller = new AbortController();
  const probeTimer = setTimeout(() => controller.abort(), HEALTH_PROBE_TIMEOUT_MS);
  let sports: AdminStats['health']['sports'];
  try {
    sports = await Promise.all([
      probeSport('nba',         controller.signal),
      probeSport('wnba',        controller.signal),
      probeSport('mlb',         controller.signal),
      probeSport('soccer/wc',   controller.signal),
    ]);
  } finally {
    clearTimeout(probeTimer);
  }

  // The first sport that reports a quota wins — odds-api returns the
  // same quota number for every sport since they share the account.
  const oddsQuotaRemaining = sports.map((s) => s.quota).find((q) => typeof q === 'number') ?? null;

  // Session 20 — fetch the per-provider quota snapshot from the
  // backend's internal endpoint. Best-effort: a failure to reach
  // the backend or a missing internal key leaves provider_quotas
  // empty and surfaces a note instead of blanking the dashboard.
  const internalKey = process.env.VYNDR_INTERNAL_KEY || '';
  let providerQuotas: AdminStats['provider_quotas'] = [];
  if (internalKey) {
    try {
      const quotaController = new AbortController();
      const quotaTimer = setTimeout(() => quotaController.abort(), HEALTH_PROBE_TIMEOUT_MS);
      try {
        const quotaRes = await fetch(`${BACKEND_URL}/api/internal/quota`, {
          signal: quotaController.signal,
          headers: { 'x-internal-key': internalKey, Accept: 'application/json' },
          cache: 'no-store',
        });
        if (quotaRes.ok) {
          const quotaBody = (await quotaRes.json().catch(() => null)) as { providers?: AdminStats['provider_quotas'] } | null;
          if (quotaBody && Array.isArray(quotaBody.providers)) {
            providerQuotas = quotaBody.providers;
          }
        } else {
          notes.push(`quota fetch returned ${quotaRes.status}`);
        }
      } finally {
        clearTimeout(quotaTimer);
      }
    } catch (err) {
      notes.push(`quota fetch failed: ${err instanceof Error ? err.message : 'unknown'}`);
    }
  } else {
    notes.push('VYNDR_INTERNAL_KEY unset — provider quotas hidden');
  }

  const payload: AdminStats = {
    generated_at: new Date().toISOString(),
    users: {
      total: totalUsers,
      by_tier: byTier,
      recent_24h: recent24h,
    },
    grades: {
      total: totalGrades,
      today: gradesToday,
    },
    health: {
      sports,
      odds_quota_remaining: oddsQuotaRemaining,
    },
    provider_quotas: providerQuotas,
    notes,
  };

  return NextResponse.json(payload, {
    headers: { 'Cache-Control': 'private, no-store' },
  });
}
