import { usePrisma } from "../utils/prisma";

/**
 * Device-Binding pro User. Free=1, Pro=1, Legend=3 (siehe plan-features.maxDevices).
 * deviceId kommt vom Frontend via Capacitor Device.getId() (persistent UUID).
 */

export interface DeviceRecord {
  id: string;
  userId: string;
  deviceId: string;
  platform: string;
  model: string | null;
  name: string | null;
  osVersion: string | null;
  lastSeenAt: Date;
  createdAt: Date;
  // Device-Account-Lock
  boundToPlan: string | null;
  releaseRequestedAt: Date | null;
  lockNotifiedAt: Date | null;
}

/** Pläne die einen Device-Account-Lock aktivieren. Free-User binden nie. */
const LOCKING_PLANS = new Set(["pro", "legend", "standard", "premium"]);

/** Ist ein Plan ein "locking" Plan (Pro/Legend inkl. Legacy-Namen)? */
export function isLockingPlan(plan: string | null | undefined): boolean {
  if (!plan) return false;
  return LOCKING_PLANS.has(plan.toLowerCase());
}

/**
 * Prüft ob ein gegebenes deviceId bereits an einen anderen User gebunden ist
 * (Lock aktiv). Gibt die bound Row zurück wenn ja, null wenn frei.
 *
 * "Gebunden" = boundToPlan ist gesetzt (isLockingPlan) UND kein Release
 * abgelaufen (releaseRequestedAt + 24h <= NOW() = released).
 */
export async function findActiveDeviceLock(
  deviceId: string,
  requestingUserId: string,
): Promise<DeviceRecord | null> {
  const db = usePrisma();

  const row = await db.userDevice.findFirst({
    where: {
      deviceId,
      // Gebunden an einen anderen User
      NOT: { userId: requestingUserId },
      // Binding existiert (Pro/Legend-Account)
      boundToPlan: { not: null },
    },
    select: {
      ...DEVICE_SELECT_WITH_LOCK,
    },
  });

  if (!row) return null;

  // Kein Lock wenn boundToPlan kein Locking-Plan (Sicherheitsnetz, eigentlich
  // schon durch oben gefiltert aber explizit prüfen)
  if (!isLockingPlan(row.boundToPlan)) return null;

  // Wenn Release-Request existiert und 24h abgelaufen → Lock ist released
  if (row.releaseRequestedAt) {
    const releaseAt = new Date(row.releaseRequestedAt.getTime() + 24 * 60 * 60 * 1000);
    if (releaseAt <= new Date()) return null;
  }

  // Lock ist aktiv
  return row;
}

/**
 * Bindet ein Device an den User (setzt boundToPlan).
 * Wird nach erfolgreichem Login aufgerufen wenn user.plan ein Locking-Plan ist.
 * Idempotent: wenn bereits gebunden → kein Update.
 */
export async function bindDeviceToUser(
  userId: string,
  deviceId: string,
  plan: string,
): Promise<void> {
  if (!isLockingPlan(plan)) return; // Free-User binden nicht

  const db = usePrisma();
  await db.userDevice.updateMany({
    where: {
      userId,
      deviceId,
      boundToPlan: null, // Nur setzen wenn noch nicht gebunden
    },
    data: {
      boundToPlan: plan,
    },
  });
}

/**
 * Request-Release: Original-User setzt release_requested_at = NOW().
 * Nach 24h ist der Lock automatisch gelöst (Lazy-Check in findActiveDeviceLock).
 * Gibt false zurück wenn Device nicht gefunden oder nicht dem User gehört.
 */
export async function requestDeviceRelease(
  userId: string,
  deviceId: string, // row-id (UUID), nicht deviceId-String
): Promise<boolean> {
  const db = usePrisma();
  const result = await db.userDevice.updateMany({
    where: {
      id: deviceId,
      userId, // Ownership-Check
      boundToPlan: { not: null }, // Muss gebunden sein um freizugeben
    },
    data: {
      releaseRequestedAt: new Date(),
    },
  });
  return result.count > 0;
}

