import { getDb } from "../db.ts";
import { eq, and, gte, lte } from "drizzle-orm";
import { transactions, fraudAlerts, users } from "../../drizzle/schema.ts";

/**
 * Fraud Detection Service
 * Analyzes betting patterns and flags suspicious activity
 */

export interface FraudDetectionResult {
  isFraudulent: boolean;
  alerts: FraudAlert[];
  riskScore: number;
}

export interface FraudAlert {
  type: "abnormal_rtp" | "rapid_betting" | "multi_account" | "bonus_abuse" | "unusual_withdrawal" | "velocity" | "pattern";
  severity: "low" | "medium" | "high" | "critical";
  description: string;
  details: Record<string, unknown>;
}

/**
 * Configuration for fraud detection thresholds
 */
const FRAUD_CONFIG = {
  // Rapid betting: spins within short time window
  RAPID_BETTING_WINDOW_MINUTES: 5,
  RAPID_BETTING_THRESHOLD: 10, // 10+ spins in 5 minutes
  RAPID_BETTING_SEVERITY: "high" as const,

  // Velocity: high bet amounts in short time
  VELOCITY_WINDOW_MINUTES: 60,
  VELOCITY_THRESHOLD: 1000, // $1000+ in 1 hour
  VELOCITY_SEVERITY: "high" as const,

  // Abnormal RTP: unusual win rate
  ABNORMAL_RTP_THRESHOLD: 0.7, // 70%+ win rate is suspicious
  ABNORMAL_RTP_MIN_SPINS: 20, // Need at least 20 spins to calculate
  ABNORMAL_RTP_SEVERITY: "medium" as const,

  // Pattern detection: consecutive losses/wins
  PATTERN_THRESHOLD: 15, // 15+ consecutive spins of same outcome
  PATTERN_SEVERITY: "medium" as const,

  // Multi-account: same IP/device playing multiple accounts
  MULTI_ACCOUNT_SEVERITY: "critical" as const,

  // Bonus abuse: large wins immediately after bonus
  BONUS_ABUSE_WINDOW_MINUTES: 30,
  BONUS_ABUSE_THRESHOLD: 5, // 5x+ multiplier within 30 min of bonus
  BONUS_ABUSE_SEVERITY: "high" as const,
};

/**
 * Analyze user betting patterns for fraud indicators
 */
export async function detectFraud(userId: number, currentBetAmount: number): Promise<FraudDetectionResult> {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  const alerts: FraudAlert[] = [];
  let riskScore = 0;

  try {
    // Get user's recent transactions (last 24 hours)
    const now = new Date();
    const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);

    const recentTransactions = await db
      .select()
      .from(transactions)
      .where(
        and(
          eq(transactions.userId, userId),
          gte(transactions.createdAt, oneDayAgo.toISOString())
        )
      )
      .orderBy(transactions.createdAt);

    // Check for rapid betting pattern
    const rapidBettingAlert = checkRapidBetting(recentTransactions);
    if (rapidBettingAlert) {
      alerts.push(rapidBettingAlert);
      riskScore += 25;
    }

    // Check for velocity (high bet amounts)
    const velocityAlert = checkVelocity(recentTransactions, currentBetAmount);
    if (velocityAlert) {
      alerts.push(velocityAlert);
      riskScore += 30;
    }

    // Check for abnormal RTP
    const rtpAlert = checkAbnormalRTP(recentTransactions);
    if (rtpAlert) {
      alerts.push(rtpAlert);
      riskScore += 20;
    }

    // Check for pattern (consecutive wins/losses)
    const patternAlert = checkPattern(recentTransactions);
    if (patternAlert) {
      alerts.push(patternAlert);
      riskScore += 15;
    }

    // Check for bonus abuse
    const bonusAbuseAlert = checkBonusAbuse(recentTransactions);
    if (bonusAbuseAlert) {
      alerts.push(bonusAbuseAlert);
      riskScore += 35;
    }

    // Cap risk score at 100
    riskScore = Math.min(riskScore, 100);

    return {
      isFraudulent: riskScore >= 60,
      alerts,
      riskScore,
    };
  } catch (error) {
    console.error("[Fraud Detection] Error analyzing patterns:", error);
    // Don't block gameplay on detection errors, but log them
    return {
      isFraudulent: false,
      alerts: [],
      riskScore: 0,
    };
  }
}

/**
 * Check for rapid betting pattern (many spins in short time)
 */
