package routing

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
)

// KnowledgeBase represents the activity knowledge base
type KnowledgeBase struct {
	Version    string               `json:"version"`
	Activities []ActivityMetadata   `json:"activities"`
	Metadata   KnowledgeBaseMetadata `json:"metadata"`
	
	// Index for fast lookups
	byName map[string]*ActivityMetadata
}

// KnowledgeBaseMetadata tracks KB metadata
type KnowledgeBaseMetadata struct {
	TotalActivities int               `json:"totalActivities"`
	LastUpdated     string            `json:"lastUpdated"`
	Categories      map[string]int    `json:"categories"`
}

// LoadKnowledgeBase loads the activity knowledge base from a JSON file
func LoadKnowledgeBase(filePath string) (*KnowledgeBase, error) {
	// Read file
	data, err := ioutil.ReadFile(filePath)
	if err != nil {
		return nil, fmt.Errorf("failed to read knowledge base file: %w", err)
	}

	// Parse JSON
	var kb KnowledgeBase
	err = json.Unmarshal(data, &kb)
	if err != nil {
		return nil, fmt.Errorf("failed to parse knowledge base JSON: %w", err)
	}

	// Build index
	kb.byName = make(map[string]*ActivityMetadata)
	for i := range kb.Activities {
		kb.byName[kb.Activities[i].Name] = &kb.Activities[i]
	}

	return &kb, nil
}

// LoadKnowledgeBaseFromDefaultPath loads KB from default location
// Looks for activity_knowledge_base.json in same directory as caller
func LoadKnowledgeBaseFromDefaultPath() (*KnowledgeBase, error) {
	// Try to find from package directory
	execDir, err := os.Executable()
	if err == nil {
		// Try in same directory as binary
		path := filepath.Join(filepath.Dir(execDir), "activity_knowledge_base.json")
		if _, err := os.Stat(path); err == nil {
			return LoadKnowledgeBase(path)
		}
	}

	// Try from current working directory
	if _, err := os.Stat("activity_knowledge_base.json"); err == nil {
		return LoadKnowledgeBase("activity_knowledge_base.json")
	}

	// Try from internal/routing directory relative to cwd
	if _, err := os.Stat("internal/routing/activity_knowledge_base.json"); err == nil {
		return LoadKnowledgeBase("internal/routing/activity_knowledge_base.json")
	}

	// Try from parent directory (for tests running from tests/ dir)
	if _, err := os.Stat("../internal/routing/activity_knowledge_base.json"); err == nil {
		return LoadKnowledgeBase("../internal/routing/activity_knowledge_base.json")
	}

	// Try using runtime to find package directory
	_, filename, _, ok := runtime.Caller(0)
	if ok {
		pkgDir := filepath.Dir(filename)
		path := filepath.Join(pkgDir, "activity_knowledge_base.json")
		if _, err := os.Stat(path); err == nil {
			return LoadKnowledgeBase(path)
		}
	}

	return nil, fmt.Errorf("activity_knowledge_base.json not found in any expected location")
}

// GetActivity returns metadata for a specific activity
func (kb *KnowledgeBase) GetActivity(name string) *ActivityMetadata {
	return kb.byName[name]
}

// ListActivities returns all activities
func (kb *KnowledgeBase) ListActivities() []ActivityMetadata {
	return kb.Activities
}

// ListActivitiesByCategory returns all activities in a category
func (kb *KnowledgeBase) ListActivitiesByCategory(category string) []ActivityMetadata {
	var result []ActivityMetadata
	for _, activity := range kb.Activities {
		if activity.Category == category {
			result = append(result, activity)
		}
	}
	return result
}

// GetActivityNames returns all activity names
func (kb *KnowledgeBase) GetActivityNames() []string {
	names := make([]string, len(kb.Activities))
	for i, activity := range kb.Activities {
		names[i] = activity.Name
	}
	return names
}

// HasActivity checks if an activity exists
func (kb *KnowledgeBase) HasActivity(name string) bool {
	_, exists := kb.byName[name]
	return exists
}

// GetDependencies returns all dependencies for an activity
func (kb *KnowledgeBase) GetDependencies(activityName string) []string {
	activity := kb.GetActivity(activityName)
	if activity == nil {
		return []string{}
	}
	return activity.Constraints.Dependencies
}

