import { getPrisma } from './prisma.service';
import { ensureRedisConnected } from './redis.service';
import providerLimitsConfig from '../config/provider-limits.json';

/**
 * Dashboard data aggregation service.
 *
 * Assembles provider status from:
 *   - Redis: provider:health:{name} and provider:limits:{name} (written by worker cron)
 *   - PG: execution_ledger aggregate (calls_24h, avg_latency) + tools GROUP BY provider
 *
 * Cached in Redis for 60s to avoid repeated PG queries.
 */

const DASHBOARD_CACHE_KEY = 'dashboard:data';
const DASHBOARD_CACHE_TTL = 60;

interface ProviderHealth {
  status: 'green' | 'orange' | 'red' | 'unknown';
  latency_ms: number | null;
  last_check: string | null;
}

interface ProviderLimits {
  type: string;
  free_limit: number;
  used: number;
  remaining: number;
  pct_remaining: number;
  status: 'green' | 'orange' | 'yellow' | 'red' | 'paid';
}

interface ProviderBalance {
  balance_usd: number | null;
  currency: string;
  last_check: string | null;
}

interface ProviderDashboardEntry {
  provider: string;
  display_name: string;
  health: ProviderHealth;
  limits: ProviderLimits;
  balance: ProviderBalance | null;
  usage: {
    calls_today: number;
    calls_this_month: number;
    calls_total: number;
  };
  calls_24h: number;
  avg_latency_ms: number | null;
  tool_count: number;
}

interface PaymentSystemStatus {
  mode_a: { status: 'green'; description: string };
  mode_b: {
    status: 'green' | 'orange' | 'red' | 'unknown';
    latency_ms: number | null;
    facilitator: string;
    network: string;
    testnet: boolean;
    last_check: string | null;
  };
}

interface DashboardResponse {
  generated_at: string;
  providers: ProviderDashboardEntry[];
  totals: {
    providers: number;
    tools: number;
    calls_24h: number;
  };
  payment_system: PaymentSystemStatus;
}

const limitsConfig = providerLimitsConfig as Record<string, {
  display_name: string;
  health_url: string;
  limit_type: string;
  free_limit: number;
  reset_period: string;
  paid_balance?: boolean;
  balance_api?: boolean;
  docs_url?: string;
  limit_proof?: string;
}>;

