import crypto from "crypto";
import { getDb } from "../db.ts";
import { auditLogs } from "../../drizzle/schema.ts";
import { eq, and } from "drizzle-orm";

/**
 * Device Fingerprinting Service
 * Tracks device signatures and detects multi-account fraud
 */

export interface DeviceSignature {
  userAgent: string;
  acceptLanguage: string;
  timezone: string;
  screenResolution: string;
  colorDepth: string;
  platform: string;
  hardwareConcurrency: string;
  deviceMemory: string;
  maxTouchPoints: string;
  webglRenderer: string;
  webglVendor: string;
}

export interface FingerprintData {
  fingerprint: string;
  ipAddress: string;
  country: string;
  city: string;
  latitude: number;
  longitude: number;
  timestamp: string;
  userAgent: string;
}

export interface LocationData {
  country: string;
  city: string;
  latitude: number;
  longitude: number;
  timezone: string;
  isp: string;
  isVPN: boolean;
  isProxy: boolean;
}

/**
 * Generate device fingerprint from signature
 */
export function generateFingerprint(signature: Partial<DeviceSignature>): string {
  const fingerprintString = JSON.stringify({
    userAgent: signature.userAgent || "",
    acceptLanguage: signature.acceptLanguage || "",
    timezone: signature.timezone || "",
    screenResolution: signature.screenResolution || "",
    colorDepth: signature.colorDepth || "",
    platform: signature.platform || "",
    hardwareConcurrency: signature.hardwareConcurrency || "",
    deviceMemory: signature.deviceMemory || "",
    maxTouchPoints: signature.maxTouchPoints || "",
    webglRenderer: signature.webglRenderer || "",
    webglVendor: signature.webglVendor || "",
  });

  return crypto.createHash("sha256").update(fingerprintString).digest("hex");
}

/**
 * Calculate similarity between two fingerprints (0-1, where 1 is identical)
 */
export function calculateFingerprintSimilarity(
  sig1: Partial<DeviceSignature>,
  sig2: Partial<DeviceSignature>
): number {
  let matches = 0;
  let total = 0;

  const keys = [
    "userAgent",
    "platform",
    "screenResolution",
    "colorDepth",
    "hardwareConcurrency",
    "deviceMemory",
    "maxTouchPoints",
    "webglRenderer",
    "webglVendor",
  ] as const;

  for (const key of keys) {
    if (sig1[key] !== undefined && sig2[key] !== undefined) {
      total++;
      if (sig1[key] === sig2[key]) {
        matches++;
      }
    }
  }

  return total > 0 ? matches / total : 0;
}

/**
 * Calculate distance between two geographic coordinates (in km)
 */
export function calculateGeoDistance(
  lat1: number,
  lon1: number,
  lat2: number,
  lon2: number
): number {
  const R = 6371; // Earth's radius in km
  const dLat = ((lat2 - lat1) * Math.PI) / 180;
  const dLon = ((lon2 - lon1) * Math.PI) / 180;
  const a =
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos((lat1 * Math.PI) / 180) *
      Math.cos((lat2 * Math.PI) / 180) *
      Math.sin(dLon / 2) *
      Math.sin(dLon / 2);
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  return R * c;
}

/**
 * Detect if login is physically impossible (too fast travel)
 */
export function isImpossibleTravel(
  prevLocation: LocationData,
  currentLocation: LocationData,
  timeDiffMinutes: number
): boolean {
  // Calculate distance in km
  const distance = calculateGeoDistance(
    prevLocation.latitude,
    prevLocation.longitude,
    currentLocation.latitude,
    currentLocation.longitude
  );

  // Maximum realistic travel speed: 900 km/h (commercial airplane)
  const maxRealisticDistance = (900 / 60) * timeDiffMinutes;

  return distance > maxRealisticDistance && distance > 100; // Also require > 100km to avoid false positives
}

/**
 * Store device fingerprint for user
 */
