import { revalidatePath } from "next/cache";
import type {
  AppConfig,
  Prisma,
  SubscriptionAccessKind,
  SubscriptionRiskLevel,
  SubscriptionRiskReason,
} from "@prisma/client";
import { prisma, type DbClient } from "@/lib/prisma";
import type { ClientRequestContext } from "@/lib/request-context";
import { recordAuditLog } from "@/services/audit";
import { createNotification } from "@/services/notifications";
import { createPanelAdapter } from "@/services/node-panel/factory";
import { getAppConfig } from "@/services/app-config";

type SubscriptionRiskConfig = Pick<
  AppConfig,
  | "subscriptionRiskEnabled"
  | "subscriptionRiskAutoSuspend"
  | "subscriptionRiskWindowHours"
  | "subscriptionRiskCityWarning"
  | "subscriptionRiskCitySuspend"
  | "subscriptionRiskRegionWarning"
  | "subscriptionRiskRegionSuspend"
>;

interface RecordSubscriptionAccessInput {
  kind: SubscriptionAccessKind;
  context: ClientRequestContext;
  userId?: string | null;
  subscriptionId?: string | null;
  allowed?: boolean;
  reason?: string | null;
  evaluateRisk?: boolean;
  riskConfig?: SubscriptionRiskConfig;
}

interface RiskDecision {
  level: SubscriptionRiskLevel;
  reason: SubscriptionRiskReason;
}

interface RiskEvaluationResult {
  warned: boolean;
  suspended: boolean;
  eventId?: string;
}

interface RiskThresholds {
  cityWarning: number;
  citySuspend: number;
  regionWarning: number;
  regionSuspend: number;
  autoSuspend: boolean;
}

function normalizeLocationPart(value: string | null | undefined) {
  return value?.trim().toLowerCase() || null;
}

function hasLocationPart(value: string | null | undefined) {
  return normalizeLocationPart(value) != null;
}

function addLocationKey(
  map: Map<string, string>,
  parts: Array<string | null | undefined>,
  labelParts: Array<string | null | undefined>,
) {
  const normalizedParts = parts.map(normalizeLocationPart).filter(Boolean);
  if (normalizedParts.length === 0) return;

  const key = normalizedParts.join(":");
  const label = labelParts.map((part) => part?.trim()).filter(Boolean).join(" / ");
  map.set(key, label || key);
}

function formatKeyPreview(values: string[]) {
  if (values.length === 0) return "未知";
  const preview = values.slice(0, 5).join("、");
  return values.length > 5 ? `${preview} 等 ${values.length} 个` : preview;
}

function getScopeLabel(kind: SubscriptionAccessKind) {
  return kind === "AGGREGATE" ? "总订阅" : "单订阅";
}

function riskMessage(options: {
  decision: RiskDecision;
  kind: SubscriptionAccessKind;
  ip: string;
  cityCount: number;
  regionCount: number;
  cityLabels: string[];
  regionLabels: string[];
}) {
  const scope = getScopeLabel(options.kind);
  const locationSummary = options.decision.reason.startsWith("REGION")
    ? `${options.regionCount} 个省/地区：${formatKeyPreview(options.regionLabels)}`
    : `${options.cityCount} 个城市：${formatKeyPreview(options.cityLabels)}`;

  if (options.decision.level === "SUSPENDED") {
    return `${scope}访问地区异常，24 小时内出现 ${locationSummary}，最近 IP ${options.ip}，已自动暂停。`;
  }

  return `${scope}访问地区异常，24 小时内出现 ${locationSummary}，最近 IP ${options.ip}，已记录警告。`;
}

function decideRisk(cityCount: number, regionCount: number, thresholds: RiskThresholds): RiskDecision | null {
  if (thresholds.autoSuspend && regionCount >= thresholds.regionSuspend) {
    return { level: "SUSPENDED", reason: "REGION_VARIANCE_SUSPEND" };
  }
  if (thresholds.autoSuspend && cityCount >= thresholds.citySuspend) {
    return { level: "SUSPENDED", reason: "CITY_VARIANCE_SUSPEND" };
  }
  if (regionCount >= thresholds.regionWarning) {
    return { level: "WARNING", reason: "REGION_VARIANCE_WARNING" };
  }
  if (cityCount >= thresholds.cityWarning) {
    return { level: "WARNING", reason: "CITY_VARIANCE_WARNING" };
  }

  return null;
}