export async function getDashboardData(): Promise<DashboardResponse> {
  try {
    const redis = await ensureRedisConnected();
    const cached = await redis.get(DASHBOARD_CACHE_KEY);
    if (cached) {
      return JSON.parse(cached) as DashboardResponse;
    }
  } catch {
    // Redis unavailable — fall through to PG query
  }

  const prisma = getPrisma();

  // Single aggregate query: tools per provider + 24h + period-aware call stats
  const providerStats: Array<{
    provider: string;
    tool_count: bigint;
    calls_24h: bigint;
    calls_today: bigint;
    calls_this_month: bigint;
    calls_total: bigint;
    paid_calls_24h: bigint;
    revenue_24h: number | null;
    avg_latency_ms: number | null;
  }> = await prisma.$queryRawUnsafe(`
    SELECT
      t.provider,
      COUNT(DISTINCT t.tool_id) AS tool_count,
      COUNT(el.execution_id) FILTER (WHERE el.created_at >= NOW() - INTERVAL '24 hours') AS calls_24h,
      COUNT(el.execution_id) FILTER (WHERE el.created_at >= date_trunc('day', NOW())) AS calls_today,
      COUNT(el.execution_id) FILTER (WHERE el.created_at >= date_trunc('month', NOW())) AS calls_this_month,
      COUNT(el.execution_id) AS calls_total,
      COUNT(el.execution_id) FILTER (WHERE el.billing_status = 'PAID' AND el.created_at >= NOW() - INTERVAL '24 hours') AS paid_calls_24h,
      SUM(el.cost_usd) FILTER (WHERE el.created_at >= NOW() - INTERVAL '24 hours')::numeric AS revenue_24h,
      ROUND(AVG(el.latency_ms) FILTER (WHERE el.created_at >= NOW() - INTERVAL '24 hours'))::integer AS avg_latency_ms
    FROM tools t
    LEFT JOIN execution_ledger el
      ON el.tool_id = t.tool_id
      AND el.status IN ('success', 'shared_success', 'provider_success')
    WHERE t.status != 'unavailable'
    GROUP BY t.provider
    ORDER BY t.provider
  `);

  // Read health and limits from Redis
  let redis: Awaited<ReturnType<typeof ensureRedisConnected>> | null = null;
  try {
    redis = await ensureRedisConnected();
  } catch {
    // Redis unavailable
  }

  const providers: ProviderDashboardEntry[] = [];
  let totalTools = 0;
  let totalCalls = 0;

  for (const row of providerStats) {
    const providerName = row.provider;
    const cfg = limitsConfig[providerName];
    const displayName = cfg?.display_name || providerName;
    const toolCount = Number(row.tool_count);
    const calls24h = Number(row.calls_24h);
    const callsToday = Number(row.calls_today);
    const callsThisMonth = Number(row.calls_this_month);
    const callsTotal = Number(row.calls_total);
    const avgLatency = row.avg_latency_ms;

    totalTools += toolCount;
    totalCalls += calls24h;

    // Read health from Redis
    let health: ProviderHealth = { status: 'unknown', latency_ms: null, last_check: null };
    if (redis) {
      try {
        const healthData = await redis.hgetall(`provider:health:${providerName}`);
        if (healthData && healthData.status) {
          health = {
            status: healthData.status as ProviderHealth['status'],
            latency_ms: healthData.latency_ms ? parseInt(healthData.latency_ms, 10) : null,
            last_check: healthData.last_check || null,
          };
        }
      } catch {
        // skip
      }
    }

    // Read limits from Redis or compute from config
    let limits: ProviderLimits;
    if (redis) {
      try {
        const limitsData = await redis.hgetall(`provider:limits:${providerName}`);
        if (limitsData && limitsData.type) {
          limits = {
            type: limitsData.type,
            free_limit: parseInt(limitsData.free_limit || '0', 10),
            used: parseInt(limitsData.used || '0', 10),
            remaining: parseInt(limitsData.remaining || '0', 10),
            pct_remaining: parseInt(limitsData.pct_remaining || '100', 10),
            status: limitsData.limit_status as ProviderLimits['status'] || 'green',
          };
        } else {
          limits = buildDefaultLimits(providerName, { today: callsToday, month: callsThisMonth, total: callsTotal });
        }
      } catch {
        limits = buildDefaultLimits(providerName, { today: callsToday, month: callsThisMonth, total: callsTotal });
      }
    } else {
      limits = buildDefaultLimits(providerName, { today: callsToday, month: callsThisMonth, total: callsTotal });
    }

    // Fetch real balance for paid providers (cached in Redis 5min)
    let balance: ProviderBalance | null = null;
    if (cfg?.paid_balance) {
      balance = await fetchProviderBalance(providerName, redis);
    }

    providers.push({
      provider: providerName,
      display_name: displayName,
      health,
      limits,
      balance,
      usage: {
        calls_today: callsToday,
        calls_this_month: callsThisMonth,
        calls_total: callsTotal,
      },
      calls_24h: calls24h,
      avg_latency_ms: avgLatency,
      tool_count: toolCount,
    });
  }

  // Read x402 health from Redis (written by x402-health.job.ts hourly)
  let paymentSystem: PaymentSystemStatus = {
    mode_a: { status: 'green', description: 'Pre-funded balance (PG)' },
    mode_b: {
      status: 'unknown',
      latency_ms: null,
      facilitator: '',
      network: '',
      testnet: false,
      last_check: null,
    },
  };
  if (redis) {
    try {
      const x402Data = await redis.hgetall('x402:health');
      if (x402Data && x402Data.status) {
        paymentSystem.mode_b = {
          status: x402Data.status as PaymentSystemStatus['mode_b']['status'],
          latency_ms: x402Data.latency_ms ? parseInt(x402Data.latency_ms, 10) : null,
          facilitator: x402Data.facilitator_url || '',
          network: x402Data.network || '',
          testnet: x402Data.testnet === 'true',
          last_check: x402Data.last_check || null,
        };
      }
    } catch {
      // non-fatal
    }
  }

  const response: DashboardResponse = {
    generated_at: new Date().toISOString(),
    providers,
    totals: {
      providers: providers.length,
      tools: totalTools,
      calls_24h: totalCalls,
    },
    payment_system: paymentSystem,
  };

  // Cache in Redis
  try {
    if (redis) {
      await redis.set(DASHBOARD_CACHE_KEY, JSON.stringify(response), 'EX', DASHBOARD_CACHE_TTL);
    }
  } catch {
    // non-fatal
  }

  return response;
}

