package quality

import (
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"
)

// StateManager manages quality analysis state with diff tracking
type StateManager struct {
	dataDir    string
	stateFile  string
	historyDir string
}

// State represents the persisted quality state
type State struct {
	Findings    []Finding         `json:"findings"`
	Scorecard   *Scorecard        `json:"scorecard"`
	LastScan    time.Time         `json:"last_scan"`
	ScanCount   int               `json:"scan_count"`
	ContentHash string            `json:"content_hash"`
	History     []StateSnapshot   `json:"history,omitempty"`
	Metadata    map[string]string `json:"metadata,omitempty"`
}

// StateSnapshot represents a historical state snapshot
type StateSnapshot struct {
	Timestamp   time.Time `json:"timestamp"`
	Hash        string    `json:"hash"`
	Score       int       `json:"score"`
	StrictScore int       `json:"strict_score"`
	Findings    int       `json:"findings"`
	File        string    `json:"file"`
}

// StateDiff represents the difference between two states
type StateDiff struct {
	Added       []Finding `json:"added"`
	Removed     []Finding `json:"removed"`
	Changed     []Finding `json:"changed"`
	Resolved    []Finding `json:"resolved"`
	Regressions []Finding `json:"regressions"`
}

// NewStateManager creates a new state manager
func NewStateManager(dataDir string) *StateManager {
	return &StateManager{
		dataDir:    dataDir,
		stateFile:  filepath.Join(dataDir, "state.json"),
		historyDir: filepath.Join(dataDir, "history"),
	}
}

// Load loads the current state from disk
func (sm *StateManager) Load() (*State, error) {
	data, err := os.ReadFile(sm.stateFile)
	if err != nil {
		if os.IsNotExist(err) {
			return &State{
				Findings: []Finding{},
				Metadata: make(map[string]string),
			}, nil
		}
		return nil, fmt.Errorf("failed to read state: %w", err)
	}

	var state State
	if err := json.Unmarshal(data, &state); err != nil {
		return nil, fmt.Errorf("failed to parse state: %w", err)
	}

	return &state, nil
}

// Save saves the state to disk
func (sm *StateManager) Save(state *State) error {
	// Ensure directory exists
	if err := os.MkdirAll(sm.dataDir, 0755); err != nil {
		return fmt.Errorf("failed to create data directory: %w", err)
	}

	// Calculate content hash
	state.ContentHash = sm.calculateHash(state.Findings)

	// Save history snapshot
	if err := sm.saveHistory(state); err != nil {
		// Log but don't fail
		fmt.Fprintf(os.Stderr, "Warning: failed to save history: %v\n", err)
	}

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

	// Write to temp file first
	tmpFile := sm.stateFile + ".tmp"
	if err := os.WriteFile(tmpFile, data, 0644); err != nil {
		return fmt.Errorf("failed to write state: %w", err)
	}

	// Rename to final location (atomic on most filesystems)
	if err := os.Rename(tmpFile, sm.stateFile); err != nil {
		return fmt.Errorf("failed to rename state file: %w", err)
	}

	return nil
}

// Merge merges new findings with existing state
func (sm *StateManager) Merge(state *State, newFindings []Finding) *StateDiff {
	diff := &StateDiff{
		Added:    []Finding{},
		Removed:  []Finding{},
		Changed:  []Finding{},
		Resolved: []Finding{},
	}

	// Create lookup maps
	existingMap := make(map[string]Finding)
	for _, f := range state.Findings {
		existingMap[f.ID] = f
	}

	newMap := make(map[string]Finding)
	for _, f := range newFindings {
		newMap[f.ID] = f
	}

	// Find added and changed findings
	for _, new := range newFindings {
		if existing, ok := existingMap[new.ID]; ok {
			// Check if changed
			if !findingsEqual(existing, new) {
				diff.Changed = append(diff.Changed, new)
			}
		} else {
			// New finding
			diff.Added = append(diff.Added, new)
		}
	}

	// Find removed findings (these are resolved)
	for _, existing := range state.Findings {
		if _, ok := newMap[existing.ID]; !ok {
			if existing.Status == StatusOpen {
				diff.Resolved = append(diff.Resolved, existing)
			}
		}
	}

	// Update state
	state.Findings = newFindings
	state.LastScan = time.Now()
	state.ScanCount++

	return diff
}

// Diff compares two states
func (sm *StateManager) Diff(old, new *State) *StateDiff {
	diff := &StateDiff{
		Added:       []Finding{},
		Removed:     []Finding{},
		Changed:     []Finding{},
		Resolved:    []Finding{},
		Regressions: []Finding{},
	}

	oldMap := make(map[string]Finding)
	for _, f := range old.Findings {
		oldMap[f.ID] = f
	}

	newMap := make(map[string]Finding)
	for _, f := range new.Findings {
		newMap[f.ID] = f
	}

	for _, n := range new.Findings {
		if o, ok := oldMap[n.ID]; ok {
			if !findingsEqual(o, n) {
				diff.Changed = append(diff.Changed, n)
				// Check for regression (resolved -> open)
				if o.Status != StatusOpen && n.Status == StatusOpen {
					diff.Regressions = append(diff.Regressions, n)
				}
			}
		} else {
			diff.Added = append(diff.Added, n)
		}
	}

	for _, o := range old.Findings {
		if _, ok := newMap[o.ID]; !ok {
			diff.Removed = append(diff.Removed, o)
		}
	}

	return diff
}

