package repository

import (
	"sort"
	"strings"
	"time"
)

// DefaultTaskMatcher implements TaskMatcher interface
type DefaultTaskMatcher struct{}

// MatchTasksToRole filters tasks suitable for a specific role and expertise
func (m *DefaultTaskMatcher) MatchTasksToRole(tasks []*Task, role string, expertise []string) ([]*Task, error) {
	var matchedTasks []*Task
	
	for _, task := range tasks {
		score := m.ScoreTaskForAgent(task, role, expertise)
		if score > 0.3 { // Minimum threshold for task suitability
			matchedTasks = append(matchedTasks, task)
		}
	}
	
	// Sort by score (highest first)
	sort.Slice(matchedTasks, func(i, j int) bool {
		scoreI := m.ScoreTaskForAgent(matchedTasks[i], role, expertise)
		scoreJ := m.ScoreTaskForAgent(matchedTasks[j], role, expertise)
		return scoreI > scoreJ
	})
	
	return matchedTasks, nil
}

// ScoreTaskForAgent calculates how suitable a task is for an agent based on role and expertise
func (m *DefaultTaskMatcher) ScoreTaskForAgent(task *Task, agentRole string, agentExpertise []string) float64 {
	score := 0.0
	
	// Base score for role matching
	if task.RequiredRole != "" {
		if task.RequiredRole == agentRole {
			score += 0.5 // Perfect role match
		} else if m.isCompatibleRole(task.RequiredRole, agentRole) {
			score += 0.3 // Compatible role
		}
	} else {
		// No specific role required, default bonus for general roles
		if m.isGeneralRole(agentRole) {
			score += 0.2
		}
	}
	
	// Expertise matching
	expertiseScore := m.calculateExpertiseScore(task.RequiredExpertise, agentExpertise)
	score += expertiseScore * 0.4
	
	// Priority bonus (higher priority tasks get small bonus)
	priorityBonus := float64(task.Priority) / 10.0 * 0.1
	score += priorityBonus
	
	// Task type bonuses based on agent role
	taskTypeScore := m.calculateTaskTypeScore(task, agentRole)
	score += taskTypeScore * 0.1
	
	// Label-based scoring
	labelScore := m.calculateLabelScore(task.Labels, agentRole, agentExpertise)
	score += labelScore * 0.1
	
	// Ensure score is between 0 and 1
	if score > 1.0 {
		score = 1.0
	}
	if score < 0.0 {
		score = 0.0
	}
	
	return score
}

// GetRecommendedAgents returns agents best suited for a task
func (m *DefaultTaskMatcher) GetRecommendedAgents(task *Task, availableAgents []AgentInfo) ([]AgentInfo, error) {
	type agentScore struct {
		agent AgentInfo
		score float64
	}
	
	var scoredAgents []agentScore
	
	for _, agent := range availableAgents {
		// Skip agents that are offline or at capacity
		if agent.Status != "online" && agent.Status != "ready" {
			continue
		}
		if agent.CurrentTasks >= agent.MaxTasks {
			continue
		}
		
		// Calculate base task suitability score
		taskScore := m.ScoreTaskForAgent(task, agent.Role, agent.Expertise)
		
		// Apply agent-specific modifiers
		finalScore := taskScore
		
		// Performance modifier (0.5 to 1.5 multiplier)
		performanceMod := 0.5 + agent.Performance
		finalScore *= performanceMod
		
		// Availability modifier
		availabilityMod := agent.Availability
		finalScore *= availabilityMod
		
		// Workload penalty (agents with fewer current tasks get slight bonus)
		workloadRatio := float64(agent.CurrentTasks) / float64(agent.MaxTasks)
		workloadBonus := (1.0 - workloadRatio) * 0.1
		finalScore += workloadBonus
		
		// Recent activity bonus (agents seen recently get small bonus)
		timeSinceLastSeen := time.Since(agent.LastSeen)
		if timeSinceLastSeen < 5*time.Minute {
			finalScore += 0.05
		}
		
		if finalScore > 0.1 { // Minimum threshold
			scoredAgents = append(scoredAgents, agentScore{agent: agent, score: finalScore})
		}
	}
	
	// Sort by score (highest first)
	sort.Slice(scoredAgents, func(i, j int) bool {
		return scoredAgents[i].score > scoredAgents[j].score
	})
	
	// Return top agents (up to 5)
	maxAgents := 5
	if len(scoredAgents) < maxAgents {
		maxAgents = len(scoredAgents)
	}
	
	result := make([]AgentInfo, maxAgents)
	for i := 0; i < maxAgents; i++ {
		result[i] = scoredAgents[i].agent
	}
	
	return result, nil
}

// calculateExpertiseScore computes overlap between required and agent expertise
func (m *DefaultTaskMatcher) calculateExpertiseScore(requiredExpertise, agentExpertise []string) float64 {
	if len(requiredExpertise) == 0 {
		return 0.5 // No specific expertise required
	}
	
	matches := 0
	for _, required := range requiredExpertise {
		for _, agent := range agentExpertise {
			if strings.EqualFold(required, agent) || m.isRelatedExpertise(required, agent) {
				matches++
				break
			}
		}
	}
	
	// Score based on percentage of required expertise covered
	score := float64(matches) / float64(len(requiredExpertise))
	
	// Bonus for having additional relevant expertise
	if len(agentExpertise) > len(requiredExpertise) {
		bonus := 0.1 * float64(len(agentExpertise)-len(requiredExpertise)) / float64(len(agentExpertise))
		score += bonus
	}
	
	return score
}

