import { getDb } from "../db.ts";
import { auditLogs, users } from "../../drizzle/schema.ts";
import { eq, and } from "drizzle-orm";
import type {
  DeviceSignature,
  MultiAccountFraudIndicators,
} from "./deviceFingerprinting.ts";
import {
  detectMultiAccountFraud,
  getUserFingerprints,
  generateFingerprint,
} from "./deviceFingerprinting.ts";
import { getIPGeolocation, isVPNOrProxy, getLocationRiskScore } from "./ipGeolocation.ts";
import { createFraudAlert } from "./fraudDetection.ts";

/**
 * Multi-Account Detection Engine
 * Identifies and flags coordinated fraud across multiple accounts
 */

export interface AccountCluster {
  accountIds: number[];
  sharedFingerprints: string[];
  sharedIPs: string[];
  commonPatterns: {
    similarGamePreferences: boolean;
    similarBettingPatterns: boolean;
    synchronizedActivity: boolean;
    coordinatedBonusAbuse: boolean;
  };
  riskScore: number;
  severity: "low" | "medium" | "high" | "critical";
}

export interface MultiAccountFraudReport {
  primaryUserId: number;
  linkedAccounts: number[];
  detectionMethod: string[];
  riskScore: number;
  severity: "low" | "medium" | "high" | "critical";
  indicators: MultiAccountFraudIndicators;
  relatedClusters: AccountCluster[];
  timestamp: string;
}

/**
 * Analyze user for multi-account fraud
 */
export async function analyzeUserForMultiAccountFraud(
  userId: number,
  fingerprint: string,
  ipAddress: string,
  deviceSignature: Partial<DeviceSignature>
): Promise<MultiAccountFraudReport> {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  // Get user fingerprints
  const userFingerprints = await getUserFingerprints(userId);

  // Get geolocation
  const location = await getIPGeolocation(ipAddress);

  // Detect multi-account fraud indicators
  const indicators = await detectMultiAccountFraud(
    userId,
    fingerprint,
    ipAddress,
    location,
    userFingerprints
  );

  // Find related account clusters
  const relatedClusters = await findRelatedAccountClusters(userId, fingerprint, ipAddress);

  // Calculate overall risk score
  let riskScore = indicators.riskScore;

  // Add location risk
  riskScore += getLocationRiskScore(location) * 0.3;

  // Add cluster risk
  for (const cluster of relatedClusters) {
    riskScore += cluster.riskScore * 0.2;
  }

  riskScore = Math.min(riskScore, 100);

  // Determine severity
  let severity: "low" | "medium" | "high" | "critical" = "low";
  if (riskScore >= 80) severity = "critical";
  else if (riskScore >= 60) severity = "high";
  else if (riskScore >= 40) severity = "medium";

  const detectionMethods: string[] = [];
  if (indicators.sharedFingerprint) detectionMethods.push("shared_fingerprint");
  if (indicators.sharedIP) detectionMethods.push("shared_ip");
  if (indicators.impossibleTravel) detectionMethods.push("impossible_travel");
  if (indicators.suspiciousPattern) detectionMethods.push("suspicious_pattern");

  const report: MultiAccountFraudReport = {
    primaryUserId: userId,
    linkedAccounts: [
      ...(indicators.details.sharedFingerprintUsers || []),
      ...(indicators.details.sharedIPUsers || []),
    ],
    detectionMethod: detectionMethods,
    riskScore,
    severity,
    indicators,
    relatedClusters,
    timestamp: new Date().toISOString(),
  };

  // Create fraud alert if high risk
  if (severity === "high" || severity === "critical") {
    await createFraudAlert(
      userId,
      "multi_account",
      severity,
      `Multi-account fraud detected: ${detectionMethods.join(", ")}`,
      {
        linkedAccounts: report.linkedAccounts,
        detectionMethods,
        riskScore,
        indicators,
      }
    );
  }

  return report;
}

/**
 * Find related account clusters
 */
