import { createClient } from '@supabase/supabase-js';
import type { H3Event } from 'h3';
import { isAdminUser } from '../db/admin';
import { findUserDevice, registerDevice, touchDevice } from '../db/devices';
import { getProfile } from '../db/profile';
import { getPlanLimits } from './plan-features';

const TOUCH_THROTTLE_MS = 60_000; // touch lastSeenAt höchstens 1×/min pro Device

export interface RequireUserOptions {
  /** Bootstrap-Endpoints (z.B. /api/devices/register) brauchen kein Device-Binding */
  skipDeviceCheck?: boolean;
}

export async function requireUser(
  event: H3Event,
  opts: RequireUserOptions = {},
) {
  const authHeader = getHeader(event, 'authorization');
  let token = authHeader?.replace('Bearer ', '');

  if (!token) {
    const query = getQuery(event);
    token = query.token as string;
  }

  if (!token) {
    throw createError({ statusCode: 401, message: 'Nicht eingeloggt' });
  }

  const config = useRuntimeConfig(event);
  const supabaseCfg =
    (config as any).public?.supabase ?? (config as any).supabase;
  const client = createClient(
    supabaseCfg.url as string,
    supabaseCfg.key as string,
    { global: { headers: { Authorization: `Bearer ${token}` } } },
  );

  const {
    data: { user },
    error,
  } = await client.auth.getUser();

  if (error || !user) {
    throw createError({ statusCode: 401, message: 'Nicht eingeloggt' });
  }

  if (opts.skipDeviceCheck) return user;

  // Device-Binding: nur enforced wenn Client einen x-device-id Header schickt.
  // Web-Clients ohne Header laufen weiter wie bisher.
  const deviceId = getHeader(event, 'x-device-id');
  if (!deviceId) return user;

  // Frontend sendet name/model/osVersion als x-device-* Headers (URL-encoded)
  function readEncoded(name: string): string | null {
    const raw = getHeader(event, name);
    if (!raw) return null;
    try { return decodeURIComponent(raw); } catch { return raw; }
  }
  const deviceName = readEncoded('x-device-name');
  const deviceModel = readEncoded('x-device-model');
  const deviceOs = readEncoded('x-device-os');

  const existing = await findUserDevice(user.id, deviceId);
  if (existing) {
    // Touch lastSeenAt + Backfill name/model/osVersion falls noch nicht gesetzt,
    // throttled auf 1×/min — fire-and-forget
    if (Date.now() - existing.lastSeenAt.getTime() > TOUCH_THROTTLE_MS) {
      touchDevice(user.id, deviceId, {
        name: deviceName,
        model: deviceModel,
        osVersion: deviceOs,
      }).catch(() => {});
    }
    return user;
  }

  // Device unbekannt — Auto-Register (ohne Frontend-explicit-call)
  // Plan-Limit holen
  const profile = await getProfile(user.id);
  const limits = getPlanLimits(profile?.plan ?? 'free');
  const platform = getHeader(event, 'x-platform') ?? 'unknown';

  try {
    await registerDevice({
      userId: user.id,
      deviceId,
      platform,
      name: deviceName,
      model: deviceModel,
      osVersion: deviceOs,
      maxDevices: limits.maxAppDevices,
    });
    return user;
  } catch (err: any) {
    if (err.code === 'DEVICE_LIMIT_REACHED') {
      // Devices-Liste mitschicken damit das Frontend-Modal die Geräte
      // anzeigen + Freigeben-Buttons rendern kann (auch wenn der 403
      // nicht vom register-Endpoint sondern vom auth-Middleware kommt).
      const { listUserDevices } = await import('../db/devices');
      const devices = await listUserDevices(user.id);
      throw createError({
        statusCode: 403,
        statusMessage: 'device_limit_reached',
        data: {
          error: 'device_limit_reached',
          max: limits.maxAppDevices,
          plan: profile?.plan ?? 'free',
          devices,
        },
      });
    }
    throw err;
  }
}

/**
 * requireAdmin — wirft 401 wenn nicht eingeloggt, 403 wenn nicht in admin_users.
 * Wraps requireUser mit skipDeviceCheck=true (Admin-App hat kein Device-Binding).
 */
export async function requireAdmin(event: H3Event) {
  const user = await requireUser(event, { skipDeviceCheck: true });

  const admin = await isAdminUser(user.id);
  if (!admin) {
    throw createError({ statusCode: 403, message: 'Kein Admin-Zugang' });
  }

  return user;
}