// calculateTaskTypeScore gives bonuses based on task type and agent role compatibility
func (m *DefaultTaskMatcher) calculateTaskTypeScore(task *Task, agentRole string) float64 {
	taskType := strings.ToLower(task.TaskType)
	role := strings.ToLower(agentRole)
	
	switch taskType {
	case "bug_fix", "bug":
		if strings.Contains(role, "qa") || strings.Contains(role, "test") {
			return 0.8
		}
		if strings.Contains(role, "backend") || strings.Contains(role, "full_stack") {
			return 0.6
		}
	case "feature", "enhancement":
		if strings.Contains(role, "developer") || strings.Contains(role, "engineer") {
			return 0.7
		}
	case "documentation", "docs":
		if strings.Contains(role, "writer") || strings.Contains(role, "documentation") {
			return 0.9
		}
	case "security":
		if strings.Contains(role, "security") {
			return 0.9
		}
	case "design":
		if strings.Contains(role, "designer") || strings.Contains(role, "ux") {
			return 0.9
		}
	case "infrastructure", "devops":
		if strings.Contains(role, "devops") || strings.Contains(role, "systems") {
			return 0.9
		}
	}
	
	return 0.0
}

// calculateLabelScore analyzes task labels for additional role matching
func (m *DefaultTaskMatcher) calculateLabelScore(labels []string, agentRole string, agentExpertise []string) float64 {
	score := 0.0
	role := strings.ToLower(agentRole)
	
	for _, label := range labels {
		label = strings.ToLower(label)
		
		// Direct role matches
		if strings.Contains(role, label) || strings.Contains(label, role) {
			score += 0.3
		}
		
		// Expertise matches
		for _, expertise := range agentExpertise {
			if strings.Contains(strings.ToLower(expertise), label) || strings.Contains(label, strings.ToLower(expertise)) {
				score += 0.2
			}
		}
		
		// Specific label bonuses
		switch label {
		case "frontend":
			if strings.Contains(role, "frontend") || strings.Contains(role, "ui") {
				score += 0.4
			}
		case "backend":
			if strings.Contains(role, "backend") || strings.Contains(role, "api") {
				score += 0.4
			}
		case "urgent", "critical":
			score += 0.1 // Small bonus for urgent tasks
		case "good first issue", "beginner":
			if strings.Contains(role, "junior") {
				score += 0.3
			}
		}
	}
	
	return score
}

// isCompatibleRole checks if two roles are compatible
func (m *DefaultTaskMatcher) isCompatibleRole(requiredRole, agentRole string) bool {
	compatibilityMap := map[string][]string{
		"frontend_developer":      {"full_stack_engineer", "ui_ux_designer"},
		"backend_developer":       {"full_stack_engineer", "database_engineer"},
		"full_stack_engineer":     {"frontend_developer", "backend_developer"},
		"qa_engineer":             {"full_stack_engineer", "backend_developer"},
		"devops_engineer":         {"systems_engineer", "backend_developer"},
		"ui_ux_designer":          {"frontend_developer", "lead_designer"},
		"security_expert":         {"backend_developer", "senior_software_architect"},
		"technical_writer":        {"full_stack_engineer"},
		"database_engineer":       {"backend_developer", "full_stack_engineer"},
		"senior_software_architect": {"full_stack_engineer", "backend_developer", "frontend_developer"},
	}
	
	compatibleRoles, exists := compatibilityMap[requiredRole]
	if !exists {
		return false
	}
	
	for _, compatible := range compatibleRoles {
		if compatible == agentRole {
			return true
		}
	}
	
	return false
}

// isGeneralRole checks if a role is considered general-purpose
func (m *DefaultTaskMatcher) isGeneralRole(role string) bool {
	generalRoles := []string{
		"full_stack_engineer",
		"senior_software_architect",
		"general_developer",
	}
	
	for _, general := range generalRoles {
		if general == role {
			return true
		}
	}
	
	return false
}

// isRelatedExpertise checks if two expertise areas are related
func (m *DefaultTaskMatcher) isRelatedExpertise(required, agent string) bool {
	relatedMap := map[string][]string{
		"frontend":        {"javascript", "typescript", "react", "vue", "angular", "css", "html"},
		"backend":         {"api_development", "server_frameworks", "databases", "microservices"},
		"database":        {"sql", "nosql", "data_modeling", "query_optimization"},
		"security":        {"cybersecurity", "owasp", "vulnerability_analysis", "penetration_testing"},
		"devops":          {"deployment", "infrastructure", "docker", "kubernetes", "cicd"},
		"testing":         {"qa_methodologies", "test_automation", "debugging"},
		"design":          {"ui_ux", "user_experience", "prototyping", "design_systems"},
		"documentation":   {"technical_writing", "api_documentation"},
	}
	
	required = strings.ToLower(required)
	agent = strings.ToLower(agent)
	
	// Check direct related expertise
	if related, exists := relatedMap[required]; exists {
		for _, rel := range related {
			if strings.Contains(agent, rel) || strings.Contains(rel, agent) {
				return true
			}
		}
	}
	
	// Check reverse mapping
	if related, exists := relatedMap[agent]; exists {
		for _, rel := range related {
			if strings.Contains(required, rel) || strings.Contains(rel, required) {
				return true
			}
		}
	}
	
	return false
}