export async function findRelatedAccountClusters(
  userId: number,
  fingerprint: string,
  ipAddress: string
): Promise<AccountCluster[]> {
  const db = await getDb();
  if (!db) return [];

  const clusters: AccountCluster[] = [];

  try {
    // Get all audit logs
    const allLogs = await db.select().from(auditLogs);

    // Build fingerprint map
    const fingerprintMap = new Map<string, Set<number>>();
    const ipMap = new Map<string, Set<number>>();

    for (const log of allLogs) {
      const fp = log.details?.fingerprint;
      const ip = log.ipAddress;
      const uid = log.actorId;

      if (fp && uid) {
        if (!fingerprintMap.has(fp)) {
          fingerprintMap.set(fp, new Set());
        }
        fingerprintMap.get(fp)!.add(uid);
      }

      if (ip && uid) {
        if (!ipMap.has(ip)) {
          ipMap.set(ip, new Set());
        }
        ipMap.get(ip)!.add(uid);
      }
    }

    // Find clusters sharing fingerprint
    if (fingerprintMap.has(fingerprint)) {
      const accountIds = Array.from(fingerprintMap.get(fingerprint)!);
      if (accountIds.length > 1) {
        const cluster = await buildAccountCluster(accountIds, "fingerprint");
        clusters.push(cluster);
      }
    }

    // Find clusters sharing IP
    if (ipMap.has(ipAddress)) {
      const accountIds = Array.from(ipMap.get(ipAddress)!);
      if (accountIds.length > 1) {
        const cluster = await buildAccountCluster(accountIds, "ip");
        clusters.push(cluster);
      }
    }

    return clusters;
  } catch (error) {
    console.error("[Multi-Account Detection] Failed to find clusters:", error);
    return [];
  }
}

/**
 * Build account cluster with analysis
 */
async function buildAccountCluster(
  accountIds: number[],
  clusterType: "fingerprint" | "ip"
): Promise<AccountCluster> {
  const db = await getDb();
  if (!db)
    return {
      accountIds: [],
      sharedFingerprints: [],
      sharedIPs: [],
      commonPatterns: {
        similarGamePreferences: false,
        similarBettingPatterns: false,
        synchronizedActivity: false,
        coordinatedBonusAbuse: false,
      },
      riskScore: 0,
      severity: "low",
    };

  let riskScore = 0;
  const sharedFingerprints: string[] = [];
  const sharedIPs: string[] = [];

  // Collect fingerprints and IPs
  for (const accountId of accountIds) {
    const logs = await db
      .select()
      .from(auditLogs)
      .where(eq(auditLogs.actorId, accountId));

    for (const log of logs) {
      if (log.details?.fingerprint && !sharedFingerprints.includes(log.details.fingerprint)) {
        sharedFingerprints.push(log.details.fingerprint);
      }
      if (log.ipAddress && !sharedIPs.includes(log.ipAddress)) {
        sharedIPs.push(log.ipAddress);
      }
    }
  }

  // Analyze common patterns
  const commonPatterns = {
    similarGamePreferences: false,
    similarBettingPatterns: false,
    synchronizedActivity: false,
    coordinatedBonusAbuse: false,
  };

  // Check for synchronized activity (logins within 5 minutes)
  const recentLogs = await db
    .select()
    .from(auditLogs)
    .where(eq(auditLogs.action, "device_fingerprint_recorded"));

  const accountLogs = recentLogs.filter((log) => accountIds.includes(log.actorId || 0));

  if (accountLogs.length >= 2) {
    const timestamps = accountLogs.map((log) => new Date(log.createdAt).getTime());
    const maxTimeDiff = Math.max(...timestamps) - Math.min(...timestamps);

    if (maxTimeDiff < 5 * 60 * 1000) {
      // 5 minutes
      commonPatterns.synchronizedActivity = true;
      riskScore += 30;
    }
  }

  // Multiple shared fingerprints/IPs increases risk
  if (sharedFingerprints.length > 1) {
    riskScore += 25;
    commonPatterns.similarGamePreferences = true;
  }

  if (sharedIPs.length > 1) {
    riskScore += 20;
  }

  // More than 2 linked accounts is highly suspicious
  if (accountIds.length > 2) {
    riskScore += 25;
    commonPatterns.coordinatedBonusAbuse = true;
  }

  riskScore = Math.min(riskScore, 100);

  let severity: "low" | "medium" | "high" | "critical" = "low";
  if (riskScore >= 80) severity = "critical";
  else if (riskScore >= 60) severity = "high";
  else if (riskScore >= 40) severity = "medium";

  return {
    accountIds,
    sharedFingerprints,
    sharedIPs,
    commonPatterns,
    riskScore,
    severity,
  };
}

