const crypto = require("crypto");
const { db } = require("./db");

function normalizeUsername(name) {
  return (name || "").trim();
}

function isUsernameAvailable(name, excludeUserId) {
  const trimmed = normalizeUsername(name);
  if (!trimmed) {
    return false;
  }
  const row = db
    .prepare(
      "SELECT id FROM user_profiles WHERE internal_username = ? LIMIT 1"
    )
    .get(trimmed);
  if (!row) {
    return true;
  }
  return excludeUserId ? row.id === excludeUserId : false;
}

function generateUniqueUsername(primary, fallback, excludeUserId) {
  const candidates = [primary, fallback]
    .map(normalizeUsername)
    .filter(Boolean);

  for (const candidate of candidates) {
    if (isUsernameAvailable(candidate, excludeUserId)) {
      return candidate;
    }
  }

  const base = candidates[0] || "user";
  let suffix = 2;
  let attempt = `${base}-${suffix}`;
  while (!isUsernameAvailable(attempt, excludeUserId)) {
    suffix += 1;
    attempt = `${base}-${suffix}`;
  }
  return attempt;
}

function getUserProfileById(userId) {
  return db
    .prepare(
      "SELECT id, internal_username, username_updated_at FROM user_profiles WHERE id = ?"
    )
    .get(userId);
}

function getUserIdentities(userId) {
  return db
    .prepare(
      "SELECT provider, provider_user_id, display_name, avatar FROM user_identities WHERE user_id = ?"
    )
    .all(userId);
}

function getIdentity(provider, providerUserId) {
  return db
    .prepare(
      "SELECT user_id, provider, provider_user_id, display_name, avatar FROM user_identities WHERE provider = ? AND provider_user_id = ?"
    )
    .get(provider, providerUserId);
}

function createUserProfile(internalUsername) {
  const now = Date.now();
  const id = crypto.randomUUID();
  db.prepare(
    "INSERT INTO user_profiles (id, internal_username, created_at, updated_at) VALUES (?, ?, ?, ?)"
  ).run(id, internalUsername, now, now);
  return getUserProfileById(id);
}

function updateIdentity(userId, provider, providerUserId, displayName, avatar) {
  const now = Date.now();
  db.prepare(
    "INSERT INTO user_identities (user_id, provider, provider_user_id, display_name, avatar, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?) " +
      "ON CONFLICT(provider, provider_user_id) DO UPDATE SET user_id = excluded.user_id, display_name = excluded.display_name, avatar = excluded.avatar, updated_at = excluded.updated_at"
  ).run(
    userId,
    provider,
    providerUserId,
    displayName || null,
    avatar || null,
    now,
    now
  );
}

function ensureUserForIdentity({ provider, providerUserId, displayName, avatar, fallbackName }) {
  const existing = getIdentity(provider, providerUserId);
  if (existing) {
    updateIdentity(
      existing.user_id,
      provider,
      providerUserId,
      displayName,
      avatar
    );
    return getUserProfileById(existing.user_id);
  }

  const internalUsername = generateUniqueUsername(
    displayName,
    fallbackName
  );
  const profile = createUserProfile(internalUsername);
  updateIdentity(
    profile.id,
    provider,
    providerUserId,
    displayName,
    avatar
  );
  return profile;
}

function mergeUsers(fromUserId, toUserId) {
  if (fromUserId === toUserId) {
    return toUserId;
  }
  db.transaction(() => {
    db.prepare("UPDATE user_identities SET user_id = ? WHERE user_id = ?").run(
      toUserId,
      fromUserId
    );
    db.prepare("UPDATE stats SET user_id = ? WHERE user_id = ?").run(
      toUserId,
      fromUserId
    );
    const accounts = db
      .prepare("SELECT id, provider FROM linked_accounts WHERE user_id = ?")
      .all(fromUserId);
    for (const account of accounts) {
      const existing = db
        .prepare(
          "SELECT id FROM linked_accounts WHERE user_id = ? AND provider = ?"
        )
        .get(toUserId, account.provider);
      if (existing) {
        db.prepare("DELETE FROM linked_accounts WHERE id = ?").run(account.id);
      } else {
        db.prepare("UPDATE linked_accounts SET user_id = ? WHERE id = ?").run(
          toUserId,
          account.id
        );
      }
    }
    db.prepare("DELETE FROM user_profiles WHERE id = ?").run(fromUserId);
  })();
  return toUserId;
}

function linkIdentityToUser({ userId, provider, providerUserId, displayName, avatar }) {
  const existing = getIdentity(provider, providerUserId);
  let targetUserId = userId;
  if (existing && existing.user_id !== userId) {
    targetUserId = mergeUsers(existing.user_id, userId);
  }
  updateIdentity(
    targetUserId,
    provider,
    providerUserId,
    displayName,
    avatar
  );
  return getUserProfileById(targetUserId);
}

function updateInternalUsername(userId, desiredName) {
  const trimmed = normalizeUsername(desiredName);
  if (!trimmed) {
    return { ok: false, reason: "Username cannot be empty." };
  }
  if (!isUsernameAvailable(trimmed, userId)) {
    return { ok: false, reason: "That username is already taken." };
  }
  db.prepare(
    "UPDATE user_profiles SET internal_username = ?, updated_at = ?, username_updated_at = ? WHERE id = ?"
  ).run(trimmed, Date.now(), Date.now(), userId);
  return { ok: true, username: trimmed };
}

function listUsersWithIdentities() {
  const profiles = db
    .prepare(
      "SELECT id, internal_username, created_at, updated_at FROM user_profiles ORDER BY internal_username"
    )
    .all();
  const identitiesByUser = new Map();
  const identities = db
    .prepare(
      "SELECT user_id, provider, provider_user_id, display_name FROM user_identities ORDER BY provider"
    )
    .all();
  for (const identity of identities) {
    if (!identitiesByUser.has(identity.user_id)) {
      identitiesByUser.set(identity.user_id, []);
    }
    identitiesByUser.get(identity.user_id).push(identity);
  }
  return profiles.map((profile) => ({
    ...profile,
    identities: identitiesByUser.get(profile.id) || []
  }));
}

function searchKnownUsers(query, { limit = 30 } = {}) {
  const value = String(query || "").trim().replace(/[%_]/g, "");
  if (value.length < 2) {
    return [];
  }
  const pattern = `%${value}%`;
  const rows = db
    .prepare(
      "SELECT DISTINCT p.id, p.internal_username, i.provider, i.provider_user_id, i.display_name " +
        "FROM user_profiles p LEFT JOIN user_identities i ON i.user_id = p.id " +
        "WHERE p.id LIKE ? OR p.internal_username LIKE ? OR i.provider_user_id LIKE ? OR i.display_name LIKE ? " +
        "ORDER BY p.internal_username LIMIT ?"
    )
    .all(pattern, pattern, pattern, pattern, Math.max(1, Math.min(Number(limit) || 30, 50)));
  return [...rows.reduce((map, row) => {
    const user = map.get(row.id) || {
      id: row.id,
      username: row.internal_username,
      internal_username: row.internal_username,
      identities: []
    };
    if (row.provider) {
      user.identities.push({
        provider: row.provider,
        provider_user_id: row.provider_user_id,
        display_name: row.display_name
      });
    }
    map.set(row.id, user);
    return map;
  }, new Map()).values()];
}

module.exports = {
  generateUniqueUsername,
  getUserProfileById,
  getUserIdentities,
  ensureUserForIdentity,
  linkIdentityToUser,
  updateInternalUsername,
  listUsersWithIdentities,
  searchKnownUsers
};
