import { getDb } from "../db.ts";
import { eq, and } from "drizzle-orm";
import { users, wallets, transactions, fraudAlerts } from "../../drizzle/schema.ts";
import { writeAuditLog } from "../db.ts";
import { notifyOwner } from "../_core/notification.ts";

/**
 * Fraud Action Service
 * Handles automated responses to fraud detection
 */

export interface FraudAction {
  type: "suspend_account" | "reverse_bonus" | "hold_withdrawal" | "flag_account" | "limit_bets";
  userId: number;
  alertId: number;
  reason: string;
  details: Record<string, unknown>;
  executedAt: Date;
  executedBy: "system" | "admin";
  adminId?: number;
}

export interface ActionResult {
  success: boolean;
  action: FraudAction;
  message: string;
  details: Record<string, unknown>;
}

/**
 * Configuration for automatic actions based on alert severity
 */
const ACTION_CONFIG = {
  // Automatic actions for critical alerts
  CRITICAL_ACTIONS: [
    "suspend_account", // Immediate account suspension
    "hold_withdrawal", // Prevent withdrawals
    "flag_account", // Mark for manual review
  ] as const,

  // Automatic actions for high alerts
  HIGH_ACTIONS: ["flag_account", "limit_bets"] as const,

  // Automatic actions for medium alerts
  MEDIUM_ACTIONS: ["flag_account"] as const,

  // Suspension duration (hours)
  SUSPENSION_DURATION_HOURS: 24,

  // Bet limit reduction percentage
  BET_LIMIT_REDUCTION: 0.5, // Reduce to 50% of normal limit

  // Maximum reversible bonus amount
  MAX_REVERSIBLE_BONUS: 10000,
};

/**
 * Execute automated actions for fraud alert
 */
export async function executeAutomatedActions(
  userId: number,
  alertId: number,
  alertSeverity: "low" | "medium" | "high" | "critical",
  alertType: string
): Promise<ActionResult[]> {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  const results: ActionResult[] = [];
  const actionsToExecute = getActionsForSeverity(alertSeverity);

  try {
    for (const actionType of actionsToExecute) {
      let result: ActionResult | null = null;

      switch (actionType) {
        case "suspend_account":
          result = await suspendAccount(userId, alertId, alertType);
          break;
        case "reverse_bonus":
          result = await reverseBonus(userId, alertId, alertType);
          break;
        case "hold_withdrawal":
          result = await holdWithdrawals(userId, alertId, alertType);
          break;
        case "flag_account":
          result = await flagAccount(userId, alertId, alertType);
          break;
        case "limit_bets":
          result = await limitBets(userId, alertId, alertType);
          break;
      }

      if (result) {
        results.push(result);

        // Log action
        await writeAuditLog({
          actorId: userId,
          actorRole: "user",
          action: `fraud_action_${actionType}`,
          category: "fraud",
          details: {
            alertId,
            alertType,
            actionResult: result,
          },
        });
      }
    }

    // Notify admin of actions taken
    if (results.length > 0) {
      await notifyAdminOfActions(userId, alertId, results);
    }

    return results;
  } catch (error) {
    console.error("[Fraud Actions] Error executing actions:", error);
    throw error;
  }
}

/**
 * Suspend user account
 */
async function suspendAccount(
  userId: number,
  alertId: number,
  reason: string
): Promise<ActionResult> {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  const suspensionUntil = new Date(Date.now() + ACTION_CONFIG.SUSPENSION_DURATION_HOURS * 60 * 60 * 1000);

  // Update user status
  await db
    .update(users)
    .set({
      isActive: false,
      suspendedAt: new Date().toISOString(),
      suspensionReason: `Fraud Alert #${alertId}: ${reason}`,
      suspensionUntil: suspensionUntil.toISOString(),
    })
    .where(eq(users.id, userId));

  const action: FraudAction = {
    type: "suspend_account",
    userId,
    alertId,
    reason,
    details: {
      suspensionDurationHours: ACTION_CONFIG.SUSPENSION_DURATION_HOURS,
      suspensionUntil: suspensionUntil.toISOString(),
    },
    executedAt: new Date(),
    executedBy: "system",
  };

  return {
    success: true,
    action,
    message: `Account suspended for ${ACTION_CONFIG.SUSPENSION_DURATION_HOURS} hours`,
    details: {
      userId,
      suspensionUntil: suspensionUntil.toISOString(),
    },
  };
}

/**
 * Reverse bonus from account
 */
