package memory

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

	"github.com/rs/zerolog/log"
)

// Outcome represents the result of a remediation action
type Outcome string

const (
	OutcomeResolved Outcome = "resolved" // Problem was fixed
	OutcomePartial  Outcome = "partial"  // Partially fixed
	OutcomeFailed   Outcome = "failed"   // Action failed
	OutcomeUnknown  Outcome = "unknown"  // Outcome not determined
)

// RemediationRecord represents a logged remediation action
type RemediationRecord struct {
	ID           string        `json:"id"`
	Timestamp    time.Time     `json:"timestamp"`
	ResourceID   string        `json:"resource_id"`
	ResourceType string        `json:"resource_type,omitempty"`
	ResourceName string        `json:"resource_name,omitempty"`
	FindingID    string        `json:"finding_id,omitempty"` // Linked AI finding if any
	Problem      string        `json:"problem"`              // What was wrong
	Action       string        `json:"action"`               // What was done (command or action)
	Output       string        `json:"output,omitempty"`     // Command output if any
	Outcome      Outcome       `json:"outcome"`              // Did it work?
	Duration     time.Duration `json:"duration,omitempty"`   // How long until resolved
	Note         string        `json:"note,omitempty"`       // Optional user/AI note
	Automatic    bool          `json:"automatic"`            // Was this triggered automatically by AI
}

// RemediationLog stores remediation history
type RemediationLog struct {
	mu         sync.RWMutex
	records    []RemediationRecord
	maxRecords int

	// Persistence
	dataDir string
}

// RemediationLogConfig configures the remediation log
type RemediationLogConfig struct {
	MaxRecords int
	DataDir    string
}

// NewRemediationLog creates a new remediation log
func NewRemediationLog(cfg RemediationLogConfig) *RemediationLog {
	if cfg.MaxRecords <= 0 {
		cfg.MaxRecords = 500
	}

	r := &RemediationLog{
		records:    make([]RemediationRecord, 0),
		maxRecords: cfg.MaxRecords,
		dataDir:    cfg.DataDir,
	}

	// Load existing records from disk
	if cfg.DataDir != "" {
		if err := r.loadFromDisk(); err != nil {
			log.Warn().Err(err).Msg("Failed to load remediation log from disk")
		} else if len(r.records) > 0 {
			log.Info().Int("count", len(r.records)).Msg("Loaded remediation log from disk")
		}
	}

	return r
}

// Log records a remediation action
func (r *RemediationLog) Log(record RemediationRecord) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	if record.ID == "" {
		record.ID = generateRecordID()
	}
	if record.Timestamp.IsZero() {
		record.Timestamp = time.Now()
	}

	r.records = append(r.records, record)
	r.trimRecords()

	// Persist asynchronously
	go func() {
		if err := r.saveToDisk(); err != nil {
			log.Warn().Err(err).Msg("Failed to save remediation log")
		}
	}()

	return nil
}

// LogCommand is a convenience method for logging a command execution
func (r *RemediationLog) LogCommand(resourceID, resourceType, resourceName, findingID, problem, command, output string, success bool, automatic bool) {
	outcome := OutcomeUnknown
	if success {
		outcome = OutcomeResolved
	} else {
		outcome = OutcomeFailed
	}

	r.Log(RemediationRecord{
		ResourceID:   resourceID,
		ResourceType: resourceType,
		ResourceName: resourceName,
		FindingID:    findingID,
		Problem:      problem,
		Action:       command,
		Output:       truncateOutput(output, 1000),
		Outcome:      outcome,
		Automatic:    automatic,
	})
}

// GetForResource returns remediation history for a specific resource
func (r *RemediationLog) GetForResource(resourceID string, limit int) []RemediationRecord {
	r.mu.RLock()
	defer r.mu.RUnlock()

	var result []RemediationRecord
	// Iterate in reverse to get most recent first
	for i := len(r.records) - 1; i >= 0 && len(result) < limit; i-- {
		if r.records[i].ResourceID == resourceID {
			result = append(result, r.records[i])
		}
	}
	return result
}

// GetForFinding returns remediation history linked to a specific finding
func (r *RemediationLog) GetForFinding(findingID string, limit int) []RemediationRecord {
	r.mu.RLock()
	defer r.mu.RUnlock()

	var result []RemediationRecord
	for i := len(r.records) - 1; i >= 0 && len(result) < limit; i-- {
		if r.records[i].FindingID == findingID {
			result = append(result, r.records[i])
		}
	}
	return result
}

// GetSimilar finds remediation records with similar problems
func (r *RemediationLog) GetSimilar(problem string, limit int) []RemediationRecord {
	r.mu.RLock()
	defer r.mu.RUnlock()

	// Simple keyword-based matching
	keywords := extractKeywords(problem)
	if len(keywords) == 0 {
		return nil
	}

	type scored struct {
		record RemediationRecord
		score  int
	}

	var matches []scored
	for _, record := range r.records {
		recordKeywords := extractKeywords(record.Problem)
		score := countMatches(keywords, recordKeywords)
		if score > 0 {
			matches = append(matches, scored{record: record, score: score})
		}
	}

	// Sort by score descending
	sort.Slice(matches, func(i, j int) bool {
		return matches[i].score > matches[j].score
	})

	var result []RemediationRecord
	for i := 0; i < len(matches) && len(result) < limit; i++ {
		result = append(result, matches[i].record)
	}
	return result
}