export async function storeDeviceFingerprint(
  userId: number,
  fingerprint: string,
  ipAddress: string,
  userAgent: string,
  location: LocationData
): Promise<void> {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  try {
    await db.insert(auditLogs).values({
      actorId: userId,
      actorRole: "user",
      action: "device_fingerprint_recorded",
      category: "auth",
      details: {
        fingerprint,
        location: {
          country: location.country,
          city: location.city,
          latitude: location.latitude,
          longitude: location.longitude,
          isVPN: location.isVPN,
          isProxy: location.isProxy,
        },
      },
      ipAddress,
      userAgent,
    });
  } catch (error) {
    console.error("[Device Fingerprinting] Failed to store fingerprint:", error);
  }
}

/**
 * Get all fingerprints for a user
 */
export async function getUserFingerprints(userId: number): Promise<any[]> {
  const db = await getDb();
  if (!db) return [];

  try {
    const logs = await db
      .select()
      .from(auditLogs)
      .where(
        and(
          eq(auditLogs.actorId, userId),
          eq(auditLogs.action, "device_fingerprint_recorded")
        )
      );

    return logs.map((log) => ({
      fingerprint: log.details?.fingerprint,
      location: log.details?.location,
      ipAddress: log.ipAddress,
      userAgent: log.userAgent,
      timestamp: log.createdAt,
    }));
  } catch (error) {
    console.error("[Device Fingerprinting] Failed to get fingerprints:", error);
    return [];
  }
}

/**
 * Find all users with matching fingerprint
 */
export async function findUsersWithFingerprint(fingerprint: string): Promise<number[]> {
  const db = await getDb();
  if (!db) return [];

  try {
    const logs = await db
      .select()
      .from(auditLogs)
      .where(eq(auditLogs.action, "device_fingerprint_recorded"));

    const matchingUserIds = new Set<number>();

    for (const log of logs) {
      if (log.details?.fingerprint === fingerprint && log.actorId) {
        matchingUserIds.add(log.actorId);
      }
    }

    return Array.from(matchingUserIds);
  } catch (error) {
    console.error("[Device Fingerprinting] Failed to find matching fingerprints:", error);
    return [];
  }
}

/**
 * Find all users with matching IP address
 */
export async function findUsersWithIP(ipAddress: string): Promise<number[]> {
  const db = await getDb();
  if (!db) return [];

  try {
    const logs = await db
      .select()
      .from(auditLogs)
      .where(eq(auditLogs.ipAddress, ipAddress));

    const userIds = new Set<number>();

    for (const log of logs) {
      if (log.actorId) {
        userIds.add(log.actorId);
      }
    }

    return Array.from(userIds);
  } catch (error) {
    console.error("[Device Fingerprinting] Failed to find users with IP:", error);
    return [];
  }
}

/**
 * Detect multi-account fraud patterns
 */
export interface MultiAccountFraudIndicators {
  sharedFingerprint: boolean;
  sharedIP: boolean;
  impossibleTravel: boolean;
  suspiciousPattern: boolean;
  riskScore: number;
  details: {
    sharedFingerprintUsers?: number[];
    sharedIPUsers?: number[];
    lastLoginLocation?: LocationData;
    currentLocation?: LocationData;
    timeSinceLastLogin?: number;
  };
}