async function reverseBonus(
  userId: number,
  alertId: number,
  reason: string
): Promise<ActionResult> {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  // Find bonus transactions in last 24 hours
  const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();

  const bonusTransactions = await db
    .select()
    .from(transactions)
    .where(
      and(
        eq(transactions.userId, userId),
        (table) => `${table.type} IN ('sc_bonus', 'gc_bonus', 'daily_bonus') AND ${table.createdAt} >= '${oneDayAgo}'`
      )
    );

  let totalReversed = 0;
  const reversedTransactions: number[] = [];

  for (const bonus of bonusTransactions) {
    const bonusAmount = parseFloat(bonus.amount);

    // Only reverse if within limit
    if (totalReversed + bonusAmount <= ACTION_CONFIG.MAX_REVERSIBLE_BONUS) {
      // Deduct bonus from wallet
      const wallet = await db
        .select()
        .from(wallets)
        .where(eq(wallets.userId, userId))
        .limit(1);

      if (wallet[0]) {
        const currency = bonus.currency as "GC" | "SC";
        const balanceField = currency === "GC" ? "gcBalance" : "scBalance";
        const currentBalance = parseFloat(wallet[0][balanceField]);
        const newBalance = Math.max(0, currentBalance - bonusAmount);

        const updateData: any = {};
        updateData[balanceField] = newBalance.toFixed(2);

        await db.update(wallets).set(updateData).where(eq(wallets.userId, userId));

        // Log reversal transaction
        await db.insert(transactions).values({
          userId,
          type: "admin_debit",
          currency,
          amount: bonusAmount.toFixed(2),
          balanceBefore: currentBalance.toFixed(2),
          balanceAfter: newBalance.toFixed(2),
          description: `Bonus reversal due to fraud alert #${alertId}`,
          referenceId: String(bonus.id),
          referenceType: "bonus_reversal",
        });

        totalReversed += bonusAmount;
        reversedTransactions.push(bonus.id);
      }
    }
  }

  const action: FraudAction = {
    type: "reverse_bonus",
    userId,
    alertId,
    reason,
    details: {
      totalReversed,
      transactionsReversed: reversedTransactions.length,
      reversedTransactionIds: reversedTransactions,
    },
    executedAt: new Date(),
    executedBy: "system",
  };

  return {
    success: totalReversed > 0,
    action,
    message: `Reversed ${totalReversed.toFixed(2)} in bonuses from ${reversedTransactions.length} transactions`,
    details: {
      userId,
      totalReversed,
      transactionsReversed: reversedTransactions.length,
    },
  };
}

/**
 * Hold withdrawals for account
 */
async function holdWithdrawals(
  userId: number,
  alertId: number,
  reason: string
): Promise<ActionResult> {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  const holdUntil = new Date(Date.now() + 72 * 60 * 60 * 1000); // 72 hours

  // Update user withdrawal hold
  await db
    .update(users)
    .set({
      withdrawalHoldUntil: holdUntil.toISOString(),
      withdrawalHoldReason: `Fraud Alert #${alertId}: ${reason}`,
    })
    .where(eq(users.id, userId));

  const action: FraudAction = {
    type: "hold_withdrawal",
    userId,
    alertId,
    reason,
    details: {
      holdDurationHours: 72,
      holdUntil: holdUntil.toISOString(),
    },
    executedAt: new Date(),
    executedBy: "system",
  };

  return {
    success: true,
    action,
    message: "Withdrawals held for 72 hours pending review",
    details: {
      userId,
      holdUntil: holdUntil.toISOString(),
    },
  };
}

/**
 * Flag account for manual review
 */
async function flagAccount(
  userId: number,
  alertId: number,
  reason: string
): Promise<ActionResult> {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  // Update user flag status
  await db
    .update(users)
    .set({
      isFlagged: true,
      flaggedReason: `Fraud Alert #${alertId}: ${reason}`,
      flaggedAt: new Date().toISOString(),
    })
    .where(eq(users.id, userId));

  const action: FraudAction = {
    type: "flag_account",
    userId,
    alertId,
    reason,
    details: {
      flaggedForReview: true,
    },
    executedAt: new Date(),
    executedBy: "system",
  };

  return {
    success: true,
    action,
    message: "Account flagged for manual review",
    details: {
      userId,
      flagged: true,
    },
  };
}

/**
 * Limit betting for account
 */
async function limitBets(
  userId: number,
  alertId: number,
  reason: string
): Promise<ActionResult> {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  // Get current wallet
  const wallet = await db.select().from(wallets).where(eq(wallets.userId, userId)).limit(1);

  if (!wallet[0]) {
    throw new Error("Wallet not found");
  }

  // Calculate reduced limits
  const gcLimit = (parseFloat(wallet[0].gcBalance) * ACTION_CONFIG.BET_LIMIT_REDUCTION).toFixed(2);
  const scLimit = (parseFloat(wallet[0].scBalance) * ACTION_CONFIG.BET_LIMIT_REDUCTION).toFixed(2);

  // Update bet limits
  await db
    .update(wallets)
    .set({
      maxGcBetLimit: gcLimit,
      maxScBetLimit: scLimit,
      betLimitUntil: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
    })
    .where(eq(wallets.userId, userId));

  const action: FraudAction = {
    type: "limit_bets",
    userId,
    alertId,
    reason,
    details: {
      gcLimit,
      scLimit,
      reductionPercentage: ACTION_CONFIG.BET_LIMIT_REDUCTION * 100,
      durationHours: 24,
    },
    executedAt: new Date(),
    executedBy: "system",
  };

  return {
    success: true,
    action,
    message: `Bet limits reduced to ${ACTION_CONFIG.BET_LIMIT_REDUCTION * 100}% for 24 hours`,
    details: {
      userId,
      gcLimit,
      scLimit,
    },
  };
}