function checkRapidBetting(transactions: any[]): FraudAlert | null {
  const now = new Date();
  const windowStart = new Date(now.getTime() - FRAUD_CONFIG.RAPID_BETTING_WINDOW_MINUTES * 60 * 1000);

  const recentBets = transactions.filter(
    (tx) =>
      (tx.type === "game_bet" || tx.type === "game_spin_bet") &&
      new Date(tx.createdAt) >= windowStart
  );

  if (recentBets.length >= FRAUD_CONFIG.RAPID_BETTING_THRESHOLD) {
    return {
      type: "rapid_betting",
      severity: FRAUD_CONFIG.RAPID_BETTING_SEVERITY,
      description: `Detected ${recentBets.length} spins in ${FRAUD_CONFIG.RAPID_BETTING_WINDOW_MINUTES} minutes (threshold: ${FRAUD_CONFIG.RAPID_BETTING_THRESHOLD})`,
      details: {
        spinsInWindow: recentBets.length,
        windowMinutes: FRAUD_CONFIG.RAPID_BETTING_WINDOW_MINUTES,
        threshold: FRAUD_CONFIG.RAPID_BETTING_THRESHOLD,
      },
    };
  }

  return null;
}

/**
 * Check for high velocity betting (large amounts in short time)
 */
function checkVelocity(transactions: any[], currentBetAmount: number): FraudAlert | null {
  const now = new Date();
  const windowStart = new Date(now.getTime() - FRAUD_CONFIG.VELOCITY_WINDOW_MINUTES * 60 * 1000);

  const recentBets = transactions.filter(
    (tx) =>
      (tx.type === "game_bet" || tx.type === "game_spin_bet") &&
      new Date(tx.createdAt) >= windowStart
  );

  const totalBetAmount = recentBets.reduce((sum, tx) => sum + parseFloat(tx.amount), 0) + currentBetAmount;

  if (totalBetAmount >= FRAUD_CONFIG.VELOCITY_THRESHOLD) {
    return {
      type: "velocity",
      severity: FRAUD_CONFIG.VELOCITY_SEVERITY,
      description: `High betting velocity: $${totalBetAmount.toFixed(2)} in ${FRAUD_CONFIG.VELOCITY_WINDOW_MINUTES} minutes (threshold: $${FRAUD_CONFIG.VELOCITY_THRESHOLD})`,
      details: {
        totalAmount: totalBetAmount,
        windowMinutes: FRAUD_CONFIG.VELOCITY_WINDOW_MINUTES,
        threshold: FRAUD_CONFIG.VELOCITY_THRESHOLD,
        spinsCount: recentBets.length,
      },
    };
  }

  return null;
}

/**
 * Check for abnormal RTP (win rate)
 */
function checkAbnormalRTP(transactions: any[]): FraudAlert | null {
  const bets = transactions.filter((tx) => tx.type === "game_bet" || tx.type === "game_spin_bet");
  const wins = transactions.filter((tx) => tx.type === "game_win" || tx.type === "game_spin_win");

  if (bets.length < FRAUD_CONFIG.ABNORMAL_RTP_MIN_SPINS) {
    return null;
  }

  const totalBet = bets.reduce((sum, tx) => sum + parseFloat(tx.amount), 0);
  const totalWin = wins.reduce((sum, tx) => sum + parseFloat(tx.amount), 0);
  const rtp = totalBet > 0 ? totalWin / totalBet : 0;

  if (rtp > FRAUD_CONFIG.ABNORMAL_RTP_THRESHOLD) {
    return {
      type: "abnormal_rtp",
      severity: FRAUD_CONFIG.ABNORMAL_RTP_SEVERITY,
      description: `Abnormal RTP detected: ${(rtp * 100).toFixed(1)}% (threshold: ${FRAUD_CONFIG.ABNORMAL_RTP_THRESHOLD * 100}%)`,
      details: {
        rtp: rtp.toFixed(4),
        totalBet: totalBet.toFixed(2),
        totalWin: totalWin.toFixed(2),
        spinsCount: bets.length,
      },
    };
  }

  return null;
}

/**
 * Check for pattern (consecutive wins/losses)
 */
function checkPattern(transactions: any[]): FraudAlert | null {
  const outcomes = transactions
    .filter((tx) => tx.type === "game_bet" || tx.type === "game_win" || tx.type === "game_spin_bet" || tx.type === "game_spin_win")
    .sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())
    .map((tx) => (tx.type === "game_win" || tx.type === "game_spin_win" ? "win" : "loss"));

  let maxConsecutive = 1;
  let currentConsecutive = 1;
  let lastOutcome = outcomes[0];

  for (let i = 1; i < outcomes.length; i++) {
    if (outcomes[i] === lastOutcome) {
      currentConsecutive++;
      maxConsecutive = Math.max(maxConsecutive, currentConsecutive);
    } else {
      currentConsecutive = 1;
      lastOutcome = outcomes[i];
    }
  }

  if (maxConsecutive >= FRAUD_CONFIG.PATTERN_THRESHOLD) {
    return {
      type: "pattern",
      severity: FRAUD_CONFIG.PATTERN_SEVERITY,
      description: `Detected ${maxConsecutive} consecutive ${lastOutcome}s (threshold: ${FRAUD_CONFIG.PATTERN_THRESHOLD})`,
      details: {
        consecutiveSpins: maxConsecutive,
        outcome: lastOutcome,
        threshold: FRAUD_CONFIG.PATTERN_THRESHOLD,
      },
    };
  }

  return null;
}