// GetTimeoutForActivity returns the timeout for an activity
func (kb *KnowledgeBase) GetTimeoutForActivity(activityName string) string {
	activity := kb.GetActivity(activityName)
	if activity == nil {
		return "5m" // Default timeout
	}
	return activity.Constraints.DefaultTimeout
}

// GetRetryPolicyForActivity returns retry configuration for an activity
func (kb *KnowledgeBase) GetRetryPolicyForActivity(activityName string) *RetryPolicy {
	activity := kb.GetActivity(activityName)
	if activity == nil {
		return &RetryPolicy{
			MaxAttempts:     1,
			BackoffRate:     1.0,
			InitialInterval: "1s",
		}
	}

	return &RetryPolicy{
		MaxAttempts:     int32(activity.Constraints.RecommendedRetries),
		BackoffRate:     activity.Constraints.RetryBackoff,
		InitialInterval: "1s",
		MaxInterval:     "30s",
	}
}

// IsFlaky returns whether an activity is marked as flaky
func (kb *KnowledgeBase) IsFlaky(activityName string) bool {
	activity := kb.GetActivity(activityName)
	if activity == nil {
		return false
	}
	return activity.Constraints.IsFlaky
}

// GetNotes returns implementation notes for an activity
func (kb *KnowledgeBase) GetNotes(activityName string) string {
	activity := kb.GetActivity(activityName)
	if activity == nil {
		return ""
	}
	return activity.Constraints.Notes
}

// Validate checks the knowledge base for consistency
func (kb *KnowledgeBase) Validate() error {
	// Check for circular dependencies
	visited := make(map[string]bool)
	for _, activity := range kb.Activities {
		if err := kb.checkDependencies(activity.Name, visited, []string{}); err != nil {
			return err
		}
	}

	// Check that all dependencies exist
	for _, activity := range kb.Activities {
		for _, dep := range activity.Constraints.Dependencies {
			if !kb.HasActivity(dep) {
				return fmt.Errorf("activity %s depends on non-existent activity %s", activity.Name, dep)
			}
		}
	}

	return nil
}

// checkDependencies validates activity dependencies for cycles
func (kb *KnowledgeBase) checkDependencies(activityName string, visited map[string]bool, path []string) error {
	// Check for cycles
	for _, p := range path {
		if p == activityName {
			cycleStr := ""
			found := false
			for _, n := range path {
				if found {
					cycleStr += " -> " + n
				}
				if n == activityName {
					found = true
					cycleStr += n
				}
			}
			cycleStr += " -> " + activityName
			return fmt.Errorf("circular dependency detected: %s", cycleStr)
		}
	}

	if visited[activityName] {
		return nil // Already checked this branch
	}

	visited[activityName] = true
	newPath := append(path, activityName)

	activity := kb.GetActivity(activityName)
	if activity == nil {
		return nil // Non-existent activity will be caught elsewhere
	}

	for _, dep := range activity.Constraints.Dependencies {
		if err := kb.checkDependencies(dep, visited, newPath); err != nil {
			return err
		}
	}

	return nil
}

// String returns a human-readable description of the knowledge base
func (kb *KnowledgeBase) String() string {
	return fmt.Sprintf("KnowledgeBase(v%s, %d activities)", kb.Version, kb.Metadata.TotalActivities)
}

// PrintSummary prints a summary of available activities
func (kb *KnowledgeBase) PrintSummary() string {
	summary := fmt.Sprintf("=== Activity Knowledge Base ===\nVersion: %s\nTotal Activities: %d\n\n", kb.Version, kb.Metadata.TotalActivities)
	
	summary += "Activities by Category:\n"
	for category, count := range kb.Metadata.Categories {
		summary += fmt.Sprintf("  %s: %d\n", category, count)
	}

	summary += "\nActivity Details:\n"
	for _, activity := range kb.Activities {
		summary += fmt.Sprintf("\n[%s] %s\n", activity.Name, activity.Description)
		summary += fmt.Sprintf("  Category: %s\n", activity.Category)
		summary += fmt.Sprintf("  Timeout: %s\n", activity.Constraints.DefaultTimeout)
		summary += fmt.Sprintf("  Flaky: %v (Retries: %d)\n", activity.Constraints.IsFlaky, activity.Constraints.RecommendedRetries)
		if len(activity.Constraints.Dependencies) > 0 {
			summary += fmt.Sprintf("  Dependencies: %v\n", activity.Constraints.Dependencies)
		}
	}

	return summary
}
