package orchestrator

import (
	"context"
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/Fimeg/RedFlag/aggregator-agent/internal/circuitbreaker"
	"github.com/Fimeg/RedFlag/aggregator-agent/internal/client"
	"github.com/Fimeg/RedFlag/aggregator-agent/internal/event"
	"github.com/Fimeg/RedFlag/aggregator-agent/internal/models"
)

// Scanner represents a generic update scanner
type Scanner interface {
	// IsAvailable checks if the scanner is available on this system
	IsAvailable() bool

	// Scan performs the actual scanning and returns update items
	Scan() ([]client.UpdateReportItem, error)

	// Name returns the scanner name for logging
	Name() string
}

// ScannerConfig holds configuration for a single scanner
type ScannerConfig struct {
	Scanner        Scanner
	CircuitBreaker *circuitbreaker.CircuitBreaker
	Timeout        time.Duration
	Enabled        bool
}

// ScanResult holds the result of a scanner execution
type ScanResult struct {
	ScannerName string
	Updates     []client.UpdateReportItem
	Error       error
	Duration    time.Duration
	Status      string // "success", "failed", "disabled", "unavailable", "skipped"
}

// Orchestrator manages and coordinates multiple scanners
type Orchestrator struct {
	scanners    map[string]*ScannerConfig
	eventBuffer *event.Buffer
	mu          sync.RWMutex
}

// NewOrchestrator creates a new scanner orchestrator
func NewOrchestrator() *Orchestrator {
	return &Orchestrator{
		scanners: make(map[string]*ScannerConfig),
	}
}

// NewOrchestratorWithEvents creates a new scanner orchestrator with event buffering
func NewOrchestratorWithEvents(buffer *event.Buffer) *Orchestrator {
	return &Orchestrator{
		scanners:    make(map[string]*ScannerConfig),
		eventBuffer: buffer,
	}
}

// RegisterScanner adds a scanner to the orchestrator
func (o *Orchestrator) RegisterScanner(name string, scanner Scanner, cb *circuitbreaker.CircuitBreaker, timeout time.Duration, enabled bool) {
	o.mu.Lock()
	defer o.mu.Unlock()

	o.scanners[name] = &ScannerConfig{
		Scanner:        scanner,
		CircuitBreaker: cb,
		Timeout:        timeout,
		Enabled:        enabled,
	}
}

// ScanAll executes all registered scanners in parallel
func (o *Orchestrator) ScanAll(ctx context.Context) ([]ScanResult, []client.UpdateReportItem) {
	o.mu.RLock()
	defer o.mu.RUnlock()

	var wg sync.WaitGroup
	resultsChan := make(chan ScanResult, len(o.scanners))

	// Launch goroutine for each scanner
	for name, scannerConfig := range o.scanners {
		wg.Add(1)
		go func(name string, cfg *ScannerConfig) {
			defer wg.Done()
			result := o.executeScan(ctx, name, cfg)
			resultsChan <- result
		}(name, scannerConfig)
	}

	// Wait for all scanners to complete
	wg.Wait()
	close(resultsChan)

	// Collect results
	var results []ScanResult
	var allUpdates []client.UpdateReportItem

	for result := range resultsChan {
		results = append(results, result)
		if result.Error == nil && len(result.Updates) > 0 {
			allUpdates = append(allUpdates, result.Updates...)
		}
	}

	return results, allUpdates
}

// ScanSingle executes a single scanner by name
func (o *Orchestrator) ScanSingle(ctx context.Context, scannerName string) (ScanResult, error) {
	o.mu.RLock()
	defer o.mu.RUnlock()

	cfg, exists := o.scanners[scannerName]
	if !exists {
		return ScanResult{
			ScannerName: scannerName,
			Status:      "failed",
			Error:       fmt.Errorf("scanner not found: %s", scannerName),
		}, fmt.Errorf("scanner not found: %s", scannerName)
	}

	return o.executeScan(ctx, scannerName, cfg), nil
}

