/**
 * Auth user repository — cloud-agnostic via @bytelyst/datastore.
 */

import bcrypt from 'bcryptjs';
import { getCollection } from '../../lib/datastore.js';
import { createHash } from 'node:crypto';
import type { UserDoc, PasswordResetTokenDoc, EmailVerificationDoc } from './types.js';

function usersCollection() {
  return getCollection<UserDoc>('users', '/id');
}

export async function getByEmail(email: string, productId: string): Promise<UserDoc | null> {
  return usersCollection().findOne({
    filter: { productId, email: email.toLowerCase() },
  });
}

export async function getById(id: string): Promise<UserDoc | null> {
  try {
    return await usersCollection().findById(id, id);
  } catch {
    return null;
  }
}

export async function create(user: UserDoc): Promise<UserDoc> {
  return usersCollection().create(user);
}

export async function updatePlan(
  id: string,
  productId: string,
  plan: UserDoc['plan']
): Promise<UserDoc | null> {
  try {
    const existing = await usersCollection().findById(id, id);
    if (!existing || existing.productId !== productId) return null;
    return await usersCollection().update(id, id, {
      plan,
      updatedAt: new Date().toISOString(),
    } as Partial<UserDoc>);
  } catch {
    return null;
  }
}

export async function updateLastLogin(id: string): Promise<void> {
  try {
    const now = new Date().toISOString();
    await usersCollection().update(id, id, {
      lastLoginAt: now,
      updatedAt: now,
    } as Partial<UserDoc>);
  } catch {
    // Non-critical — don't throw
  }
}

// ── Admin user management ────────────────────────────────────

export async function list(productId: string, limit = 100, offset = 0): Promise<UserDoc[]> {
  const results = await usersCollection().findMany({
    filter: { productId },
    sort: { createdAt: -1 },
    limit: limit + offset,
  });
  return results.slice(offset);
}

export async function count(productId: string): Promise<number> {
  return usersCollection().count({ productId });
}

export async function countByPlan(productId: string): Promise<Record<string, number>> {
  // Fetch all users and aggregate in-memory
  const docs = await usersCollection().findMany({
    filter: { productId },
  });
  const result: Record<string, number> = {};
  for (const doc of docs) {
    result[doc.plan] = (result[doc.plan] ?? 0) + 1;
  }
  return result;
}

export async function update(
  id: string,
  updates: Partial<
    Pick<
      UserDoc,
      | 'displayName'
      | 'role'
      | 'plan'
      | 'status'
      | 'phone'
      | 'bio'
      | 'avatarUrl'
      | 'mfaEnabled'
      | 'mfaMethods'
      | 'emailVerified'
    >
  >
): Promise<UserDoc | null> {
  try {
    return await usersCollection().update(id, id, {
      ...updates,
      updatedAt: new Date().toISOString(),
    } as Partial<UserDoc>);
  } catch {
    return null;
  }
}

export async function remove(id: string): Promise<boolean> {
  try {
    await usersCollection().delete(id, id);
    return true;
  } catch {
    return false;
  }
}

export async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, 12);
}

export async function verifyPassword(password: string, hash: string): Promise<boolean> {
  return bcrypt.compare(password, hash);
}

export async function updatePassword(id: string, newPasswordHash: string): Promise<boolean> {
  try {
    await usersCollection().update(id, id, {
      passwordHash: newPasswordHash,
      updatedAt: new Date().toISOString(),
    } as Partial<UserDoc>);
    return true;
  } catch {
    return false;
  }
}

export async function setEmailVerified(id: string, verified: boolean): Promise<boolean> {
  try {
    await usersCollection().update(id, id, {
      emailVerified: verified,
      updatedAt: new Date().toISOString(),
    } as Partial<UserDoc>);
    return true;
  } catch {
    return false;
  }
}

// ── OneAuth: Cross-product lookup ────────────────────────────

export async function getByEmailCrossProduct(email: string): Promise<UserDoc | null> {
  return usersCollection().findOne({
    filter: { email: email.toLowerCase() },
  });
}

// ── OneAuth: Backfill memberships migration ─────────────────

export async function backfillMemberships(productId: string): Promise<number> {
  const users = await usersCollection().findMany({ filter: { productId } });
  let updated = 0;
  for (const user of users) {
    if (user.memberships && user.memberships.length > 0) continue;
    const membership: import('./types.js').ProductMembership = {
      productId: user.productId,
      plan: user.plan,
      role: user.role,
      firstAccessAt: user.createdAt,
    };
    await usersCollection().update(user.id, user.id, {
      primaryProductId: user.productId,
      memberships: [membership],
      updatedAt: new Date().toISOString(),
    } as Partial<UserDoc>);
    updated++;
  }
  return updated;
}

// ── OneAuth: Lockout helpers ────────────────────────────────

export async function incrementFailedLogin(id: string, lockedUntil?: string): Promise<void> {
  try {
    const user = await usersCollection().findById(id, id);
    if (!user) return;
    const updates: Partial<UserDoc> = {
      failedLoginAttempts: (user.failedLoginAttempts ?? 0) + 1,
      updatedAt: new Date().toISOString(),
    };
    if (lockedUntil) {
      updates.lockedUntil = lockedUntil;
    }
    await usersCollection().update(id, id, updates);
  } catch {
    // best-effort
  }
}

export async function resetFailedLogin(id: string): Promise<void> {
  try {
    await usersCollection().update(id, id, {
      failedLoginAttempts: 0,
      lockedUntil: null,
      updatedAt: new Date().toISOString(),
    } as Partial<UserDoc>);
  } catch {
    // best-effort
  }
}

// ── Password Reset Tokens ────────────────────────────────────

function resetTokensCollection() {
  return getCollection<PasswordResetTokenDoc>('password_reset_tokens', '/productId');
}

export function hashToken(token: string): string {
  return createHash('sha256').update(token).digest('hex');
}

export async function createResetToken(doc: PasswordResetTokenDoc): Promise<PasswordResetTokenDoc> {
  return resetTokensCollection().create(doc);
}

export async function findResetToken(
  tokenHash: string,
  productId: string
): Promise<PasswordResetTokenDoc | null> {
  return resetTokensCollection().findOne({
    filter: { productId, tokenHash, usedAt: { $exists: false } },
  });
}

export async function markResetTokenUsed(id: string, productId: string): Promise<void> {
  try {
    await resetTokensCollection().update(id, productId, {
      usedAt: new Date().toISOString(),
    } as Partial<PasswordResetTokenDoc>);
  } catch {
    // best-effort
  }
}

// ── Email Verification Tokens ────────────────────────────────

function emailVerificationsCollection() {
  return getCollection<EmailVerificationDoc>('email_verifications', '/productId');
}

export async function createEmailVerification(
  doc: EmailVerificationDoc
): Promise<EmailVerificationDoc> {
  return emailVerificationsCollection().create(doc);
}

export async function findEmailVerification(
  tokenHash: string,
  productId: string
): Promise<EmailVerificationDoc | null> {
  return emailVerificationsCollection().findOne({
    filter: { productId, tokenHash, verifiedAt: { $exists: false } },
  });
}

export async function markEmailVerified(id: string, productId: string): Promise<void> {
  try {
    await emailVerificationsCollection().update(id, productId, {
      verifiedAt: new Date().toISOString(),
    } as Partial<EmailVerificationDoc>);
  } catch {
    // best-effort
  }
}
