import type { Store } from "@/lib/data/store";
import type {
  Badge,
  CommunityMember,
  MarketplaceListing,
  SellerReview,
  UserBadge,
} from "@/lib/types";

/**
 * Grants any rule-based badge the person now qualifies for.
 * The PHP reference left this as a placeholder; here the rules actually run.
 */
export async function syncAutoBadges(store: Store, userId: string) {
  const badges = await store.small<Badge[]>("badges", []);
  const held = await store.small<UserBadge[]>("userBadges", []);

  // Nothing to work out if no badge has a rule on it.
  if (!badges.some((b) => b.active && b.rule)) return false;

  const theirPosts = await store.posts.byAuthor(userId);

  // No join date is stored, so age is measured from the earliest post.
  const firstPost = theirPosts
    .map((p) => new Date(p.createdAt).getTime())
    .sort((a, z) => a - z)[0];
  const joined = firstPost ?? Date.now();

  const listings = await store.small<MarketplaceListing[]>("marketplaceListings", []);
  const reviews = await store.small<SellerReview[]>("sellerReviews", []);
  const members = await store.small<CommunityMember[]>("communityMembers", []);

  const metrics: Record<NonNullable<Badge["rule"]>["metric"], number> = {
    posts: theirPosts.length,
    followers: await store.follows.countFollowers(userId),
    following: await store.follows.countFollowing(userId),
    comments: await store.comments.countByAuthorSince(userId, "0000"),
    likesReceived: theirPosts.reduce((sum, p) => sum + (p.likeCount ?? 0), 0),
    listings: listings.filter((l) => l.seller.id === userId).length,
    sales: listings.filter((l) => l.seller.id === userId && l.sold).length,
    reviews: reviews.filter((r) => r.sellerId === userId).length,
    accountAgeDays: Math.floor((Date.now() - joined) / 86400000),
    communities: members.filter((m) => m.userId === userId).length,
  };

  let changed = false;

  for (const badge of badges) {
    if (!badge.active || !badge.rule) continue;

    // Some badges are given for what someone is rather than what they've
    // done — an admin or a moderator has it by virtue of the role.
    if (badge.rule.metric.startsWith("role:")) {
      const role = badge.rule.metric.slice(5);
      const who = await store.users.get(userId);
      const has =
        (role === "admin" && who?.isAdmin) ||
        (role === "moderator" &&
          (who as { isModerator?: boolean } | undefined)?.isModerator);

      const already = held.find(
        (ub) => ub.badgeId === badge.id && ub.userId === userId
      );

      if (has && !already) {
        held.push({
          id: Math.random().toString(36).slice(2, 10),
          badgeId: badge.id,
          userId,
          awardedAt: new Date().toISOString(),
          automatic: true,
        });
        changed = true;
      }

      // Taken back if the role goes, or it outlives the reason for it.
      if (!has && already) {
        held.splice(held.indexOf(already), 1);
        changed = true;
      }

      continue;
    }

    const qualifies = metrics[badge.rule.metric] >= badge.rule.atLeast;
    const mine = held.find(
      (ub) => ub.badgeId === badge.id && ub.userId === userId
    );

    if (qualifies && !mine) {
      held.push({
        id: Math.random().toString(36).slice(2, 10),
        badgeId: badge.id,
        userId,
        awardedAt: new Date().toISOString(),
        automatic: true,
      });
      changed = true;
    }

    // Only automatic awards are withdrawn — an admin's grant stands.
    if (!qualifies && mine?.automatic) {
      held.splice(held.indexOf(mine), 1);
      changed = true;
    }
  }

  if (changed) await store.putSmall("userBadges", held);

  return changed;
}

/** The badges a person currently holds, ordered as the admin arranged them. */
export async function badgesFor(store: Store, userId: string) {
  const badges = await store.small<Badge[]>("badges", []);
  const held = await store.small<UserBadge[]>("userBadges", []);
  return held
    .filter((ub) => ub.userId === userId)
    .map((ub) => badges.find((b) => b.id === ub.badgeId))
    .filter((b): b is Badge => Boolean(b) && b!.active)
    .sort((a, b) => a.order - b.order);
}
