/**
 * Ranking and Fair Distribution Service
 *
 * Provides consistency-first property scoring, balanced verification trust,
 * and anti-monopoly fair distribution across creators to prevent single-user feed domination.
 */

export interface CreatorConsistencyStats {
  publisherKey: string;
  totalActiveListings: number;
  mostRecentListingDate: string | null;
  hasRecentActivity: boolean; // within 30 days
  daysSinceLastListing: number;
}

export interface PropertyRankingScores {
  consistencyScore: number;  // 0 - 100
  verificationScore: number; // 0 - 100
  freshnessScore: number;    // 0 - 100
  featuredBonus: number;     // 0 - 25
  compositeScore: number;    // Combined weighted score
}

export interface DistributionOptions {
  /** Maximum number of listings a single publisher can have in the top 10 */
  topTenPublisherCap?: number;
  /** Minimum slots spacing between listings from the same publisher */
  minSpacingGap?: number;
  /** Decay factor applied to sequential listings from the same publisher */
  repetitionDecayFactor?: number;
}

/**
 * Returns a unique key representing the publisher (agency or creator) of a property.
 * Groups by agency ID/name first (so multi-agent brokerages don't bypass caps),
 * then falls back to user/creator ID.
 */
export function getListingPublisherKey(p: any): string {
  if (!p) return "unknown";
  return (
    p.agency_id ||
    p.agency?.id ||
    (p.agency?.name ? `agency:${p.agency.name.toLowerCase().trim()}` : null) ||
    p.created_by_user_id ||
    p.creator?.id ||
    (p.creator?.first_name ? `user:${p.creator.first_name.toLowerCase().trim()}` : null) ||
    "anonymous"
  );
}

/**
 * Extracts aggregate consistency statistics across all active listings in the catalog.
 */
export function buildPublisherStatsMap(properties: any[]): Map<string, CreatorConsistencyStats> {
  const statsMap = new Map<string, { count: number; latestTimestamp: number }>();
  const now = Date.now();

  for (const p of properties) {
    const key = getListingPublisherKey(p);
    const dateStr = p.created_at || p.published_at || p.listedDate || p.updated_at;
    const timestamp = dateStr ? new Date(dateStr).getTime() : 0;

    const existing = statsMap.get(key);
    if (!existing) {
      statsMap.set(key, { count: 1, latestTimestamp: timestamp });
    } else {
      existing.count += 1;
      if (timestamp > existing.latestTimestamp) {
        existing.latestTimestamp = timestamp;
      }
    }
  }

  const result = new Map<string, CreatorConsistencyStats>();
  const MS_PER_DAY = 1000 * 60 * 60 * 24;

  for (const [key, val] of statsMap.entries()) {
    const daysSince = val.latestTimestamp > 0 
      ? Math.max(0, Math.floor((now - val.latestTimestamp) / MS_PER_DAY))
      : 999;

    result.set(key, {
      publisherKey: key,
      totalActiveListings: val.count,
      mostRecentListingDate: val.latestTimestamp > 0 ? new Date(val.latestTimestamp).toISOString() : null,
      hasRecentActivity: daysSince <= 30,
      daysSinceLastListing: daysSince,
    });
  }

  return result;
}

/**
 * Calculates a 0 - 100 consistency score for a creator/publisher.
 * Rewards active cadence, regular recent posts, and profile completeness.
 */
export function calculateUserConsistencyScore(
  property: any,
  publisherStats?: CreatorConsistencyStats
): number {
  let score = 0;

  // 1. Cadence & Active Inventory (Max 35 pts)
  // Rewards having maintained active listings without letting them go stale
  const count = publisherStats?.totalActiveListings ?? 1;
  if (count >= 5) score += 35;
  else if (count >= 3) score += 28;
  else if (count >= 2) score += 20;
  else score += 12;

  // 2. Recent Platform Activity (Max 35 pts)
  const daysSince = publisherStats?.daysSinceLastListing ?? 999;
  if (daysSince <= 7) score += 35;
  else if (daysSince <= 21) score += 28;
  else if (daysSince <= 45) score += 20;
  else if (daysSince <= 90) score += 10;
  else score += 0;

  // 3. Profile Completeness & Direct Reachability (Max 20 pts)
  const creator = property.creator || {};
  const agency = property.agency || {};

  const hasWhatsapp = Boolean(creator.whatsapp_number || agency.whatsapp_number || property.whatsapp);
  const hasPhone = Boolean(creator.phone_number || agency.phone || property.phone);
  const hasAvatar = Boolean(creator.avatar_url || agency.logo_url);
  const hasName = Boolean(creator.first_name || agency.name);

  if (hasWhatsapp) score += 8;
  if (hasPhone) score += 4;
  if (hasAvatar) score += 4;
  if (hasName) score += 4;

  // 4. Reliable Track Record Bonus (Max 10 pts)
  if (property.is_verified || agency.is_verified) {
    score += 10;
  }

  return Math.min(100, score);
}

/**
 * Evaluates the verification trust tier (0 - 100 pts).
 * CAC Verified Agency: 100 pts
 * Registered Agency / Pro Agent: 75 pts
 * Verified Individual / Broker: 50 pts
 * Standard User: 25 pts
 */
export function calculateVerificationScore(p: any): number {
  if (p.agency?.is_verified) return 100;
  if (p.agency?.name || p.assignedAgent?.agencyName) return 75;
  if (p.creator?.first_name || p.assignedAgent?.name) return 50;
  return 25;
}

/**
 * Evaluates listing freshness based on created/updated date (0 - 100 pts).
 */