function riskDayKey(date = new Date()) {
  return date.toISOString().slice(0, 10);
}

function scopeKey(input: { kind: SubscriptionAccessKind; userId?: string | null; subscriptionId?: string | null }) {
  if (input.kind === "SINGLE") return `subscription:${input.subscriptionId}`;
  return `aggregate:${input.userId}`;
}

async function getTargetLabel(input: { userId?: string | null; subscriptionId?: string | null }, db: DbClient) {
  if (input.subscriptionId) {
    const subscription = await db.userSubscription.findUnique({
      where: { id: input.subscriptionId },
      select: {
        plan: { select: { name: true } },
        user: { select: { email: true } },
      },
    });

    if (subscription) return `${subscription.user.email} / ${subscription.plan.name}`;
  }

  if (input.userId) {
    const user = await db.user.findUnique({
      where: { id: input.userId },
      select: { email: true },
    });
    return user?.email ?? input.userId;
  }

  return null;
}

function revalidateRiskViews(subscriptionIds: string[] = []) {
  revalidatePath("/admin/audit-logs");
  revalidatePath("/admin/subscription-risk");
  revalidatePath("/admin/subscriptions");
  revalidatePath("/subscriptions");
  revalidatePath("/dashboard");
  revalidatePath("/notifications");
  for (const id of subscriptionIds) {
    revalidatePath(`/admin/subscriptions/${id}`);
    revalidatePath(`/subscriptions/${id}`);
  }
}

async function disableProxyClient(subscriptionId: string) {
  const client = await prisma.nodeClient.findUnique({
    where: { subscriptionId },
    select: {
      id: true,
      uuid: true,
      inbound: {
        select: {
          panelInboundId: true,
          server: true,
        },
      },
    },
  });

  if (!client) return null;
  if (client.inbound.panelInboundId == null) {
    throw new Error("3x-ui 入站 ID 缺失，请重新同步节点入站");
  }

  const adapter = createPanelAdapter(client.inbound.server);
  await adapter.login();
  await adapter.updateClientEnable(client.inbound.panelInboundId, client.uuid, false);

  await prisma.nodeClient.update({
    where: { id: client.id },
    data: { isEnabled: false },
  });

  return client.id;
}

async function suspendSubscriptionForRisk(subscriptionId: string, message: string) {
  const subscription = await prisma.userSubscription.findUnique({
    where: { id: subscriptionId },
    include: {
      plan: true,
      user: true,
    },
  });

  if (!subscription || subscription.status !== "ACTIVE") {
    return false;
  }

  let disableError: string | null = null;
  if (subscription.plan.type === "PROXY") {
    try {
      await disableProxyClient(subscription.id);
    } catch (error) {
      disableError = error instanceof Error ? error.message : String(error);
    }
  }

  await prisma.userSubscription.update({
    where: { id: subscription.id },
    data: { status: "SUSPENDED" },
  });

  await createNotification({
    userId: subscription.userId,
    type: "SUBSCRIPTION",
    level: "ERROR",
    title: "订阅已自动暂停",
    body: `${subscription.plan.name} 因订阅访问地区异常已被系统暂停，请联系管理员确认。`,
    link: `/subscriptions/${subscription.id}`,
    dedupeKey: `risk:suspended:${subscription.id}:${riskDayKey()}`,
  });

  await recordAuditLog({
    action: "subscription.auto_suspend",
    targetType: "UserSubscription",
    targetId: subscription.id,
    targetLabel: `${subscription.user.email} / ${subscription.plan.name}`,
    message,
    metadata: {
      reason: "subscription_access_risk",
      disableProxyClientError: disableError,
    },
  });

  return true;
}