/**
 * Cancel-Release: User zieht den Release-Request zurück.
 * Setzt release_requested_at zurück auf NULL.
 */
export async function cancelDeviceRelease(
  userId: string,
  deviceId: string, // row-id
): Promise<boolean> {
  const db = usePrisma();
  const result = await db.userDevice.updateMany({
    where: {
      id: deviceId,
      userId,
      releaseRequestedAt: { not: null }, // Muss offener Request sein
    },
    data: {
      releaseRequestedAt: null,
    },
  });
  return result.count > 0;
}

/**
 * Setzt lockNotifiedAt für Rate-Limiting der E-Mail-Notifications.
 */
export async function markDeviceLockNotified(rowId: string): Promise<void> {
  const db = usePrisma();
  await db.userDevice.update({
    where: { id: rowId },
    data: { lockNotifiedAt: new Date() },
  }).catch(() => {});
}

/**
 * Auto-Release: Alle Devices die 30 Tage nicht gesehen wurden UND noch
 * boundToPlan gesetzt haben → boundToPlan zurücksetzen.
 * Wird vom Cron-Plugin (device-lock-cron.ts) aufgerufen.
 *
 * Schützt vor verlorenen/verkauften/defekten Geräten ohne Customer-Support.
 */
export async function autoReleaseInactiveDevices(): Promise<number> {
  const db = usePrisma();
  const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

  const result = await db.userDevice.updateMany({
    where: {
      boundToPlan: { not: null },
      lastSeenAt: { lt: cutoff },
      // Nur wenn kein Release-Request bereits pending (würde in 24h ohnehin ablaufen)
    },
    data: {
      boundToPlan: null,
      releaseRequestedAt: null,
    },
  });

  return result.count;
}

const DEVICE_SELECT = {
  id: true,
  userId: true,
  deviceId: true,
  platform: true,
  model: true,
  name: true,
  osVersion: true,
  lastSeenAt: true,
  createdAt: true,
  boundToPlan: true,
  releaseRequestedAt: true,
  lockNotifiedAt: true,
} as const;

// Alias — identisch mit DEVICE_SELECT, explizit für Lock-Queries
const DEVICE_SELECT_WITH_LOCK = DEVICE_SELECT;

/** Liste aller Devices eines Users, aktuellstes zuerst. */
export async function listUserDevices(userId: string): Promise<DeviceRecord[]> {
  const db = usePrisma();
  return db.userDevice.findMany({
    where: { userId },
    orderBy: { lastSeenAt: "desc" },
    select: DEVICE_SELECT,
  });
}

/** Gibt das Device zurück wenn registriert; sonst null. */
export async function findUserDevice(
  userId: string,
  deviceId: string,
): Promise<DeviceRecord | null> {
  const db = usePrisma();
  return db.userDevice.findUnique({
    where: { userId_deviceId: { userId, deviceId } },
    select: DEVICE_SELECT,
  });
}

/**
 * Sucht nach einem "Merge-Kandidaten": existierendes Device des Users mit
 * identischem name + model das zuletzt innerhalb der letzten 30 Tage gesehen
 * wurde. Tritt auf wenn iOS IDFV sich nach Recovery-Restore ändert → neuer
 * deviceId, aber gleicher name ("iPhone von Chahine") + gleiches model
 * ("iPhone18,4").
 *
 * Bekannter Trade-off: zwei physisch identische iPhones mit gleichem Namen
 * würden gemerged. Edge-case, bewusst akzeptiert.
 */
async function findMergeCandidate(
  userId: string,
  name: string | null | undefined,
  model: string | null | undefined,
): Promise<DeviceRecord | null> {
  if (!name || !model) return null;

  const db = usePrisma();
  const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

  const candidate = await db.userDevice.findFirst({
    where: {
      userId,
      name,
      model,
      lastSeenAt: { gte: cutoff },
    },
    orderBy: { lastSeenAt: "desc" },
    select: DEVICE_SELECT,
  });

  return candidate;
}