export async function detectMultiAccountFraud(
  userId: number,
  fingerprint: string,
  ipAddress: string,
  currentLocation: LocationData,
  userFingerprints: any[]
): Promise<MultiAccountFraudIndicators> {
  let riskScore = 0;
  const details: MultiAccountFraudIndicators["details"] = {};

  // Check for shared fingerprint with other accounts
  const usersWithFingerprint = await findUsersWithFingerprint(fingerprint);
  const sharedFingerprint = usersWithFingerprint.length > 1;

  if (sharedFingerprint) {
    riskScore += 40;
    details.sharedFingerprintUsers = usersWithFingerprint.filter((id) => id !== userId);
  }

  // Check for shared IP with other accounts
  const usersWithIP = await findUsersWithIP(ipAddress);
  const sharedIP = usersWithIP.length > 1;

  if (sharedIP) {
    riskScore += 30;
    details.sharedIPUsers = usersWithIP.filter((id) => id !== userId);
  }

  // Check for impossible travel
  let impossibleTravel = false;

  if (userFingerprints.length > 0) {
    const lastLogin = userFingerprints[userFingerprints.length - 1];
    const lastLocation = lastLogin.location;

    if (lastLocation && lastLocation.latitude && lastLocation.longitude) {
      const timeDiff = Math.floor(
        (Date.now() - new Date(lastLogin.timestamp).getTime()) / (1000 * 60)
      );

      impossibleTravel = isImpossibleTravel(lastLocation, currentLocation, timeDiff);

      if (impossibleTravel) {
        riskScore += 50;
        details.lastLoginLocation = lastLocation;
        details.currentLocation = currentLocation;
        details.timeSinceLastLogin = timeDiff;
      }
    }
  }

  // Detect suspicious patterns
  let suspiciousPattern = false;

  // Pattern 1: Multiple logins from different countries in short time
  if (userFingerprints.length >= 2) {
    const recentLogins = userFingerprints.slice(-5);
    const countries = new Set(recentLogins.map((fp) => fp.location?.country));

    if (countries.size >= 3) {
      // 3+ countries in last 5 logins
      suspiciousPattern = true;
      riskScore += 25;
    }
  }

  // Pattern 2: VPN/Proxy usage
  if (currentLocation.isVPN || currentLocation.isProxy) {
    riskScore += 15;
    suspiciousPattern = true;
  }

  return {
    sharedFingerprint,
    sharedIP,
    impossibleTravel,
    suspiciousPattern,
    riskScore: Math.min(riskScore, 100),
    details,
  };
}

/**
 * Get device fingerprint statistics for admin
 */
export async function getDeviceFingerprintStats(): Promise<{
  totalFingerprints: number;
  uniqueFingerprints: number;
  sharedFingerprints: number;
  suspiciousAccounts: number;
}> {
  const db = await getDb();
  if (!db)
    return {
      totalFingerprints: 0,
      uniqueFingerprints: 0,
      sharedFingerprints: 0,
      suspiciousAccounts: 0,
    };

  try {
    const logs = await db
      .select()
      .from(auditLogs)
      .where(eq(auditLogs.action, "device_fingerprint_recorded"));

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

    for (const log of logs) {
      const fingerprint = log.details?.fingerprint;
      const ip = log.ipAddress;
      const userId = log.actorId;

      if (fingerprint && userId) {
        if (!fingerprintMap.has(fingerprint)) {
          fingerprintMap.set(fingerprint, []);
        }
        fingerprintMap.get(fingerprint)!.push(userId);
      }

      if (ip && userId) {
        if (!ipMap.has(ip)) {
          ipMap.set(ip, []);
        }
        ipMap.get(ip)!.push(userId);
      }
    }

    const sharedFingerprints = Array.from(fingerprintMap.values()).filter(
      (users) => users.length > 1
    ).length;

    const suspiciousAccounts = new Set<number>();

    for (const users of fingerprintMap.values()) {
      if (users.length > 1) {
        users.forEach((id) => suspiciousAccounts.add(id));
      }
    }

    for (const users of ipMap.values()) {
      if (users.length > 1) {
        users.forEach((id) => suspiciousAccounts.add(id));
      }
    }

    return {
      totalFingerprints: logs.length,
      uniqueFingerprints: fingerprintMap.size,
      sharedFingerprints,
      suspiciousAccounts: suspiciousAccounts.size,
    };
  } catch (error) {
    console.error("[Device Fingerprinting] Failed to get stats:", error);
    return {
      totalFingerprints: 0,
      uniqueFingerprints: 0,
      sharedFingerprints: 0,
      suspiciousAccounts: 0,
    };
  }
}