async function suspendScopeForRisk(input: {
  kind: SubscriptionAccessKind;
  userId?: string | null;
  subscriptionId?: string | null;
  message: string;
}) {
  if (input.kind === "SINGLE" && input.subscriptionId) {
    const suspended = await suspendSubscriptionForRisk(input.subscriptionId, input.message);
    return suspended ? [input.subscriptionId] : [];
  }

  if (input.kind === "AGGREGATE" && input.userId) {
    const subscriptions = await prisma.userSubscription.findMany({
      where: {
        userId: input.userId,
        status: "ACTIVE",
        plan: { type: "PROXY" },
      },
      select: { id: true },
    });

    const suspendedIds: string[] = [];
    for (const subscription of subscriptions) {
      const suspended = await suspendSubscriptionForRisk(subscription.id, input.message);
      if (suspended) suspendedIds.push(subscription.id);
    }

    return suspendedIds;
  }

  return [];
}

async function createRiskEvent(input: {
  kind: SubscriptionAccessKind;
  userId?: string | null;
  subscriptionId?: string | null;
  ip: string;
  decision: RiskDecision;
  message: string;
  windowStartedAt: Date;
  countryLabels: string[];
  regionLabels: string[];
  cityLabels: string[];
  db: DbClient;
}) {
  const dedupeKey = [
    "subscription-risk",
    scopeKey(input),
    input.decision.reason,
    riskDayKey(),
  ].join(":");

  const existing = await input.db.subscriptionRiskEvent.findUnique({
    where: { dedupeKey },
  });
  if (existing) return { event: existing, created: false };

  const event = await input.db.subscriptionRiskEvent.create({
    data: {
      userId: input.userId ?? null,
      subscriptionId: input.subscriptionId ?? null,
      kind: input.kind,
      level: input.decision.level,
      reason: input.decision.reason,
      ip: input.ip === "unknown" ? null : input.ip,
      countryCount: input.countryLabels.length,
      regionCount: input.regionLabels.length,
      cityCount: input.cityLabels.length,
      countryKeys: input.countryLabels as Prisma.InputJsonValue,
      regionKeys: input.regionLabels as Prisma.InputJsonValue,
      cityKeys: input.cityLabels as Prisma.InputJsonValue,
      message: input.message,
      dedupeKey,
      windowStartedAt: input.windowStartedAt,
    },
  });

  return { event, created: true };
}