function buildDefaultLimits(
  providerName: string,
  counts: { today: number; month: number; total: number },
): ProviderLimits {
  const cfg = limitsConfig[providerName];
  if (!cfg || cfg.limit_type === 'unlimited') {
    return {
      type: 'unlimited',
      free_limit: 0,
      used: counts.total,
      remaining: 0,
      pct_remaining: 100,
      status: 'green',
    };
  }

  // Paid providers with no free tier
  if (cfg.paid_balance && cfg.free_limit === 0) {
    return {
      type: cfg.limit_type,
      free_limit: 0,
      used: counts.total,
      remaining: 0,
      pct_remaining: 0,
      status: 'paid',
    };
  }

  // Pick the right counter based on reset period
  const freeLimit = cfg.free_limit;
  let used: number;
  switch (cfg.reset_period) {
    case 'daily':
      used = counts.today;
      break;
    case 'monthly':
      used = counts.month;
      break;
    case 'hourly':
      used = counts.today; // approximate — hourly is too granular for dashboard
      break;
    default:
      // 'none' (credits) or unknown — use total
      used = counts.total;
  }

  const remaining = Math.max(0, freeLimit - used);
  const pctRemaining = freeLimit > 0 ? Math.round((remaining / freeLimit) * 100) : 100;

  return {
    type: cfg.limit_type,
    free_limit: freeLimit,
    used,
    remaining,
    pct_remaining: pctRemaining,
    status: computeLimitStatus(pctRemaining),
  };
}

function computeLimitStatus(pctRemaining: number): ProviderLimits['status'] {
  if (pctRemaining <= 0) return 'red';
  if (pctRemaining < 25) return 'yellow';
  if (pctRemaining < 50) return 'orange';
  return 'green';
}

// ---------------------------------------------------------------------------
// Real-time balance fetching for paid providers
// ---------------------------------------------------------------------------

const BALANCE_CACHE_TTL = 300; // 5 minutes

type RedisClient = Awaited<ReturnType<typeof ensureRedisConnected>>;

async function fetchProviderBalance(
  providerName: string,
  redis: RedisClient | null,
): Promise<ProviderBalance | null> {
  const cacheKey = `provider:balance:${providerName}`;

  // Check Redis cache first
  if (redis) {
    try {
      const cached = await redis.get(cacheKey);
      if (cached) return JSON.parse(cached);
    } catch { /* non-fatal */ }
  }

  // Fetch real balance from upstream
  let balance: ProviderBalance | null = null;
  try {
    balance = await fetchBalanceUpstream(providerName);
  } catch { /* non-fatal — return null */ }

  // Cache result
  if (balance && redis) {
    try {
      await redis.set(cacheKey, JSON.stringify(balance), 'EX', BALANCE_CACHE_TTL);
    } catch { /* non-fatal */ }
  }

  return balance;
}

async function fetchBalanceUpstream(providerName: string): Promise<ProviderBalance | null> {
  const cfg = limitsConfig[providerName as keyof typeof limitsConfig] as Record<string, unknown> | undefined;
  if (!cfg) return null;

  switch (providerName) {
    case 'namesilo': {
      const key = process.env.PROVIDER_KEY_NAMESILO;
      if (!key) return null;
      const res = await fetch(`https://www.namesilo.com/api/getAccountBalance?version=1&type=json&key=${key}`, { signal: AbortSignal.timeout(5000) });
      const data = (await res.json()) as { reply: { balance: string } };
      return { balance_usd: parseFloat(data.reply.balance), currency: 'USD', last_check: new Date().toISOString() };
    }
    case 'zerobounce': {
      const key = process.env.PROVIDER_KEY_ZEROBOUNCE;
      if (!key) return null;
      const res = await fetch(`https://api.zerobounce.net/v2/getcredits?api_key=${key}`, { signal: AbortSignal.timeout(5000) });
      const data = (await res.json()) as { Credits: string };
      return { balance_usd: parseInt(data.Credits, 10), currency: 'credits', last_check: new Date().toISOString() };
    }
    default:
      return null;
  }
}