/**
 * Check for bonus abuse (large wins after bonus)
 */
function checkBonusAbuse(transactions: any[]): FraudAlert | null {
  const now = new Date();
  const windowStart = new Date(now.getTime() - FRAUD_CONFIG.BONUS_ABUSE_WINDOW_MINUTES * 60 * 1000);

  // Find bonus transactions
  const bonusTransactions = transactions.filter(
    (tx) =>
      (tx.type === "sc_bonus" || tx.type === "gc_bonus" || tx.type === "daily_bonus") &&
      new Date(tx.createdAt) >= windowStart
  );

  if (bonusTransactions.length === 0) {
    return null;
  }

  // Find large wins after bonus
  const lastBonusTime = new Date(bonusTransactions[bonusTransactions.length - 1].createdAt);
  const wins = transactions.filter(
    (tx) =>
      (tx.type === "game_win" || tx.type === "game_spin_win") &&
      new Date(tx.createdAt) >= lastBonusTime
  );

  for (const win of wins) {
    const correspondingBet = transactions.find(
      (tx) =>
        (tx.type === "game_bet" || tx.type === "game_spin_bet") &&
        new Date(tx.createdAt) <= new Date(win.createdAt) &&
        new Date(tx.createdAt) >= new Date(win.createdAt.getTime() - 5000) // Within 5 seconds
    );

    if (correspondingBet) {
      const multiplier = parseFloat(win.amount) / parseFloat(correspondingBet.amount);
      if (multiplier >= FRAUD_CONFIG.BONUS_ABUSE_THRESHOLD) {
        return {
          type: "bonus_abuse",
          severity: FRAUD_CONFIG.BONUS_ABUSE_SEVERITY,
          description: `Bonus abuse detected: ${multiplier.toFixed(1)}x win within ${FRAUD_CONFIG.BONUS_ABUSE_WINDOW_MINUTES} minutes of bonus (threshold: ${FRAUD_CONFIG.BONUS_ABUSE_THRESHOLD}x)`,
          details: {
            multiplier: multiplier.toFixed(2),
            winAmount: win.amount,
            betAmount: correspondingBet.amount,
            threshold: FRAUD_CONFIG.BONUS_ABUSE_THRESHOLD,
          },
        };
      }
    }
  }

  return null;
}

/**
 * Create fraud alert in database
 */
export async function createFraudAlert(
  userId: number,
  alertType: FraudAlert["type"],
  severity: FraudAlert["severity"],
  description: string,
  details: Record<string, unknown>
) {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  try {
    const result = await db.insert(fraudAlerts).values({
      userId,
      alertType,
      severity,
      description,
      details: JSON.stringify(details),
      status: "open",
    });

    // Get the alert ID
    const alerts = await db
      .select()
      .from(fraudAlerts)
      .where((table) => `${table.userId} = ${userId} AND ${table.alertType} = '${alertType}'`)
      .orderBy((table) => `${table.createdAt} DESC`)
      .limit(1);

    if (alerts[0]) {
      // Trigger automated actions for critical/high severity alerts
      if (severity === "critical" || severity === "high") {
        try {
          const { executeAutomatedActions } = await import("./fraudActions");
          await executeAutomatedActions(userId, alerts[0].id, severity, alertType);
        } catch (actionError) {
          console.error("[Fraud Alert] Failed to execute automated actions:", actionError);
        }
      }
    }
  } catch (error) {
    console.error("[Fraud Alert] Failed to create alert:", error);
  }
}

/**
 * Get fraud alerts for admin dashboard
 */
export async function getFraudAlerts(
  limit = 50,
  offset = 0,
  status?: "open" | "investigating" | "resolved" | "dismissed",
  severity?: "low" | "medium" | "high" | "critical"
) {
  const db = await getDb();
  if (!db) return [];

  const conditions = [];
  if (status) conditions.push(eq(fraudAlerts.status, status));
  if (severity) conditions.push(eq(fraudAlerts.severity, severity));

  let query = db.select().from(fraudAlerts);

  if (conditions.length > 0) {
    query = query.where(and(...conditions)) as any;
  }

  return query
    .orderBy(fraudAlerts.createdAt)
    .limit(limit)
    .offset(offset);
}

/**
 * Update fraud alert status
 */
export async function updateFraudAlertStatus(
  alertId: number,
  status: "open" | "investigating" | "resolved" | "dismissed",
  resolvedBy?: number
) {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  const updateData: any = {
    status,
  };

  if (resolvedBy) {
    updateData.resolvedBy = resolvedBy;
    updateData.resolvedAt = new Date().toISOString();
  }

  await db.update(fraudAlerts).set(updateData).where(eq(fraudAlerts.id, alertId));
}