async function evaluateSubscriptionRisk(input: {
  kind: SubscriptionAccessKind;
  userId?: string | null;
  subscriptionId?: string | null;
  ip: string;
  db: DbClient;
  config?: SubscriptionRiskConfig;
}): Promise<RiskEvaluationResult> {
  if (!input.userId) return { warned: false, suspended: false };
  if (input.kind === "SINGLE" && !input.subscriptionId) return { warned: false, suspended: false };

  const config = input.config ?? await getAppConfig(input.db);
  if (!config.subscriptionRiskEnabled) return { warned: false, suspended: false };

  const thresholds: RiskThresholds = {
    cityWarning: config.subscriptionRiskCityWarning,
    citySuspend: config.subscriptionRiskCitySuspend,
    regionWarning: config.subscriptionRiskRegionWarning,
    regionSuspend: config.subscriptionRiskRegionSuspend,
    autoSuspend: config.subscriptionRiskAutoSuspend,
  };
  const windowStartedAt = new Date(Date.now() - config.subscriptionRiskWindowHours * 60 * 60 * 1000);
  const logs = await input.db.subscriptionAccessLog.findMany({
    where: {
      allowed: true,
      createdAt: { gte: windowStartedAt },
      ...(input.kind === "SINGLE"
        ? { kind: "SINGLE", subscriptionId: input.subscriptionId }
        : { kind: "AGGREGATE", userId: input.userId }),
    },
    select: {
      country: true,
      region: true,
      regionCode: true,
      city: true,
    },
  });

  const countryMap = new Map<string, string>();
  const regionMap = new Map<string, string>();
  const cityMap = new Map<string, string>();

  for (const log of logs) {
    addLocationKey(countryMap, [log.country], [log.country]);

    if (hasLocationPart(log.regionCode) || hasLocationPart(log.region)) {
      addLocationKey(
        regionMap,
        [log.country, log.regionCode ?? log.region],
        [log.country, log.region ?? log.regionCode],
      );
    }

    if (hasLocationPart(log.city)) {
      addLocationKey(
        cityMap,
        [log.country, log.regionCode ?? log.region, log.city],
        [log.country, log.region ?? log.regionCode, log.city],
      );
    }
  }

  const countryLabels = Array.from(countryMap.values());
  const regionLabels = Array.from(regionMap.values());
  const cityLabels = Array.from(cityMap.values());
  const decision = decideRisk(cityLabels.length, regionLabels.length, thresholds);
  if (!decision) return { warned: false, suspended: false };

  const message = riskMessage({
    decision,
    kind: input.kind,
    ip: input.ip,
    cityCount: cityLabels.length,
    regionCount: regionLabels.length,
    cityLabels,
    regionLabels,
  });

  const { event, created } = await createRiskEvent({
    ...input,
    decision,
    message,
    windowStartedAt,
    countryLabels,
    regionLabels,
    cityLabels,
    db: input.db,
  });

  const targetLabel = created
    ? await getTargetLabel({ userId: input.userId, subscriptionId: input.subscriptionId }, input.db)
    : null;

  if (created) {
    await recordAuditLog({
      action: decision.level === "SUSPENDED" ? "risk.subscription.suspend" : "risk.subscription.warning",
      targetType: input.subscriptionId ? "UserSubscription" : "User",
      targetId: input.subscriptionId ?? input.userId ?? null,
      targetLabel,
      message,
      metadata: {
        eventId: event.id,
        reason: decision.reason,
        kind: input.kind,
        ip: input.ip,
        countryCount: countryLabels.length,
        regionCount: regionLabels.length,
        cityCount: cityLabels.length,
        windowStartedAt: windowStartedAt.toISOString(),
      },
    }, input.db);

    if (input.userId && decision.level === "WARNING") {
      await createNotification({
        userId: input.userId,
        type: "SUBSCRIPTION",
        level: "WARNING",
        title: "订阅访问异常",
        body: "检测到订阅链接在多个地区访问。如果不是你本人操作，请重置订阅访问并联系管理员。",
        link: input.subscriptionId ? `/subscriptions/${input.subscriptionId}` : "/subscriptions",
        dedupeKey: `risk:warning:${event.id}`,
      }, input.db);
    }
  }

  if (decision.level === "SUSPENDED") {
    const suspendedIds = await suspendScopeForRisk({
      kind: input.kind,
      userId: input.userId,
      subscriptionId: input.subscriptionId,
      message,
    });
    revalidateRiskViews(suspendedIds);
    return { warned: false, suspended: true, eventId: event.id };
  }

  if (created) revalidateRiskViews(input.subscriptionId ? [input.subscriptionId] : []);
  return { warned: true, suspended: false, eventId: event.id };
}

export async function recordSubscriptionAccess(
  input: RecordSubscriptionAccessInput,
  db: DbClient = prisma,
): Promise<RiskEvaluationResult> {
  await db.subscriptionAccessLog.create({
    data: {
      userId: input.userId ?? null,
      subscriptionId: input.subscriptionId ?? null,
      kind: input.kind,
      ip: input.context.ip,
      userAgent: input.context.userAgent,
      country: input.context.geo.country,
      region: input.context.geo.region,
      regionCode: input.context.geo.regionCode,
      city: input.context.geo.city,
      latitude: input.context.geo.latitude,
      longitude: input.context.geo.longitude,
      geoSource: input.context.geo.source,
      allowed: input.allowed ?? true,
      reason: input.reason ?? null,
    },
  });

  if (input.allowed === false || input.evaluateRisk === false) {
    return { warned: false, suspended: false };
  }

  return evaluateSubscriptionRisk({
    kind: input.kind,
    userId: input.userId,
    subscriptionId: input.subscriptionId,
    ip: input.context.ip,
    db,
    config: input.riskConfig,
  });
}