/**
 * Get actions based on alert severity
 */
function getActionsForSeverity(severity: "low" | "medium" | "high" | "critical"): string[] {
  switch (severity) {
    case "critical":
      return ACTION_CONFIG.CRITICAL_ACTIONS as any;
    case "high":
      return ACTION_CONFIG.HIGH_ACTIONS as any;
    case "medium":
      return ACTION_CONFIG.MEDIUM_ACTIONS as any;
    case "low":
    default:
      return []; // No automatic actions for low severity
  }
}

/**
 * Notify admin of executed actions
 */
async function notifyAdminOfActions(
  userId: number,
  alertId: number,
  results: ActionResult[]
): Promise<void> {
  const db = await getDb();
  if (!db) return;

  // Get user details
  const user = await db.select().from(users).where(eq(users.id, userId)).limit(1);
  const userData = user[0];

  const actionsList = results.map((r) => `- ${r.action.type}: ${r.message}`).join("\n");

  const title = `🚨 Automated Fraud Actions Executed`;
  const content = `
**Alert ID:** ${alertId}
**User:** ${userData?.name || "Unknown"} (ID: ${userData?.id})
**Email:** ${userData?.email || "N/A"}

**Actions Executed:**
${actionsList}

**Summary:**
${results.length} automatic action(s) have been executed in response to the fraud alert.
Please review the account and alert details in the admin dashboard.

---
This is an automated notification. Review and override actions if necessary.
  `.trim();

  await notifyOwner({
    title,
    content,
  });
}

/**
 * Override or undo fraud action
 */
export async function overrideFraudAction(
  actionId: string,
  adminId: number,
  reason: string
): Promise<ActionResult> {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");

  // Log override action
  await writeAuditLog({
    actorId: adminId,
    actorRole: "admin",
    action: "fraud_action_override",
    category: "fraud",
    details: {
      actionId,
      reason,
    },
  });

  return {
    success: true,
    action: {
      type: "suspend_account", // Placeholder
      userId: 0,
      alertId: 0,
      reason: "Override",
      details: { overridden: true },
      executedAt: new Date(),
      executedBy: "admin",
      adminId,
    },
    message: "Action override recorded",
    details: { overridden: true },
  };
}

/**
 * Check if user account is suspended
 */
export async function isAccountSuspended(userId: number): Promise<boolean> {
  const db = await getDb();
  if (!db) return false;

  const user = await db.select().from(users).where(eq(users.id, userId)).limit(1);

  if (!user[0]) return false;

  // Check if suspended and suspension period hasn't expired
  if (user[0].isActive === false && user[0].suspensionUntil) {
    const suspensionUntil = new Date(user[0].suspensionUntil);
    if (suspensionUntil > new Date()) {
      return true;
    }

    // Suspension period expired, reactivate account
    await db.update(users).set({ isActive: true }).where(eq(users.id, userId));
  }

  return false;
}

/**
 * Check if withdrawals are held
 */
export async function areWithdrawalsHeld(userId: number): Promise<boolean> {
  const db = await getDb();
  if (!db) return false;

  const user = await db.select().from(users).where(eq(users.id, userId)).limit(1);

  if (!user[0] || !user[0].withdrawalHoldUntil) return false;

  const holdUntil = new Date(user[0].withdrawalHoldUntil);
  if (holdUntil > new Date()) {
    return true;
  }

  // Hold period expired, clear hold
  await db
    .update(users)
    .set({ withdrawalHoldUntil: null, withdrawalHoldReason: null })
    .where(eq(users.id, userId));

  return false;
}

/**
 * Get account restrictions
 */
export async function getAccountRestrictions(userId: number): Promise<{
  suspended: boolean;
  withdrawalsHeld: boolean;
  betLimited: boolean;
  flagged: boolean;
  restrictions: string[];
}> {
  const db = await getDb();
  if (!db) {
    return {
      suspended: false,
      withdrawalsHeld: false,
      betLimited: false,
      flagged: false,
      restrictions: [],
    };
  }

  const user = await db.select().from(users).where(eq(users.id, userId)).limit(1);

  if (!user[0]) {
    return {
      suspended: false,
      withdrawalsHeld: false,
      betLimited: false,
      flagged: false,
      restrictions: [],
    };
  }

  const restrictions: string[] = [];
  const suspended = user[0].isActive === false;
  const withdrawalsHeld =
    user[0].withdrawalHoldUntil && new Date(user[0].withdrawalHoldUntil) > new Date();
  const flagged = user[0].isFlagged === true;

  if (suspended) restrictions.push("Account suspended");
  if (withdrawalsHeld) restrictions.push("Withdrawals held");
  if (flagged) restrictions.push("Account flagged for review");

  return {
    suspended,
    withdrawalsHeld: !!withdrawalsHeld,
    betLimited: false, // Would check wallet bet limits
    flagged,
    restrictions,
  };
}