/**
 * Idempotente Registrierung. Wenn Device bereits existiert: Touch lastSeenAt.
 * Wenn nicht existiert UND gleicher name+model innerhalb 30d: Merge (IDFV-Reset-Heuristik).
 * Wenn nicht existiert UND Limit erreicht: throw mit Liste der existierenden Devices.
 */
export async function registerDevice(opts: {
  userId: string;
  deviceId: string;
  platform: string;
  model?: string | null;
  name?: string | null;
  osVersion?: string | null;
  maxDevices: number;
}): Promise<{
  device: DeviceRecord;
  created: boolean;
  merged?: boolean;
}> {
  const db = usePrisma();

  // Idempotent: existiert das Device schon?
  const existing = await findUserDevice(opts.userId, opts.deviceId);
  if (existing) {
    // model/name/osVersion beim Re-Register aktualisieren — User-Agent oder OS-Version
    // kann sich geändert haben (App-Update, OS-Upgrade, iPad-Detection-Fix).
    const updated = await db.userDevice.update({
      where: { id: existing.id },
      data: {
        lastSeenAt: new Date(),
        ...(opts.model !== undefined && { model: opts.model }),
        ...(opts.name !== undefined && { name: opts.name }),
        ...(opts.osVersion !== undefined && { osVersion: opts.osVersion }),
      },
      select: DEVICE_SELECT,
    });
    return { device: updated, created: false };
  }

  // Merge-Heuristik: IDFV-Reset nach iOS Recovery-Restore.
  // Wenn name+model matchen und Device zuletzt < 30 Tage gesehen → merge statt insert.
  const mergeTarget = await findMergeCandidate(opts.userId, opts.name, opts.model);
  if (mergeTarget) {
    const merged = await db.userDevice.update({
      where: { id: mergeTarget.id },
      data: {
        deviceId: opts.deviceId, // neue IDFV übernehmen
        lastSeenAt: new Date(),
        ...(opts.osVersion !== undefined && { osVersion: opts.osVersion }),
      },
      select: DEVICE_SELECT,
    });
    return { device: merged, created: false, merged: true };
  }

  // Neues Device — Limit prüfen
  const count = await db.userDevice.count({ where: { userId: opts.userId } });
  if (count >= opts.maxDevices) {
    throw Object.assign(new Error("device_limit_reached"), {
      code: "DEVICE_LIMIT_REACHED",
      currentCount: count,
      max: opts.maxDevices,
    });
  }

  const created = await db.userDevice.create({
    data: {
      userId: opts.userId,
      deviceId: opts.deviceId,
      platform: opts.platform,
      model: opts.model ?? null,
      name: opts.name ?? null,
      osVersion: opts.osVersion ?? null,
    },
    select: DEVICE_SELECT,
  });
  return { device: created, created: true };
}

/** Touch lastSeenAt — wird in der Auth-Middleware bei jedem Request aufgerufen.
 * Optional: backfillt name/model/osVersion wenn Headers da sind (für Devices
 * die unter altem Build registriert wurden ohne diese Info). */
export async function touchDevice(
  userId: string,
  deviceId: string,
  info?: { name?: string | null; model?: string | null; osVersion?: string | null },
): Promise<void> {
  const db = usePrisma();
  const data: { lastSeenAt: Date; name?: string; model?: string; osVersion?: string } = {
    lastSeenAt: new Date(),
  };
  if (info?.name) data.name = info.name;
  if (info?.model) data.model = info.model;
  if (info?.osVersion) data.osVersion = info.osVersion;
  await db.userDevice
    .updateMany({
      where: { userId, deviceId },
      data,
    })
    .catch(() => {
      /* race-safe: wenn Device gerade gelöscht wurde */
    });
}

/** User entfernt ein eigenes Device — gibt Slot frei. */
export async function deleteUserDevice(userId: string, id: string): Promise<void> {
  const db = usePrisma();
  await db.userDevice.deleteMany({ where: { id, userId } });
}
