package acknowledgment

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"time"
)

// PendingResult represents a command result awaiting acknowledgment
type PendingResult struct {
	CommandID  string    `json:"command_id"`
	SentAt     time.Time `json:"sent_at"`
	RetryCount int       `json:"retry_count"`
}

// Tracker manages pending acknowledgments for command results
type Tracker struct {
	pending  map[string]*PendingResult
	mu       sync.RWMutex
	filePath string
	maxAge   time.Duration // Max time to keep pending (default 24h)
	maxRetries int         // Max retries before giving up (default 10)
}

// NewTracker creates a new acknowledgment tracker
func NewTracker(statePath string) *Tracker {
	return &Tracker{
		pending:    make(map[string]*PendingResult),
		filePath:   filepath.Join(statePath, "pending_acks.json"),
		maxAge:     24 * time.Hour,
		maxRetries: 10,
	}
}

// Load restores pending acknowledgments from disk
func (t *Tracker) Load() error {
	t.mu.Lock()
	defer t.mu.Unlock()

	// If file doesn't exist, that's fine (fresh start)
	if _, err := os.Stat(t.filePath); os.IsNotExist(err) {
		return nil
	}

	data, err := os.ReadFile(t.filePath)
	if err != nil {
		return fmt.Errorf("failed to read pending acks: %w", err)
	}

	if len(data) == 0 {
		return nil // Empty file
	}

	var pending map[string]*PendingResult
	if err := json.Unmarshal(data, &pending); err != nil {
		return fmt.Errorf("failed to parse pending acks: %w", err)
	}

	t.pending = pending
	return nil
}

// Save persists pending acknowledgments to disk
func (t *Tracker) Save() error {
	t.mu.RLock()
	defer t.mu.RUnlock()

	// Ensure directory exists
	dir := filepath.Dir(t.filePath)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return fmt.Errorf("failed to create ack directory: %w", err)
	}

	data, err := json.MarshalIndent(t.pending, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal pending acks: %w", err)
	}

	if err := os.WriteFile(t.filePath, data, 0600); err != nil {
		return fmt.Errorf("failed to write pending acks: %w", err)
	}

	return nil
}

// Add marks a command result as pending acknowledgment
func (t *Tracker) Add(commandID string) {
	t.mu.Lock()
	defer t.mu.Unlock()

	t.pending[commandID] = &PendingResult{
		CommandID:  commandID,
		SentAt:     time.Now(),
		RetryCount: 0,
	}
}

// Acknowledge marks command results as acknowledged and removes them
func (t *Tracker) Acknowledge(commandIDs []string) {
	t.mu.Lock()
	defer t.mu.Unlock()

	for _, id := range commandIDs {
		delete(t.pending, id)
	}
}

// GetPending returns list of command IDs awaiting acknowledgment
func (t *Tracker) GetPending() []string {
	t.mu.RLock()
	defer t.mu.RUnlock()

	ids := make([]string, 0, len(t.pending))
	for id := range t.pending {
		ids = append(ids, id)
	}
	return ids
}

// IncrementRetry increments retry count for a command
func (t *Tracker) IncrementRetry(commandID string) {
	t.mu.Lock()
	defer t.mu.Unlock()

	if result, exists := t.pending[commandID]; exists {
		result.RetryCount++
	}
}

// Cleanup removes old or over-retried pending results
func (t *Tracker) Cleanup() int {
	t.mu.Lock()
	defer t.mu.Unlock()

	now := time.Now()
	removed := 0

	for id, result := range t.pending {
		// Remove if too old
		if now.Sub(result.SentAt) > t.maxAge {
			delete(t.pending, id)
			removed++
			continue
		}

		// Remove if retried too many times
		if result.RetryCount >= t.maxRetries {
			delete(t.pending, id)
			removed++
			continue
		}
	}

	return removed
}

// Stats returns statistics about pending acknowledgments
func (t *Tracker) Stats() Stats {
	t.mu.RLock()
	defer t.mu.RUnlock()

	stats := Stats{
		Total: len(t.pending),
	}

	now := time.Now()
	for _, result := range t.pending {
		age := now.Sub(result.SentAt)

		if age > 1*time.Hour {
			stats.OlderThan1Hour++
		}
		if result.RetryCount > 0 {
			stats.WithRetries++
		}
		if result.RetryCount >= 5 {
			stats.HighRetries++
		}
	}

	return stats
}

// Stats holds statistics about pending acknowledgments
type Stats struct {
	Total          int
	OlderThan1Hour int
	WithRetries    int
	HighRetries    int
}
