package service

import (
	"sync"
	"time"

	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/port"
)

// BuildProgressTracker estimates build progress based on agent activity patterns.
// It tracks phases and emits progress events periodically.
type BuildProgressTracker struct {
	streams port.StreamPublisher
	mu      sync.RWMutex
	tasks   map[string]*buildProgress
}

// buildProgress tracks the progress state for a single build.
type buildProgress struct {
	taskID      string
	projectID   string
	phase       domain.BuildPhase
	percentage  float64
	toolCount   int
	outputLines int
	startTime   time.Time
	lastUpdate  time.Time
}

// NewBuildProgressTracker creates a new progress tracker.
func NewBuildProgressTracker(streams port.StreamPublisher) *BuildProgressTracker {
	return &BuildProgressTracker{
		streams: streams,
		tasks:   make(map[string]*buildProgress),
	}
}

// Start begins tracking progress for a build.
func (t *BuildProgressTracker) Start(taskID, projectID string) {
	t.mu.Lock()
	defer t.mu.Unlock()

	now := time.Now()
	t.tasks[taskID] = &buildProgress{
		taskID:     taskID,
		projectID:  projectID,
		phase:      domain.BuildPhaseStarting,
		percentage: 0,
		startTime:  now,
		lastUpdate: now,
	}

	t.emitProgress(taskID)
}

// RecordToolUse updates progress when a tool is used.
func (t *BuildProgressTracker) RecordToolUse(taskID, toolName string) {
	t.mu.Lock()
	defer t.mu.Unlock()

	progress, exists := t.tasks[taskID]
	if !exists {
		return
	}

	progress.toolCount++
	progress.lastUpdate = time.Now()

	// Infer phase from tool usage
	switch toolName {
	case "Read", "Glob", "Grep":
		if progress.phase == domain.BuildPhaseStarting {
			progress.phase = domain.BuildPhaseReading
		}
	case "Write", "Edit":
		progress.phase = domain.BuildPhaseWriting
	case "Bash":
		// Could be testing or committing depending on context
		if progress.phase == domain.BuildPhaseWriting {
			progress.phase = domain.BuildPhaseTesting
		}
	}

	t.updatePercentage(progress)
	t.emitProgress(taskID)
}

// RecordOutput updates progress when output is received.
func (t *BuildProgressTracker) RecordOutput(taskID string) {
	t.mu.Lock()
	defer t.mu.Unlock()

	progress, exists := t.tasks[taskID]
	if !exists {
		return
	}

	progress.outputLines++
	progress.lastUpdate = time.Now()

	// Emit progress periodically (every 10 lines or 5 seconds)
	if progress.outputLines%10 == 0 || time.Since(progress.lastUpdate) > 5*time.Second {
		t.updatePercentage(progress)
		t.emitProgress(taskID)
	}
}

// Complete marks a build as complete.
func (t *BuildProgressTracker) Complete(taskID string, success bool) {
	t.mu.Lock()
	defer t.mu.Unlock()

	progress, exists := t.tasks[taskID]
	if !exists {
		return
	}

	progress.phase = domain.BuildPhaseComplete
	progress.percentage = 100
	progress.lastUpdate = time.Now()

	t.emitProgress(taskID)

	// Clean up
	delete(t.tasks, taskID)
}

// GetProgress returns current progress for a build.
func (t *BuildProgressTracker) GetProgress(taskID string) (phase domain.BuildPhase, percentage float64, ok bool) {
	t.mu.RLock()
	defer t.mu.RUnlock()

	progress, exists := t.tasks[taskID]
	if !exists {
		return "", 0, false
	}

	return progress.phase, progress.percentage, true
}

// updatePercentage estimates completion percentage based on phase and activity.
func (t *BuildProgressTracker) updatePercentage(progress *buildProgress) {
	// Base percentage from phase
	var basePercent float64
	switch progress.phase {
	case domain.BuildPhaseStarting:
		basePercent = 5
	case domain.BuildPhaseReading:
		basePercent = 15
	case domain.BuildPhaseWriting:
		basePercent = 50
	case domain.BuildPhaseTesting:
		basePercent = 80
	case domain.BuildPhaseCommitting:
		basePercent = 95
	case domain.BuildPhaseComplete:
		basePercent = 100
	}

	// Add progress within phase based on activity
	// Tool count adds ~1% per tool (max +10% within phase)
	toolBonus := float64(progress.toolCount) * 1.0
	if toolBonus > 10 {
		toolBonus = 10
	}

	// Time-based bonus: ~1% per 10 seconds (max +5% within phase)
	elapsed := time.Since(progress.startTime).Seconds()
	timeBonus := elapsed / 10.0
	if timeBonus > 5 {
		timeBonus = 5
	}

	// Calculate total but don't exceed next phase threshold
	progress.percentage = basePercent + toolBonus + timeBonus

	// Cap to reasonable maximum for current phase
	maxForPhase := map[domain.BuildPhase]float64{
		domain.BuildPhaseStarting:   14,
		domain.BuildPhaseReading:    49,
		domain.BuildPhaseWriting:    79,
		domain.BuildPhaseTesting:    94,
		domain.BuildPhaseCommitting: 99,
		domain.BuildPhaseComplete:   100,
	}
	if max, ok := maxForPhase[progress.phase]; ok && progress.percentage > max {
		progress.percentage = max
	}
}

// emitProgress publishes a progress event. Must be called with lock held.
func (t *BuildProgressTracker) emitProgress(taskID string) {
	progress, exists := t.tasks[taskID]
	if !exists || t.streams == nil {
		return
	}

	t.streams.Publish(taskID, port.StreamEvent{
		Type:   "build.progress",
		TaskID: taskID,
		Data: map[string]any{
			"phase":      string(progress.phase),
			"percentage": progress.percentage,
			"tool_count": progress.toolCount,
			"elapsed_ms": time.Since(progress.startTime).Milliseconds(),
		},
	})
}
