import { fromBase64, generateX25519KeyPair, toBase64, wipe } from '../crypto/index.js';
import { bytesToPgHex, pgHexToBytes } from '../supabase/bytea.js';
import type { AppSupabaseClient } from '../supabase/client.js';
import type { DevicePlatform } from '../supabase/types.js';
import type { SecretStore } from './secure-storage.js';

export interface RegisterDeviceParams {
  name: string;          // user-facing, e.g. "Dennis Laptop"
  platform: DevicePlatform;
  publicKey: Uint8Array; // X25519 public key, 32 bytes
}

export interface DeviceRecord {
  id: string;
  name: string;
  platform: DevicePlatform;
  publicKey: Uint8Array;
  lastSeenAt: string;
}

export async function registerDevice(
  client: AppSupabaseClient,
  params: RegisterDeviceParams,
): Promise<DeviceRecord> {
  const { data: session } = await client.auth.getUser();
  if (!session.user) throw new Error('not authenticated');

  const { data, error } = await client
    .from('devices')
    .insert({
      user_id: session.user.id,
      name: params.name,
      platform: params.platform,
      public_key: bytesToPgHex(params.publicKey),
    })
    .select('id, name, platform, public_key, last_seen_at')
    .single();
  if (error) throw error;

  return {
    id: data.id,
    name: data.name,
    platform: data.platform,
    publicKey: pgHexToBytes(data.public_key),
    lastSeenAt: data.last_seen_at,
  };
}

export async function listOwnDevices(client: AppSupabaseClient): Promise<DeviceRecord[]> {
  const { data: session } = await client.auth.getUser();
  if (!session.user) throw new Error('not authenticated');

  const { data, error } = await client
    .from('devices')
    .select('id, name, platform, public_key, last_seen_at')
    .eq('user_id', session.user.id)
    .order('last_seen_at', { ascending: false });
  if (error) throw error;

  return data.map((row) => ({
    id: row.id,
    name: row.name,
    platform: row.platform,
    publicKey: pgHexToBytes(row.public_key),
    lastSeenAt: row.last_seen_at,
  }));
}

export async function touchDeviceLastSeen(
  client: AppSupabaseClient,
  deviceId: string,
): Promise<void> {
  const { error } = await client
    .from('devices')
    .update({ last_seen_at: new Date().toISOString() })
    .eq('id', deviceId);
  if (error) throw error;
}

// ---------------------------------------------------------------------------
// End-to-end device provisioning flow.
// ---------------------------------------------------------------------------

export interface ProvisionDeviceParams {
  client: AppSupabaseClient;
  secretStore: SecretStore;
  userId: string;
  name: string;
  platform: DevicePlatform;
}

export interface ProvisionResult {
  device: DeviceRecord;
  created: boolean;
}

function privateKeySecretName(userId: string, deviceId: string): string {
  return `chatapp.priv.${userId}.${deviceId}`;
}

// Creates a brand-new device: generates an X25519 keypair, registers the public
// half with Supabase, stores the private half in the platform secret store.
export async function provisionNewDevice({
  client,
  secretStore,
  userId,
  name,
  platform,
}: ProvisionDeviceParams): Promise<DeviceRecord> {
  const kp = await generateX25519KeyPair();

  const device = await registerDevice(client, {
    name,
    platform,
    publicKey: kp.publicKey,
  });

  try {
    await secretStore.setSecret(privateKeySecretName(userId, device.id), kp.privateKey);
  } finally {
    wipe(kp.privateKey);
  }
  return device;
}

// Loads the private key for `deviceId` from the secret store. Returns null if
// this install has never stored one.
export async function loadDevicePrivateKey(
  secretStore: SecretStore,
  userId: string,
  deviceId: string,
): Promise<Uint8Array | null> {
  return secretStore.getSecret(privateKeySecretName(userId, deviceId));
}

export async function forgetDevicePrivateKey(
  secretStore: SecretStore,
  userId: string,
  deviceId: string,
): Promise<void> {
  await secretStore.removeSecret(privateKeySecretName(userId, deviceId));
}

// Writes a device private key into the secret store. Used by the
// backup-restore flow to re-import a key generated on another machine.
export async function saveDevicePrivateKey(
  secretStore: SecretStore,
  userId: string,
  deviceId: string,
  privateKey: Uint8Array,
): Promise<void> {
  await secretStore.setSecret(privateKeySecretName(userId, deviceId), privateKey);
}

// Restores a device record from a backup by re-seeding the local private key
// store for an EXISTING server-side device row. Does NOT insert a new row —
// the original row is kept intact so conversation-key bundles stay valid.
// Throws when the server-side device was removed (the backup is then unusable;
// user must provision a fresh device and get conv-keys shared from another
// live device).
export async function restoreDeviceFromServerRecord(params: {
  client: AppSupabaseClient;
  secretStore: SecretStore;
  userId: string;
  deviceId: string;
  privateKey: Uint8Array;
}): Promise<DeviceRecord> {
  const { data: session } = await params.client.auth.getUser();
  if (!session.user) throw new Error('not authenticated');
  if (session.user.id !== params.userId) {
    throw new Error(
      'Backup is for a different account — sign in as the owner before restoring.',
    );
  }

  const { data: row, error } = await params.client
    .from('devices')
    .select('id, name, platform, public_key, last_seen_at')
    .eq('id', params.deviceId)
    .eq('user_id', params.userId)
    .maybeSingle();
  if (error) throw error;
  if (!row) {
    throw new Error(
      'Device record not found on server — it was removed. Backup is no longer valid; register a new device instead.',
    );
  }

  await saveDevicePrivateKey(
    params.secretStore,
    params.userId,
    params.deviceId,
    params.privateKey,
  );

  return {
    id: row.id,
    name: row.name,
    platform: row.platform,
    publicKey: pgHexToBytes(row.public_key),
    lastSeenAt: row.last_seen_at,
  };
}

// Lightweight helpers for platforms that want to cache their current device id
// in JSON storage (separate from the secret store, which only holds raw bytes).
export const DEVICE_ID_STORAGE_KEY_PREFIX = 'chatapp.device_id';

export function deviceIdStorageKey(userId: string): string {
  return `${DEVICE_ID_STORAGE_KEY_PREFIX}.${userId}`;
}

// Intentional re-exports so app layers only need @chat-app/shared/auth.
export type { SecretStore } from './secure-storage.js';
export { toBase64 as base64FromBytes, fromBase64 as bytesFromBase64 };