// calculateHash calculates a content hash for findings
func (sm *StateManager) calculateHash(findings []Finding) string {
	// Sort findings for consistent hashing
	sort.Slice(findings, func(i, j int) bool {
		return findings[i].ID < findings[j].ID
	})

	// Create hash from findings
	data, _ := json.Marshal(findings)
	hash := sha256.Sum256(data)
	return fmt.Sprintf("%x", hash)[:16]
}

// saveHistory saves a historical snapshot
func (sm *StateManager) saveHistory(state *State) error {
	if err := os.MkdirAll(sm.historyDir, 0755); err != nil {
		return err
	}

	// Create snapshot
	snapshot := StateSnapshot{
		Timestamp:   time.Now(),
		Hash:        state.ContentHash,
		Score:       state.Scorecard.TotalScore,
		StrictScore: state.Scorecard.StrictScore,
		Findings:    len(state.Findings),
		File:        fmt.Sprintf("%s.json", state.ContentHash),
	}

	// Save snapshot file
	snapshotFile := filepath.Join(sm.historyDir, snapshot.File)
	snapshotData, _ := json.MarshalIndent(state, "", "  ")
	if err := os.WriteFile(snapshotFile, snapshotData, 0644); err != nil {
		return err
	}

	// Update history in state (keep last 50 snapshots)
	state.History = append(state.History, snapshot)
	if len(state.History) > 50 {
		// Remove old snapshots
		for _, old := range state.History[:len(state.History)-50] {
			oldFile := filepath.Join(sm.historyDir, old.File)
			os.Remove(oldFile) // Ignore errors
		}
		state.History = state.History[len(state.History)-50:]
	}

	return nil
}

// ResolveFinding updates a finding's status
func (sm *StateManager) ResolveFinding(state *State, id string, status Status, note string) error {
	for i, f := range state.Findings {
		if f.ID == id {
			state.Findings[i].Status = status
			state.Findings[i].UpdatedAt = time.Now()
			if state.Findings[i].Metadata == nil {
				state.Findings[i].Metadata = make(map[string]string)
			}
			state.Findings[i].Metadata["resolution_note"] = note
			return nil
		}
	}

	return fmt.Errorf("finding not found: %s", id)
}

// GetFinding retrieves a finding by ID
func (sm *StateManager) GetFinding(state *State, id string) *Finding {
	for _, f := range state.Findings {
		if f.ID == id {
			return &f
		}
	}
	return nil
}

// GetOpenFindings returns all open findings
func (sm *StateManager) GetOpenFindings(state *State) []Finding {
	var open []Finding
	for _, f := range state.Findings {
		if f.Status == StatusOpen {
			open = append(open, f)
		}
	}
	return open
}

// GetFindingsByTier returns findings grouped by severity
func (sm *StateManager) GetFindingsByTier(state *State) map[Severity][]Finding {
	result := make(map[Severity][]Finding)
	for _, f := range state.Findings {
		result[f.Severity] = append(result[f.Severity], f)
	}
	return result
}

// GetTrend returns the trend over the last N scans
func (sm *StateManager) GetTrend(state *State, n int) []StateSnapshot {
	if len(state.History) < n {
		return state.History
	}
	return state.History[len(state.History)-n:]
}

// findingsEqual checks if two findings are equal (excluding timestamps)
func findingsEqual(a, b Finding) bool {
	return a.ID == b.ID &&
		a.Type == b.Type &&
		a.Title == b.Title &&
		a.File == b.File &&
		a.Line == b.Line &&
		a.Severity == b.Severity &&
		a.Score == b.Score &&
		a.Status == b.Status
}

// FormatDiff formats a state diff for display
func FormatDiff(diff *StateDiff) string {
	var sb strings.Builder

	if len(diff.Added) > 0 {
		sb.WriteString(fmt.Sprintf("[+] Added: %d findings\n", len(diff.Added)))
		for _, f := range diff.Added {
			sb.WriteString(fmt.Sprintf("  - %s: %s\n", f.ID, f.Title))
		}
	}

	if len(diff.Removed) > 0 {
		sb.WriteString(fmt.Sprintf("[-] Removed: %d findings\n", len(diff.Removed)))
		for _, f := range diff.Removed {
			sb.WriteString(fmt.Sprintf("  - %s: %s\n", f.ID, f.Title))
		}
	}

	if len(diff.Changed) > 0 {
		sb.WriteString(fmt.Sprintf("[~] Changed: %d findings\n", len(diff.Changed)))
		for _, f := range diff.Changed {
			sb.WriteString(fmt.Sprintf("  - %s: %s\n", f.ID, f.Title))
		}
	}

	if len(diff.Resolved) > 0 {
		sb.WriteString(fmt.Sprintf("[OK] Resolved: %d findings\n", len(diff.Resolved)))
		for _, f := range diff.Resolved {
			sb.WriteString(fmt.Sprintf("  - %s: %s\n", f.ID, f.Title))
		}
	}

	if len(diff.Regressions) > 0 {
		sb.WriteString(fmt.Sprintf("[!] Regressions: %d findings\n", len(diff.Regressions)))
		for _, f := range diff.Regressions {
			sb.WriteString(fmt.Sprintf("  - %s: %s\n", f.ID, f.Title))
		}
	}

	if sb.Len() == 0 {
		sb.WriteString("No changes detected\n")
	}

	return sb.String()
}
