/**
 * Broadcast targeting engine — evaluates user matching against broadcast targets
 * @module broadcasts/targeting
 */

import { BroadcastTarget, TargetingContext, UserSegment, Platform } from './types.js';

// FNV-1a 32-bit hash (same as feature flags module)
function fnv1a32(str: string): number {
  let hash = 0x811c9dc5;
  for (let i = 0; i < str.length; i++) {
    hash ^= str.charCodeAt(i);
    hash = Math.imul(hash, 0x01000193);
  }
  return hash >>> 0; // Convert to unsigned 32-bit
}

/**
 * Evaluate if a user matches a broadcast target
 * Uses AND logic across all specified criteria
 */
export function evaluateTarget(
  target: BroadcastTarget,
  context: TargetingContext
): boolean {
  // User segments (OR within array - match any segment)
  if (target.userSegments && target.userSegments.length > 0) {
    const hasMatchingSegment = target.userSegments.some((seg) =>
      context.userSegments.includes(seg)
    );
    if (!hasMatchingSegment) return false;
  }

  // Platform targeting
  if (target.platforms && target.platforms.length > 0) {
    if (!target.platforms.includes(context.platform)) {
      return false;
    }
  }

  // App version range (semver comparison)
  if (target.appVersionMin) {
    if (compareSemver(context.appVersion, target.appVersionMin) < 0) {
      return false;
    }
  }
  if (target.appVersionMax) {
    if (compareSemver(context.appVersion, target.appVersionMax) > 0) {
      return false;
    }
  }

  // OS version range
  if (target.osVersionMin) {
    if (compareSemver(context.osVersion, target.osVersionMin) < 0) {
      return false;
    }
  }
  if (target.osVersionMax) {
    if (compareSemver(context.osVersion, target.osVersionMax) > 0) {
      return false;
    }
  }

  // Geo targeting - country
  if (target.countryCodes && target.countryCodes.length > 0) {
    if (!context.countryCode || !target.countryCodes.includes(context.countryCode)) {
      return false;
    }
  }

  // Geo targeting - region (e.g., "US-CA" for California)
  if (target.regionCodes && target.regionCodes.length > 0) {
    const userRegion = context.regionCode;
    if (!userRegion || !target.regionCodes.includes(userRegion)) {
      return false;
    }
  }

  // Percentage rollout (FNV-1a hash for deterministic bucketing)
  if (target.percentageRollout !== undefined && target.percentageRollout < 100) {
    // Use userId only as seed for consistent bucketing
    // This ensures when rollout % increases, same users stay included
    const hash = fnv1a32(context.userId);
    const bucket = hash % 100;
    if (bucket >= target.percentageRollout) {
      return false;
    }
  }

  // Specific user IDs (explicit override for testing)
  if (target.specificUserIds && target.specificUserIds.length > 0) {
    if (!target.specificUserIds.includes(context.userId)) {
      return false;
    }
  }

  return true;
}

/**
 * Compare two semantic version strings
 * Returns: -1 if v1 < v2, 0 if equal, 1 if v1 > v2
 */
function compareSemver(v1: string, v2: string): number {
  const parts1 = v1.split('.').map(Number);
  const parts2 = v2.split('.').map(Number);

  const maxLength = Math.max(parts1.length, parts2.length);

  for (let i = 0; i < maxLength; i++) {
    const p1 = parts1[i] ?? 0;
    const p2 = parts2[i] ?? 0;

    if (p1 < p2) return -1;
    if (p1 > p2) return 1;
  }

  return 0;
}

/**
 * Build Cosmos query filters from target (for reach estimation)
 * Returns parameterized query components
 */
export function buildTargetQuery(
  target: BroadcastTarget
): { whereClauses: string[]; parameters: { name: string; value: unknown }[] } {
  const whereClauses: string[] = [];
  const parameters: { name: string; value: unknown }[] = [];

  // Platform filter
  if (target.platforms && target.platforms.length > 0) {
    whereClauses.push('(c.platform IN @platforms)');
    parameters.push({ name: '@platforms', value: target.platforms });
  }

  // App version range
  if (target.appVersionMin) {
    whereClauses.push('c.appVersion >= @appVersionMin');
    parameters.push({ name: '@appVersionMin', value: target.appVersionMin });
  }
  if (target.appVersionMax) {
    whereClauses.push('c.appVersion <= @appVersionMax');
    parameters.push({ name: '@appVersionMax', value: target.appVersionMax });
  }

  // OS version range
  if (target.osVersionMin) {
    whereClauses.push('c.osVersion >= @osVersionMin');
    parameters.push({ name: '@osVersionMin', value: target.osVersionMin });
  }
  if (target.osVersionMax) {
    whereClauses.push('c.osVersion <= @osVersionMax');
    parameters.push({ name: '@osVersionMax', value: target.osVersionMax });
  }

  // Geo filters
  if (target.countryCodes && target.countryCodes.length > 0) {
    whereClauses.push('(c.countryCode IN @countryCodes)');
    parameters.push({ name: '@countryCodes', value: target.countryCodes });
  }

  // Specific user IDs (for testing)
  if (target.specificUserIds && target.specificUserIds.length > 0) {
    whereClauses.push('(c.id IN @userIds)');
    parameters.push({ name: '@userIds', value: target.specificUserIds });
  }

  return { whereClauses, parameters };
}

/**
 * Extract targeting context from request headers/payload
 * Used by routes to build context from incoming requests
 */
export function extractTargetingContext(
  headers: Record<string, string | undefined>,
  jwtPayload: { sub: string; segments?: UserSegment[] } | undefined
): TargetingContext {
  const userId = jwtPayload?.sub ?? headers['x-user-id'] ?? 'anonymous';
  const segments = jwtPayload?.segments ?? ['free'];

  return {
    userId,
    productId: headers['x-product-id'] ?? 'unknown',
    platform: (headers['x-platform'] as Platform) ?? 'web',
    appVersion: headers['x-app-version'] ?? '0.0.0',
    osVersion: headers['x-os-version'] ?? '0.0.0',
    countryCode: headers['x-country-code'],
    regionCode: headers['x-region-code'],
    userSegments: segments as UserSegment[],
  };
}
