import { z } from "zod";
import { router, protectedProcedure } from "../_core/trpc.ts";
import { TRPCError } from "@trpc/server";
import {
  getUserFingerprints,
  findUsersWithFingerprint,
  findUsersWithIP,
} from "../services/deviceFingerprinting.ts";
import {
  getAllAccountClusters,
  getSuspiciousAccountPairs,
  linkAccountsForInvestigation,
} from "../services/multiAccountDetection.ts";
import { getDb } from "../db.ts";
import { auditLogs } from "../../drizzle/schema.ts";
import { eq } from "drizzle-orm";

/**
 * Admin router for fingerprint analysis
 */
export const adminFingerprintAnalysisRouter = router({
  /**
   * Get fingerprints for a user
   */
  getUserFingerprints: protectedProcedure
    .input(z.object({ userId: z.number() }))
    .query(async ({ ctx, input }) => {
      // Verify admin role
      if (ctx.user.role !== "admin") {
        throw new TRPCError({ code: "FORBIDDEN", message: "Admin access required" });
      }

      const fingerprints = await getUserFingerprints(input.userId);

      return {
        userId: input.userId,
        fingerprints: fingerprints.map((fp) => ({
          fingerprint: fp.fingerprint,
          location: fp.location,
          ipAddress: fp.ipAddress,
          timestamp: fp.timestamp,
        })),
        totalCount: fingerprints.length,
      };
    }),

  /**
   * Find all users with matching fingerprint
   */
  findUsersWithFingerprint: protectedProcedure
    .input(z.object({ fingerprint: z.string() }))
    .query(async ({ ctx, input }) => {
      // Verify admin role
      if (ctx.user.role !== "admin") {
        throw new TRPCError({ code: "FORBIDDEN", message: "Admin access required" });
      }

      const userIds = await findUsersWithFingerprint(input.fingerprint);

      return {
        fingerprint: input.fingerprint,
        userIds,
        count: userIds.length,
      };
    }),

  /**
   * Find all users with matching IP
   */
  findUsersWithIP: protectedProcedure
    .input(z.object({ ipAddress: z.string() }))
    .query(async ({ ctx, input }) => {
      // Verify admin role
      if (ctx.user.role !== "admin") {
        throw new TRPCError({ code: "FORBIDDEN", message: "Admin access required" });
      }

      const userIds = await findUsersWithIP(input.ipAddress);

      return {
        ipAddress: input.ipAddress,
        userIds,
        count: userIds.length,
      };
    }),

  /**
   * Get all account clusters
   */
  getAllClusters: protectedProcedure.query(async ({ ctx }) => {
    // Verify admin role
    if (ctx.user.role !== "admin") {
      throw new TRPCError({ code: "FORBIDDEN", message: "Admin access required" });
    }

    const clusters = await getAllAccountClusters();

    return {
      totalClusters: clusters.length,
      criticalClusters: clusters.filter((c) => c.severity === "critical").length,
      highClusters: clusters.filter((c) => c.severity === "high").length,
      clusters: clusters.map((c) => ({
        accountIds: c.accountIds,
        sharedFingerprints: c.sharedFingerprints,
        sharedIPs: c.sharedIPs,
        riskScore: c.riskScore,
        severity: c.severity,
        commonPatterns: c.commonPatterns,
      })),
    };
  }),

  /**
   * Get suspicious account pairs
   */
  getSuspiciousPairs: protectedProcedure.query(async ({ ctx }) => {
    // Verify admin role
    if (ctx.user.role !== "admin") {
      throw new TRPCError({ code: "FORBIDDEN", message: "Admin access required" });
    }

    const pairs = await getSuspiciousAccountPairs();

    return {
      totalPairs: pairs.length,
      pairs: pairs.map((p) => ({
        account1: p.account1,
        account2: p.account2,
        commonFactors: p.commonFactors,
        riskScore: p.riskScore,
      })),
    };
  }),

  /**
   * Get login history for user
   */
  getLoginHistory: protectedProcedure
    .input(
      z.object({
        userId: z.number(),
        limit: z.number().min(1).max(100).default(20),
        offset: z.number().min(0).default(0),
      })
    )
    .query(async ({ ctx, input }) => {
      // Verify admin role
      if (ctx.user.role !== "admin") {
        throw new TRPCError({ code: "FORBIDDEN", message: "Admin access required" });
      }

      const db = await getDb();
      if (!db) throw new Error("Database unavailable");

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

      const userLogs = logs
        .filter((log) => log.actorId === input.userId)
        .sort(
          (a, b) =>
            new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
        )
        .slice(input.offset, input.offset + input.limit);

      return {
        userId: input.userId,
        totalLogins: logs.filter((log) => log.actorId === input.userId).length,
        logins: userLogs.map((log) => ({
          timestamp: log.createdAt,
          ipAddress: log.ipAddress,
          fingerprint: log.details?.fingerprint,
          location: log.details?.location,
          userAgent: log.userAgent,
        })),
      };
    }),

  /**
   * Get IP address activity
   */
  getIPActivity: protectedProcedure
    .input(
      z.object({
        ipAddress: z.string(),
        limit: z.number().min(1).max(100).default(20),
      })
    )
    .query(async ({ ctx, input }) => {
      // Verify admin role
      if (ctx.user.role !== "admin") {
        throw new TRPCError({ code: "FORBIDDEN", message: "Admin access required" });
      }

      const db = await getDb();
      if (!db) throw new Error("Database unavailable");

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

      const userIds = new Set<number>();
      const activities: any[] = [];

      for (const log of logs.slice(0, input.limit)) {
        if (log.actorId) {
          userIds.add(log.actorId);
        }

        activities.push({
          userId: log.actorId,
          action: log.action,
          timestamp: log.createdAt,
          details: log.details,
        });
      }

      return {
        ipAddress: input.ipAddress,
        uniqueUsers: Array.from(userIds),
        totalActivities: logs.length,
        activities: activities.slice(0, input.limit),
      };
    }),

  /**
   * Link accounts for investigation
   */
  linkAccounts: protectedProcedure
    .input(
      z.object({
        primaryUserId: z.number(),
        linkedUserIds: z.array(z.number()).min(1),
        reason: z.string().min(10).max(500),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Verify admin role
      if (ctx.user.role !== "admin") {
        throw new TRPCError({ code: "FORBIDDEN", message: "Admin access required" });
      }

      await linkAccountsForInvestigation(
        input.primaryUserId,
        input.linkedUserIds,
        input.reason
      );

      return {
        success: true,
        message: `Linked ${input.linkedUserIds.length} accounts for investigation`,
      };
    }),

  /**
   * Get fingerprint statistics
   */
  getStatistics: protectedProcedure.query(async ({ ctx }) => {
    // Verify admin role
    if (ctx.user.role !== "admin") {
      throw new TRPCError({ code: "FORBIDDEN", message: "Admin access required" });
    }

    const db = await getDb();
    if (!db) throw new Error("Database unavailable");

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

    const fingerprintMap = new Map<string, Set<number>>();
    const ipMap = new Map<string, Set<number>>();
    const userFingerprintCounts = new Map<number, 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);
        userFingerprintCounts.set(uid, (userFingerprintCounts.get(uid) || 0) + 1);
      }

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

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

    const sharedIPs = Array.from(ipMap.values()).filter((users) => users.size > 1)
      .length;

    const suspiciousAccounts = new Set<number>();
    for (const users of fingerprintMap.values()) {
      if (users.size > 1) {
        users.forEach((id) => suspiciousAccounts.add(id));
      }
    }
    for (const users of ipMap.values()) {
      if (users.size > 1) {
        users.forEach((id) => suspiciousAccounts.add(id));
      }
    }

    return {
      totalFingerprints: fingerprintMap.size,
      totalIPs: ipMap.size,
      sharedFingerprints,
      sharedIPs,
      suspiciousAccounts: suspiciousAccounts.size,
      averageFingerprintsPerUser:
        userFingerprintCounts.size > 0
          ? Array.from(userFingerprintCounts.values()).reduce((a, b) => a + b, 0) /
            userFingerprintCounts.size
          : 0,
    };
  }),

  /**
   * Export fingerprint report
   */
  exportReport: protectedProcedure
    .input(
      z.object({
        includeAllUsers: z.boolean().default(false),
        minRiskScore: z.number().min(0).max(100).default(50),
      })
    )
    .query(async ({ ctx, input }) => {
      // Verify admin role
      if (ctx.user.role !== "admin") {
        throw new TRPCError({ code: "FORBIDDEN", message: "Admin access required" });
      }

      const clusters = await getAllAccountClusters();
      const pairs = await getSuspiciousAccountPairs();

      const filteredClusters = input.includeAllUsers
        ? clusters
        : clusters.filter((c) => c.riskScore >= input.minRiskScore);

      const filteredPairs = input.includeAllUsers
        ? pairs
        : pairs.filter((p) => p.riskScore >= input.minRiskScore);

      return {
        generatedAt: new Date().toISOString(),
        filters: {
          includeAllUsers: input.includeAllUsers,
          minRiskScore: input.minRiskScore,
        },
        summary: {
          totalClusters: filteredClusters.length,
          totalPairs: filteredPairs.length,
          criticalClusters: filteredClusters.filter((c) => c.severity === "critical")
            .length,
          highClusters: filteredClusters.filter((c) => c.severity === "high").length,
        },
        clusters: filteredClusters,
        pairs: filteredPairs,
      };
    }),
});