// GetSuccessfulRemediations returns successful remediations for similar problems
func (r *RemediationLog) GetSuccessfulRemediations(problem string, limit int) []RemediationRecord {
	similar := r.GetSimilar(problem, limit*3) // Get more to filter
	var result []RemediationRecord
	for _, rec := range similar {
		if rec.Outcome == OutcomeResolved || rec.Outcome == OutcomePartial {
			result = append(result, rec)
			if len(result) >= limit {
				break
			}
		}
	}
	return result
}

// GetRecentRemediations returns the most recent remediations
func (r *RemediationLog) GetRecentRemediations(limit int, since time.Time) []RemediationRecord {
	r.mu.RLock()
	defer r.mu.RUnlock()

	var result []RemediationRecord
	for i := len(r.records) - 1; i >= 0 && len(result) < limit; i-- {
		if r.records[i].Timestamp.After(since) {
			result = append(result, r.records[i])
		}
	}
	return result
}

// FormatForContext creates AI-consumable summary of remediation history
func (r *RemediationLog) FormatForContext(resourceID string, limit int) string {
	records := r.GetForResource(resourceID, limit)
	if len(records) == 0 {
		return ""
	}

	var result string
	for _, rec := range records {
		ago := time.Since(rec.Timestamp)
		outcomeStr := string(rec.Outcome)
		result += "- " + formatDuration(ago) + " ago: " + rec.Problem + "\n"
		result += "  Action: " + rec.Action + " (" + outcomeStr + ")\n"
		if rec.Note != "" {
			result += "  Note: " + rec.Note + "\n"
		}
	}
	return result
}

// GetRemediationStats returns statistics about remediation effectiveness
func (r *RemediationLog) GetRemediationStats() map[string]int {
	r.mu.RLock()
	defer r.mu.RUnlock()

	stats := map[string]int{
		"total":    len(r.records),
		"resolved": 0,
		"partial":  0,
		"failed":   0,
		"unknown":  0,
	}

	for _, rec := range r.records {
		switch rec.Outcome {
		case OutcomeResolved:
			stats["resolved"]++
		case OutcomePartial:
			stats["partial"]++
		case OutcomeFailed:
			stats["failed"]++
		default:
			stats["unknown"]++
		}
	}

	return stats
}

// trimRecords removes old records beyond maxRecords
func (r *RemediationLog) trimRecords() {
	if len(r.records) > r.maxRecords {
		r.records = r.records[len(r.records)-r.maxRecords:]
	}
}

// saveToDisk persists records to JSON file
func (r *RemediationLog) saveToDisk() error {
	if r.dataDir == "" {
		return nil
	}

	r.mu.RLock()
	records := make([]RemediationRecord, len(r.records))
	copy(records, r.records)
	r.mu.RUnlock()

	path := filepath.Join(r.dataDir, "ai_remediations.json")
	data, err := json.MarshalIndent(records, "", "  ")
	if err != nil {
		return err
	}

	tmpPath := path + ".tmp"
	if err := os.WriteFile(tmpPath, data, 0600); err != nil {
		return err
	}

	return os.Rename(tmpPath, path)
}

// loadFromDisk loads records from JSON file
func (r *RemediationLog) loadFromDisk() error {
	if r.dataDir == "" {
		return nil
	}

	path := filepath.Join(r.dataDir, "ai_remediations.json")
	if st, err := os.Stat(path); err == nil {
		const maxOnDiskBytes = 10 << 20 // 10 MiB safety cap
		if st.Size() > maxOnDiskBytes {
			return fmt.Errorf("remediation history file too large (%d bytes)", st.Size())
		}
	}
	data, err := os.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return err
	}

	var records []RemediationRecord
	if err := json.Unmarshal(data, &records); err != nil {
		return err
	}

	// Sort by timestamp
	sort.Slice(records, func(i, j int) bool {
		return records[i].Timestamp.Before(records[j].Timestamp)
	})

	r.records = records
	r.trimRecords()
	return nil
}

// Helper functions

var recordCounter int64

func generateRecordID() string {
	recordCounter++
	return "rem-" + time.Now().Format("20060102150405") + "-" + intToString(int(recordCounter%1000))
}

func truncateOutput(output string, maxLen int) string {
	if len(output) <= maxLen {
		return output
	}
	return output[:maxLen-3] + "..."
}

func extractKeywords(text string) []string {
	// Simple keyword extraction - split by common delimiters
	// In production, this could use NLP or embeddings
	var keywords []string
	var current string

	for _, c := range text {
		if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' {
			current += string(c)
		} else {
			if len(current) > 3 { // Only keep words > 3 chars
				keywords = append(keywords, current)
			}
			current = ""
		}
	}
	if len(current) > 3 {
		keywords = append(keywords, current)
	}

	return keywords
}

func countMatches(a, b []string) int {
	bSet := make(map[string]bool)
	for _, s := range b {
		bSet[s] = true
	}

	count := 0
	for _, s := range a {
		if bSet[s] {
			count++
		}
	}
	return count
}