export function calculateFreshnessScore(p: any): number {
  const dateStr = p.created_at || p.published_at || p.listedDate || p.updated_at;
  if (!dateStr) return 20;

  const ageMs = Date.now() - new Date(dateStr).getTime();
  const ageDays = Math.max(0, Math.floor(ageMs / (1000 * 60 * 60 * 24)));

  if (ageDays <= 7) return 100;
  if (ageDays <= 30) return 75;
  if (ageDays <= 60) return 50;
  if (ageDays <= 90) return 25;
  return 10;
}

/**
 * Calculates multi-factor ranking score for a property.
 * Consistency weight: 40%
 * Verification rank: 25%
 * Freshness/Recency: 25%
 * Featured Boost: 10%
 */
export function calculatePropertyRankScore(
  property: any,
  publisherStatsMap?: Map<string, CreatorConsistencyStats>
): PropertyRankingScores {
  const pubKey = getListingPublisherKey(property);
  const stats = publisherStatsMap?.get(pubKey);

  const consistencyScore = calculateUserConsistencyScore(property, stats);
  const verificationScore = calculateVerificationScore(property);
  const freshnessScore = calculateFreshnessScore(property);
  const featuredBonus = property.is_featured ? 25 : 0;

  const compositeScore =
    consistencyScore * 0.40 +
    verificationScore * 0.25 +
    freshnessScore * 0.25 +
    featuredBonus * 0.10;

  return {
    consistencyScore,
    verificationScore,
    freshnessScore,
    featuredBonus,
    compositeScore: Math.round(compositeScore * 100) / 100,
  };
}

/**
 * Fair Anti-Monopoly Feed Distribution
 *
 * Prevents any single user or agency from dominating the top of the marketplace.
 * Uses rank decay on sequential listings per publisher and an interleaving spacing pass.
 */
export function distributeListingsFairly(
  listings: any[],
  publisherStatsMap?: Map<string, CreatorConsistencyStats>,
  options: DistributionOptions = {}
): any[] {
  if (!listings || listings.length <= 1) return listings;

  const {
    topTenPublisherCap = 2,
    minSpacingGap = 1,
    repetitionDecayFactor = 0.35,
  } = options;

  // 1. Attach scores to each listing
  const scored = listings.map((p) => {
    const scores = calculatePropertyRankScore(p, publisherStatsMap);
    return {
      property: p,
      publisherKey: getListingPublisherKey(p),
      scores,
      baseScore: scores.compositeScore,
      timestamp: new Date(p.created_at || p.published_at || p.listedDate || 0).getTime(),
    };
  });

  // 2. Group listings by publisher, sorted internally by highest merit
  const publisherGroups = new Map<string, typeof scored>();
  for (const item of scored) {
    const group = publisherGroups.get(item.publisherKey) || [];
    group.push(item);
    publisherGroups.set(item.publisherKey, group);
  }

  // Sort each group internally by base score descending (then timestamp)
  for (const group of publisherGroups.values()) {
    group.sort((a, b) => b.baseScore - a.baseScore || b.timestamp - a.timestamp);
  }

  // Count unique publishers
  const uniquePublisherCount = publisherGroups.size;

  // 3. Flatten with rank decay:
  // For the k-th listing from publisher P (k = 0, 1, 2...):
  // effectiveScore = baseScore / (1 + decay * k)
  const candidateList: Array<{
    item: (typeof scored)[0];
    effectiveScore: number;
    listingIndexForPublisher: number;
  }> = [];

  for (const group of publisherGroups.values()) {
    group.forEach((item, k) => {
      const effectiveScore = item.baseScore / (1 + repetitionDecayFactor * k);
      candidateList.push({
        item,
        effectiveScore,
        listingIndexForPublisher: k,
      });
    });
  }

  // Sort candidates by effective score descending
  candidateList.sort((a, b) => b.effectiveScore - a.effectiveScore || b.item.timestamp - a.item.timestamp);

  // 4. Interleaving pass with spacing & top-10 anti-monopoly caps
  const distributed: any[] = [];
  const remaining = [...candidateList];
  const publisherCountInTopTen = new Map<string, number>();

  while (remaining.length > 0) {
    let chosenIdx = -1;

    for (let i = 0; i < remaining.length; i++) {
      const cand = remaining[i];
      const pubKey = cand.item.publisherKey;
      const currentPos = distributed.length;

      // Check Top-10 Cap: prioritize diverse publishers if any are available in remaining
      if (currentPos < 10) {
        const inTopTen = publisherCountInTopTen.get(pubKey) || 0;
        if (inTopTen >= topTenPublisherCap) {
          const hasAlternative = remaining.some((c) => c.item.publisherKey !== pubKey);
          if (hasAlternative) {
            continue; // yield slot to another publisher
          }
        }
      }

      // Check consecutive spacing gap: avoid placing same publisher back-to-back
      if (minSpacingGap > 0 && currentPos > 0 && uniquePublisherCount > 1) {
        const lastPlacedPubKey = getListingPublisherKey(distributed[currentPos - 1]);
        if (lastPlacedPubKey === pubKey && remaining.length > 1) {
          // Check if there is another alternative candidate further down
          const hasAlternative = remaining.some((c) => c.item.publisherKey !== pubKey);
          if (hasAlternative) {
            continue;
          }
        }
      }

      chosenIdx = i;
      break;
    }

    // Fallback: If all remaining candidates were skipped due to constraints, take the top available candidate
    if (chosenIdx === -1) {
      chosenIdx = 0;
    }

    const [selected] = remaining.splice(chosenIdx, 1);
    const pubKey = selected.item.publisherKey;

    if (distributed.length < 10) {
      publisherCountInTopTen.set(pubKey, (publisherCountInTopTen.get(pubKey) || 0) + 1);
    }

    distributed.push(selected.item.property);
  }

  return distributed;
}