/**
 * Get all account clusters
 */
export async function getAllAccountClusters(): Promise<AccountCluster[]> {
  const db = await getDb();
  if (!db) return [];

  const clusters: AccountCluster[] = [];
  const processedCombos = new Set<string>();

  try {
    const allLogs = await db.select().from(auditLogs);

    const fingerprintMap = new Map<string, Set<number>>();
    const ipMap = new Map<string, Set<number>>();

    for (const log of allLogs) {
      const fp = log.details?.fingerprint;
      const ip = log.ipAddress;
      const uid = log.actorId;

      if (fp && uid) {
        if (!fingerprintMap.has(fp)) {
          fingerprintMap.set(fp, new Set());
        }
        fingerprintMap.get(fp)!.add(uid);
      }

      if (ip && uid) {
        if (!ipMap.has(ip)) {
          ipMap.set(ip, new Set());
        }
        ipMap.get(ip)!.add(uid);
      }
    }

    // Process fingerprint clusters
    for (const [fp, accountIds] of fingerprintMap) {
      if (accountIds.size > 1) {
        const key = Array.from(accountIds).sort().join(",");
        if (!processedCombos.has(key)) {
          const cluster = await buildAccountCluster(Array.from(accountIds), "fingerprint");
          clusters.push(cluster);
          processedCombos.add(key);
        }
      }
    }

    // Process IP clusters
    for (const [ip, accountIds] of ipMap) {
      if (accountIds.size > 1) {
        const key = Array.from(accountIds).sort().join(",");
        if (!processedCombos.has(key)) {
          const cluster = await buildAccountCluster(Array.from(accountIds), "ip");
          clusters.push(cluster);
          processedCombos.add(key);
        }
      }
    }

    return clusters;
  } catch (error) {
    console.error("[Multi-Account Detection] Failed to get clusters:", error);
    return [];
  }
}

/**
 * Get suspicious account pairs
 */
export async function getSuspiciousAccountPairs(): Promise<
  Array<{
    account1: number;
    account2: number;
    commonFactors: string[];
    riskScore: number;
  }>
> {
  const clusters = await getAllAccountClusters();
  const pairs: Array<{
    account1: number;
    account2: number;
    commonFactors: string[];
    riskScore: number;
  }> = [];

  for (const cluster of clusters) {
    if (cluster.severity === "high" || cluster.severity === "critical") {
      const commonFactors: string[] = [];

      if (cluster.sharedFingerprints.length > 0) commonFactors.push("shared_fingerprint");
      if (cluster.sharedIPs.length > 0) commonFactors.push("shared_ip");
      if (cluster.commonPatterns.synchronizedActivity) commonFactors.push("synchronized_activity");
      if (cluster.commonPatterns.coordinatedBonusAbuse) commonFactors.push("bonus_abuse");

      // Create pairs from cluster
      for (let i = 0; i < cluster.accountIds.length; i++) {
        for (let j = i + 1; j < cluster.accountIds.length; j++) {
          pairs.push({
            account1: cluster.accountIds[i],
            account2: cluster.accountIds[j],
            commonFactors,
            riskScore: cluster.riskScore,
          });
        }
      }
    }
  }

  return pairs;
}

/**
 * Link accounts for investigation
 */
export async function linkAccountsForInvestigation(
  primaryUserId: number,
  linkedUserIds: number[],
  reason: string
): Promise<void> {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  try {
    for (const linkedUserId of linkedUserIds) {
      await db.insert(auditLogs).values({
        actorId: primaryUserId,
        actorRole: "admin",
        action: "account_link_investigation",
        category: "fraud",
        targetUserId: linkedUserId,
        details: {
          reason,
          linkedAccounts: linkedUserIds,
        },
      });
    }
  } catch (error) {
    console.error("[Multi-Account Detection] Failed to link accounts:", error);
  }
}