// executeScan runs a single scanner with circuit breaker and timeout protection
func (o *Orchestrator) executeScan(ctx context.Context, name string, cfg *ScannerConfig) ScanResult {
	result := ScanResult{
		ScannerName: name,
		Status:      "failed",
	}

	startTime := time.Now()
	defer func() {
		result.Duration = time.Since(startTime)
	}()

	// Check if enabled
	if !cfg.Enabled {
		result.Status = "disabled"
		log.Printf("[%s] Scanner disabled via configuration", name)
		
		// Buffer disabled event if event buffer is available
		if o.eventBuffer != nil {
			event := &models.SystemEvent{
				EventType:    "agent_scan",
				EventSubtype: "skipped",
				Severity:     "info",
				Component:    "scanner",
				Message:      fmt.Sprintf("Scanner %s is disabled via configuration", name),
				Metadata: map[string]interface{}{
					"scanner_name": name,
					"status":       "disabled",
					"reason":       "configuration",
				},
				CreatedAt: time.Now(),
			}
			if err := o.eventBuffer.BufferEvent(event); err != nil {
				log.Printf("Warning: Failed to buffer scanner disabled event: %v", err)
			}
		}
		
		return result
	}

	// Check if available
	if !cfg.Scanner.IsAvailable() {
		result.Status = "unavailable"
		log.Printf("[%s] Scanner not available on this system", name)
		
		// Buffer unavailable event if event buffer is available
		if o.eventBuffer != nil {
			event := &models.SystemEvent{
				EventType:    "agent_scan",
				EventSubtype: "skipped",
				Severity:     "info",
				Component:    "scanner",
				Message:      fmt.Sprintf("Scanner %s is not available on this system", name),
				Metadata: map[string]interface{}{
					"scanner_name": name,
					"status":       "unavailable",
					"reason":       "system_incompatible",
				},
				CreatedAt: time.Now(),
			}
			if err := o.eventBuffer.BufferEvent(event); err != nil {
				log.Printf("Warning: Failed to buffer scanner unavailable event: %v", err)
			}
		}
		
		return result
	}

	// Execute with circuit breaker and timeout
	log.Printf("[%s] Starting scan...", name)

	var updates []client.UpdateReportItem

	err := cfg.CircuitBreaker.Call(func() error {
		// Create timeout context
		timeoutCtx, cancel := context.WithTimeout(ctx, cfg.Timeout)
		defer cancel()

		// Channel for scan result
		type scanResult struct {
			updates []client.UpdateReportItem
			err     error
		}
		scanChan := make(chan scanResult, 1)

		// Run scan in goroutine
		go func() {
			u, e := cfg.Scanner.Scan()
			scanChan <- scanResult{updates: u, err: e}
		}()

		// Wait for scan or timeout
		select {
		case <-timeoutCtx.Done():
			return fmt.Errorf("scan timeout after %v", cfg.Timeout)
		case res := <-scanChan:
			if res.err != nil {
				return res.err
			}
			updates = res.updates
			return nil
		}
	})

	if err != nil {
		result.Error = err
		result.Status = "failed"
		log.Printf("[%s] Scan failed: %v", name, err)
		
		// Buffer event if event buffer is available
		if o.eventBuffer != nil {
			event := &models.SystemEvent{
				EventType:    "agent_scan",
				EventSubtype: "failed",
				Severity:     "error",
				Component:    "scanner",
				Message:      fmt.Sprintf("Scanner %s failed: %v", name, err),
				Metadata: map[string]interface{}{
					"scanner_name": name,
					"error_type":   "scan_failed",
					"error_details": err.Error(),
					"duration_ms":  result.Duration.Milliseconds(),
				},
				CreatedAt: time.Now(),
			}
			if err := o.eventBuffer.BufferEvent(event); err != nil {
				log.Printf("Warning: Failed to buffer scanner failure event: %v", err)
			}
		}
		
		return result
	}

	result.Updates = updates
	result.Status = "success"
	log.Printf("[%s] Scan completed: found %d updates (took %v)", name, len(updates), result.Duration)
	
	// Buffer success event if event buffer is available
	if o.eventBuffer != nil {
		event := &models.SystemEvent{
			EventType:    "agent_scan",
			EventSubtype: "completed",
			Severity:     "info",
			Component:    "scanner",
			Message:      fmt.Sprintf("Scanner %s completed successfully", name),
			Metadata: map[string]interface{}{
				"scanner_name": name,
				"updates_found": len(updates),
				"duration_ms":   result.Duration.Milliseconds(),
				"status":        "success",
			},
			CreatedAt: time.Now(),
		}
		if err := o.eventBuffer.BufferEvent(event); err != nil {
			log.Printf("Warning: Failed to buffer scanner success event: %v", err)
		}
	}

	return result
}

// GetScannerNames returns a list of all registered scanner names
func (o *Orchestrator) GetScannerNames() []string {
	o.mu.RLock()
	defer o.mu.RUnlock()

	names := make([]string, 0, len(o.scanners))
	for name := range o.scanners {
		names = append(names, name)
	}
	return names
}

// FormatScanSummary creates a human-readable summary of scan results
func FormatScanSummary(results []ScanResult) (stdout string, stderr string, exitCode int) {
	var successResults []string
	var errorMessages []string
	totalUpdates := 0

	for _, result := range results {
		switch result.Status {
		case "success":
			msg := fmt.Sprintf("%s: Found %d updates (%.2fs)",
				result.ScannerName, len(result.Updates), result.Duration.Seconds())
			successResults = append(successResults, msg)
			totalUpdates += len(result.Updates)

		case "failed":
			msg := fmt.Sprintf("%s: %v", result.ScannerName, result.Error)
			errorMessages = append(errorMessages, msg)

		case "disabled":
			successResults = append(successResults, fmt.Sprintf("%s: Disabled", result.ScannerName))

		case "unavailable":
			successResults = append(successResults, fmt.Sprintf("%s: Not available", result.ScannerName))
		}
	}

	// Build stdout
	if len(successResults) > 0 {
		stdout = "Scan Results:\n"
		for _, msg := range successResults {
			stdout += fmt.Sprintf("  - %s\n", msg)
		}
		stdout += fmt.Sprintf("\nTotal Updates Found: %d\n", totalUpdates)
	}

	// Build stderr
	if len(errorMessages) > 0 {
		stderr = "Scan Errors:\n"
		for _, msg := range errorMessages {
			stderr += fmt.Sprintf("  - %s\n", msg)
		}
	}

	// Determine exit code
	if len(errorMessages) > 0 {
		exitCode = 1
	} else {
		exitCode = 0
	}

	return stdout, stderr, exitCode
}
