import type { Resource, ScoreBreakdown, SkillEntry, StaffingSuggestion } from "@capakraken/shared";
import { SCORE_WEIGHTS } from "@capakraken/shared";

export interface SkillMatchInput {
  requiredSkills: string[];
  preferredSkills?: string[];
  resources: (Pick<Resource, "id" | "displayName" | "eid" | "skills" | "lcrCents" | "chargeabilityTarget"> & {
    currentUtilizationPercent: number;
    hasAvailabilityConflicts: boolean;
    conflictDays?: string[];
  })[];
  budgetLcrCentsPerHour?: number;
}

/**
 * Computes a skill match score (0-100) for a resource against required skills.
 * Factors in proficiency levels and coverage.
 */
export function computeSkillScore(
  resourceSkills: SkillEntry[],
  requiredSkills: string[],
  preferredSkills: string[] = [],
): { score: number; matchedRequired: string[]; matchedPreferred: string[]; missing: string[] } {
  if (requiredSkills.length === 0) {
    return { score: 100, matchedRequired: [], matchedPreferred: [], missing: [] };
  }

  const resourceSkillMap = new Map<string, SkillEntry>();
  for (const skill of resourceSkills) {
    resourceSkillMap.set(skill.skill.toLowerCase(), skill);
  }

  const matchedRequired: string[] = [];
  const missing: string[] = [];

  // Required skills contribute 70% of score
  let requiredScore = 0;
  for (const req of requiredSkills) {
    const match = resourceSkillMap.get(req.toLowerCase());
    if (match) {
      matchedRequired.push(req);
      // Proficiency 1-5, normalized to 0-1, weighted
      requiredScore += (match.proficiency / 5) * (70 / requiredSkills.length);
    } else {
      missing.push(req);
    }
  }

  // Preferred skills contribute 30% as a bonus on top of required (70%).
  // If no preferred skills defined, required score is normalized to 100%.
  const matchedPreferred: string[] = [];
  let preferredScore = 0;
  if (preferredSkills.length > 0) {
    for (const pref of preferredSkills) {
      const match = resourceSkillMap.get(pref.toLowerCase());
      if (match) {
        matchedPreferred.push(pref);
        preferredScore += (match.proficiency / 5) * (30 / preferredSkills.length);
      }
    }
  } else {
    // No preferred skills: normalize required score to 100-point scale
    return {
      score: Math.min(100, Math.round((requiredScore / 70) * 100)),
      matchedRequired,
      matchedPreferred: [],
      missing,
    };
  }

  return {
    score: Math.min(100, Math.round(requiredScore + preferredScore)),
    matchedRequired,
    matchedPreferred,
    missing,
  };
}

/**
 * Computes availability score (0-100) based on whether conflicts exist.
 */
export function computeAvailabilityScore(
  hasConflicts: boolean,
  conflictDayCount = 0,
): number {
  if (!hasConflicts) return 100;
  // Reduce score by 10 per conflict day, minimum 0
  return Math.max(0, 100 - conflictDayCount * 10);
}

/**
 * Computes cost score (0-100). Lower LCR = higher score.
 * Normalized against a budget target.
 */
export function computeCostScore(
  resourceLcrCents: number,
  budgetLcrCentsPerHour?: number,
): number {
  if (!budgetLcrCentsPerHour || budgetLcrCentsPerHour <= 0) return 50; // Neutral
  if (resourceLcrCents <= budgetLcrCentsPerHour) return 100;
  // Above budget: score decreases linearly, 0 at 2× budget
  const ratio = resourceLcrCents / budgetLcrCentsPerHour;
  return Math.max(0, Math.round(100 - (ratio - 1) * 100));
}

/**
 * Computes utilization score (0-100).
 * Prefers resources with utilization below their chargeability target
 * (i.e., capacity to absorb new work).
 */
export function computeUtilizationScore(
  currentUtilizationPercent: number,
  chargeabilityTarget: number,
): number {
  const gap = chargeabilityTarget - currentUtilizationPercent;
  if (gap >= 20) return 100; // Well under target — great candidate
  if (gap >= 0) return 60 + gap * 2; // Slightly under target
  if (gap >= -20) return Math.max(0, 60 + gap * 2); // Slightly over
  return 0; // Severely overallocated
}

/**
 * Multi-factor staffing scorer.
 * Returns ranked suggestions with score breakdowns.
 *
 * Score weights:
 *   Skill       40%
 *   Availability 30%
 *   Cost        20%
 *   Utilization 10%
 */
export function rankResources(input: SkillMatchInput): StaffingSuggestion[] {
  const { requiredSkills, preferredSkills, resources, budgetLcrCentsPerHour } = input;

  const suggestions: StaffingSuggestion[] = resources.map((resource) => {
    const skillResult = computeSkillScore(resource.skills, requiredSkills, preferredSkills);
    const availScore = computeAvailabilityScore(
      resource.hasAvailabilityConflicts,
      resource.conflictDays?.length,
    );
    const costScore = computeCostScore(resource.lcrCents, budgetLcrCentsPerHour);
    const utilScore = computeUtilizationScore(
      resource.currentUtilizationPercent,
      resource.chargeabilityTarget,
    );

    const total = Math.round(
      skillResult.score * SCORE_WEIGHTS.SKILL +
        availScore * SCORE_WEIGHTS.AVAILABILITY +
        costScore * SCORE_WEIGHTS.COST +
        utilScore * SCORE_WEIGHTS.UTILIZATION,
    );

    const scoreBreakdown: ScoreBreakdown = {
      skillScore: Math.round(skillResult.score),
      availabilityScore: Math.round(availScore),
      costScore: Math.round(costScore),
      utilizationScore: Math.round(utilScore),
      total,
    };

    return {
      resourceId: resource.id,
      resourceName: resource.displayName,
      eid: resource.eid,
      score: total,
      scoreBreakdown,
      matchedSkills: [...skillResult.matchedRequired, ...skillResult.matchedPreferred],
      missingSkills: skillResult.missing,
      availabilityConflicts: resource.conflictDays ?? [],
      estimatedDailyCostCents: resource.lcrCents * 8,
      currentUtilization: resource.currentUtilizationPercent,
    };
  });

  // Sort by score descending
  return suggestions.sort((a, b) => b.score - a.score);
}
