package quality

import (
	"encoding/json"
	"os"
	"path/filepath"
	"testing"
	"time"
)

func TestNewStateManager(t *testing.T) {
	dataDir := "/tmp/test_state"
	sm := NewStateManager(dataDir)

	if sm == nil {
		t.Error("NewStateManager() should not return nil")
	}

	if sm.dataDir != dataDir {
		t.Errorf("NewStateManager() dataDir = %v, want %v", sm.dataDir, dataDir)
	}

	expectedStateFile := filepath.Join(dataDir, "state.json")
	if sm.stateFile != expectedStateFile {
		t.Errorf("NewStateManager() stateFile = %v, want %v", sm.stateFile, expectedStateFile)
	}

	expectedHistoryDir := filepath.Join(dataDir, "history")
	if sm.historyDir != expectedHistoryDir {
		t.Errorf("NewStateManager() historyDir = %v, want %v", sm.historyDir, expectedHistoryDir)
	}
}

func TestStateManager_Load(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "state_test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	sm := NewStateManager(tmpDir)

	// Test loading non-existent file
	state, err := sm.Load()
	if err != nil {
		t.Errorf("Load() should not error for non-existent file: %v", err)
	}

	if state == nil {
		t.Error("Load() should return empty state for non-existent file")
	}

	if len(state.Findings) != 0 {
		t.Errorf("Load() should return empty findings for non-existent file, got %d", len(state.Findings))
	}

	if state.Metadata == nil {
		t.Error("Load() should initialize metadata map")
	}

	// Test loading existing file
	testState := &State{
		Findings: []Finding{
			{ID: "test1", Type: "test", Title: "Test Finding 1", Status: StatusOpen},
			{ID: "test2", Type: "test", Title: "Test Finding 2", Status: StatusFixed},
		},
		ScanCount: 5,
		Metadata:  map[string]string{"env": "test"},
	}

	data, _ := json.Marshal(testState)
	stateFile := sm.stateFile
	os.WriteFile(stateFile, data, 0644)

	loadedState, err := sm.Load()
	if err != nil {
		t.Errorf("Load() failed: %v", err)
	}

	if loadedState.ScanCount != 5 {
		t.Errorf("Load() ScanCount = %v, want 5", loadedState.ScanCount)
	}

	if len(loadedState.Findings) != 2 {
		t.Errorf("Load() findings count = %v, want 2", len(loadedState.Findings))
	}

	if loadedState.Metadata["env"] != "test" {
		t.Errorf("Load() metadata = %v, want test", loadedState.Metadata["env"])
	}
}

func TestStateManager_Load_InvalidJSON(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "state_test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	sm := NewStateManager(tmpDir)

	// Write invalid JSON
	stateFile := sm.stateFile
	os.WriteFile(stateFile, []byte("{ invalid json"), 0644)

	_, err = sm.Load()
	if err == nil {
		t.Error("Load() should error for invalid JSON")
	}
}

func TestStateManager_Save(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "state_test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	sm := NewStateManager(tmpDir)

	state := &State{
		Findings: []Finding{
			{ID: "test1", Type: "test", Title: "Test Finding", Status: StatusOpen},
		},
		Scorecard: &Scorecard{TotalScore: 100, StrictScore: 50},
		ScanCount: 1,
		Metadata:  map[string]string{"env": "test"},
	}

	err = sm.Save(state)
	if err != nil {
		t.Errorf("Save() failed: %v", err)
	}

	// Verify file was created
	if _, err := os.Stat(sm.stateFile); err != nil {
		t.Errorf("Save() should create state file: %v", err)
	}

	// Verify content hash was calculated
	if state.ContentHash == "" {
		t.Error("Save() should calculate content hash")
	}

	// Load and verify
	loadedState, err := sm.Load()
	if err != nil {
		t.Errorf("Save() failed to load saved state: %v", err)
	}

	if len(loadedState.Findings) != 1 {
		t.Errorf("Save() should save findings, got %d", len(loadedState.Findings))
	}

	if loadedState.ScanCount != 1 {
		t.Errorf("Save() should increment scan count, got %d", loadedState.ScanCount)
	}
}

func TestStateManager_Save_History(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "state_test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	sm := NewStateManager(tmpDir)

	state := &State{
		Findings:  []Finding{{ID: "test", Type: "test", Title: "Test", Status: StatusOpen}},
		Scorecard: &Scorecard{TotalScore: 100, StrictScore: 50},
	}

	err = sm.Save(state)
	if err != nil {
		t.Errorf("Save() failed: %v", err)
	}

	// Check history directory was created
	if _, err := os.Stat(sm.historyDir); err != nil {
		t.Errorf("Save() should create history directory: %v", err)
	}

	// Check history file was created
	historyFiles, err := filepath.Glob(filepath.Join(sm.historyDir, "*.json"))
	if err != nil {
		t.Errorf("Save() failed to list history files: %v", err)
	}

	if len(historyFiles) != 1 {
		t.Errorf("Save() should create 1 history file, got %d", len(historyFiles))
	}
}

func TestStateManager_Merge(t *testing.T) {
	sm := NewStateManager("/tmp")

	existingState := &State{
		Findings: []Finding{
			{ID: "existing1", Type: "test", Title: "Existing 1", Status: StatusOpen, Score: 5},
			{ID: "existing2", Type: "test", Title: "Existing 2", Status: StatusOpen, Score: 10},
			{ID: "existing3", Type: "test", Title: "Existing 3", Status: StatusOpen, Score: 15},
		},
	}

	newFindings := []Finding{
		{ID: "existing1", Type: "test", Title: "Existing 1 Changed", Status: StatusOpen, Score: 5}, // Changed
		{ID: "new1", Type: "test", Title: "New Finding", Status: StatusOpen, Score: 20},            // Added
		{ID: "new2", Type: "test", Title: "New Finding 2", Status: StatusOpen, Score: 25},          // Added
	}

	diff := sm.Merge(existingState, newFindings)

	if len(diff.Added) != 2 {
		t.Errorf("Merge() added count = %v, want 2", len(diff.Added))
	}

	if len(diff.Changed) != 1 {
		t.Errorf("Merge() changed count = %v, want 1", len(diff.Changed))
	}

	if len(diff.Resolved) != 2 {
		t.Errorf("Merge() resolved count = %v, want 2", len(diff.Resolved))
	}

	if len(existingState.Findings) != 3 {
		t.Errorf("Merge() should update state findings count to %d, got %d", len(newFindings), len(existingState.Findings))
	}

	if existingState.ScanCount != 1 {
		t.Errorf("Merge() should increment scan count to 1, got %d", existingState.ScanCount)
	}
}

func TestStateManager_Merge_Resolved(t *testing.T) {
	sm := NewStateManager("/tmp")

	existingState := &State{
		Findings: []Finding{
			{ID: "open1", Type: "test", Title: "Open Finding", Status: StatusOpen},
			{ID: "open2", Type: "test", Title: "Open Finding 2", Status: StatusOpen},
		},
	}

	newFindings := []Finding{
		{ID: "open1", Type: "test", Title: "Open Finding", Status: StatusOpen}, // Kept
		{ID: "new1", Type: "test", Title: "New Finding", Status: StatusOpen},   // Added
	}

	diff := sm.Merge(existingState, newFindings)

	if len(diff.Added) != 1 {
		t.Errorf("Merge() added count = %v, want 1", len(diff.Added))
	}

	if len(diff.Resolved) != 1 {
		t.Errorf("Merge() resolved count = %v, want 1", len(diff.Resolved))
	}

	if diff.Resolved[0].ID != "open2" {
		t.Errorf("Merge() resolved wrong finding: %s", diff.Resolved[0].ID)
	}
}

func TestStateManager_Diff(t *testing.T) {
	sm := NewStateManager("/tmp")

	oldState := &State{
		Findings: []Finding{
			{ID: "old1", Type: "test", Title: "Old Finding", Status: StatusOpen},
			{ID: "old2", Type: "test", Title: "Old Finding 2", Status: StatusFixed},
		},
	}

	newState := &State{
		Findings: []Finding{
			{ID: "old1", Type: "test", Title: "Old Finding Changed", Status: StatusOpen}, // Changed
			{ID: "new1", Type: "test", Title: "New Finding", Status: StatusOpen},         // Added
			{ID: "old2", Type: "test", Title: "Old Finding 2", Status: StatusOpen},       // Regression
		},
	}

	diff := sm.Diff(oldState, newState)

	if len(diff.Added) != 1 {
		t.Errorf("Diff() added count = %v, want 1", len(diff.Added))
	}

	if len(diff.Changed) != 2 {
		t.Errorf("Diff() changed count = %v, want 2", len(diff.Changed))
	}

	if len(diff.Removed) != 0 {
		t.Errorf("Diff() removed count = %v, want 0", len(diff.Removed))
	}

	if len(diff.Regressions) != 1 {
		t.Errorf("Diff() regressions count = %v, want 1", len(diff.Regressions))
	}
}

func TestStateManager_calculateHash(t *testing.T) {
	sm := NewStateManager("/tmp")

	findings := []Finding{
		{ID: "test1", Type: "test", Title: "Test 1", Status: StatusOpen},
		{ID: "test2", Type: "test", Title: "Test 2", Status: StatusOpen},
	}

	hash1 := sm.calculateHash(findings)
	hash2 := sm.calculateHash(findings)

	if hash1 != hash2 {
		t.Errorf("calculateHash() should be deterministic, got %s and %s", hash1, hash2)
	}

	if len(hash1) != 16 {
		t.Errorf("calculateHash() should return 16 character hash, got %d", len(hash1))
	}

	// Test with different order
	reversed := []Finding{findings[1], findings[0]}
	hash3 := sm.calculateHash(reversed)

	if hash1 != hash3 {
		t.Errorf("calculateHash() should be order-independent, got %s and %s", hash1, hash3)
	}
}

func TestStateManager_saveHistory(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "state_test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	sm := NewStateManager(tmpDir)

	state := &State{
		Findings:    []Finding{{ID: "test", Type: "test", Title: "Test", Status: StatusOpen}},
		Scorecard:   &Scorecard{TotalScore: 100, StrictScore: 50},
		ContentHash: "testhash",
	}

	err = sm.saveHistory(state)
	if err != nil {
		t.Errorf("saveHistory() failed: %v", err)
	}

	// Check history file was created
	files, err := filepath.Glob(filepath.Join(sm.historyDir, "*.json"))
	if err != nil {
		t.Errorf("saveHistory() failed to list files: %v", err)
	}

	if len(files) != 1 {
		t.Errorf("saveHistory() should create 1 file, got %d", len(files))
	}

	// Verify snapshot content
	snapshotFile := files[0]
	data, err := os.ReadFile(snapshotFile)
	if err != nil {
		t.Errorf("saveHistory() failed to read snapshot file: %v", err)
	}

	// The file contains the full state, not a snapshot
	var savedState State
	if err := json.Unmarshal(data, &savedState); err != nil {
		t.Errorf("saveHistory() failed to parse saved state: %v", err)
	}

	if len(savedState.Findings) != 1 {
		t.Errorf("saveHistory() saved state findings count = %v, want 1", len(savedState.Findings))
	}

	if savedState.Scorecard.TotalScore != 100 {
		t.Errorf("saveHistory() saved state score = %v, want 100", savedState.Scorecard.TotalScore)
	}

	if savedState.ContentHash != "testhash" {
		t.Errorf("saveHistory() saved state hash = %v, want testhash", savedState.ContentHash)
	}
}

func TestStateManager_ResolveFinding(t *testing.T) {
	sm := NewStateManager("/tmp")

	state := &State{
		Findings: []Finding{
			{ID: "test1", Type: "test", Title: "Test Finding", Status: StatusOpen},
			{ID: "test2", Type: "test", Title: "Test Finding 2", Status: StatusOpen},
		},
	}

	// Resolve existing finding
	err := sm.ResolveFinding(state, "test1", StatusFixed, "Fixed the issue")
	if err != nil {
		t.Errorf("ResolveFinding() failed: %v", err)
	}

	if state.Findings[0].Status != StatusFixed {
		t.Errorf("ResolveFinding() status = %v, want Fixed", state.Findings[0].Status)
	}

	if state.Findings[0].Metadata["resolution_note"] != "Fixed the issue" {
		t.Errorf("ResolveFinding() resolution_note = %v, want 'Fixed the issue'", state.Findings[0].Metadata["resolution_note"])
	}

	// Try to resolve non-existent finding
	err = sm.ResolveFinding(state, "nonexistent", StatusFixed, "note")
	if err == nil {
		t.Error("ResolveFinding() should error for non-existent finding")
	}
}

func TestStateManager_GetFinding(t *testing.T) {
	sm := NewStateManager("/tmp")

	state := &State{
		Findings: []Finding{
			{ID: "test1", Type: "test", Title: "Test Finding", Status: StatusOpen},
			{ID: "test2", Type: "test", Title: "Test Finding 2", Status: StatusOpen},
		},
	}

	// Get existing finding
	finding := sm.GetFinding(state, "test1")
	if finding == nil {
		t.Error("GetFinding() should return finding for existing ID")
	}

	if finding.ID != "test1" {
		t.Errorf("GetFinding() returned wrong finding ID: %s", finding.ID)
	}

	// Get non-existent finding
	finding = sm.GetFinding(state, "nonexistent")
	if finding != nil {
		t.Error("GetFinding() should return nil for non-existent ID")
	}
}

func TestStateManager_GetOpenFindings(t *testing.T) {
	sm := NewStateManager("/tmp")

	state := &State{
		Findings: []Finding{
			{ID: "open1", Type: "test", Title: "Open Finding", Status: StatusOpen},
			{ID: "fixed1", Type: "test", Title: "Fixed Finding", Status: StatusFixed},
			{ID: "open2", Type: "test", Title: "Open Finding 2", Status: StatusOpen},
		},
	}

	open := sm.GetOpenFindings(state)
	if len(open) != 2 {
		t.Errorf("GetOpenFindings() count = %v, want 2", len(open))
	}

	for _, f := range open {
		if f.Status != StatusOpen {
			t.Errorf("GetOpenFindings() should only return open findings, got %v", f.Status)
		}
	}
}

func TestStateManager_GetFindingsByTier(t *testing.T) {
	sm := NewStateManager("/tmp")

	state := &State{
		Findings: []Finding{
			{ID: "t1", Type: "test", Title: "T1 Finding", Status: StatusOpen, Severity: SeverityT1},
			{ID: "t2", Type: "test", Title: "T2 Finding", Status: StatusOpen, Severity: SeverityT2},
			{ID: "t3", Type: "test", Title: "T3 Finding", Status: StatusOpen, Severity: SeverityT3},
			{ID: "t4", Type: "test", Title: "T4 Finding", Status: StatusOpen, Severity: SeverityT4},
		},
	}

	byTier := sm.GetFindingsByTier(state)
	if len(byTier) != 4 {
		t.Errorf("GetFindingsByTier() should return 4 tiers, got %d", len(byTier))
	}

	if len(byTier[SeverityT1]) != 1 {
		t.Errorf("GetFindingsByTier() T1 count = %v, want 1", len(byTier[SeverityT1]))
	}

	if len(byTier[SeverityT4]) != 1 {
		t.Errorf("GetFindingsByTier() T4 count = %v, want 1", len(byTier[SeverityT4]))
	}
}

func TestStateManager_GetTrend(t *testing.T) {
	sm := NewStateManager("/tmp")

	state := &State{
		History: []StateSnapshot{
			{Timestamp: time.Now().Add(-4 * time.Hour), Score: 100, Findings: 10},
			{Timestamp: time.Now().Add(-3 * time.Hour), Score: 90, Findings: 12},
			{Timestamp: time.Now().Add(-2 * time.Hour), Score: 80, Findings: 15},
			{Timestamp: time.Now().Add(-1 * time.Hour), Score: 70, Findings: 18},
			{Timestamp: time.Now(), Score: 60, Findings: 20},
		},
	}

	// Get last 3 snapshots
	trend := sm.GetTrend(state, 3)
	if len(trend) != 3 {
		t.Errorf("GetTrend() should return 3 snapshots, got %d", len(trend))
	}

	// Verify order (should be chronological, oldest first)
	if trend[0].Score != 80 {
		t.Errorf("GetTrend() first snapshot should be oldest: %d", trend[0].Score)
	}

	if trend[2].Score != 60 {
		t.Errorf("GetTrend() last snapshot should be most recent: %d", trend[2].Score)
	}

	// Request more than available
	allTrend := sm.GetTrend(state, 10)
	if len(allTrend) != 5 {
		t.Errorf("GetTrend() should return all available snapshots when requesting more than available: %d", len(allTrend))
	}
}

func TestFindingsEqual(t *testing.T) {
	finding1 := Finding{
		ID: "test", Type: "test", Title: "Test", File: "test.go", Line: 10,
		Severity: SeverityT2, Score: 5, Status: StatusOpen,
	}

	finding2 := Finding{
		ID: "test", Type: "test", Title: "Test", File: "test.go", Line: 10,
		Severity: SeverityT2, Score: 5, Status: StatusOpen,
	}

	finding3 := Finding{
		ID: "different", Type: "test", Title: "Different", File: "test.go", Line: 10,
		Severity: SeverityT2, Score: 5, Status: StatusOpen,
	}

	if !findingsEqual(finding1, finding2) {
		t.Error("findingsEqual() should return true for equal findings")
	}

	if findingsEqual(finding1, finding3) {
		t.Error("findingsEqual() should return false for different findings")
	}
}

func TestFindingsEqual_DifferentStatus(t *testing.T) {
	finding1 := Finding{ID: "test", Type: "test", Status: StatusOpen}
	finding2 := Finding{ID: "test", Type: "test", Status: StatusFixed}

	if findingsEqual(finding1, finding2) {
		t.Error("findingsEqual() should return false for different status")
	}
}

func TestFormatDiff(t *testing.T) {
	diff := &StateDiff{
		Added: []Finding{
			{ID: "new1", Title: "New Finding 1"},
			{ID: "new2", Title: "New Finding 2"},
		},
		Removed: []Finding{
			{ID: "old1", Title: "Old Finding 1"},
		},
		Changed: []Finding{
			{ID: "changed1", Title: "Changed Finding 1"},
		},
		Resolved: []Finding{
			{ID: "resolved1", Title: "Resolved Finding 1"},
		},
		Regressions: []Finding{
			{ID: "regression1", Title: "Regression Finding 1"},
		},
	}

	output := FormatDiff(diff)

	expected := "[+] Added: 2 findings\n  - new1: New Finding 1\n  - new2: New Finding 2\n[-] Removed: 1 findings\n  - old1: Old Finding 1\n[~] Changed: 1 findings\n  - changed1: Changed Finding 1\n[OK] Resolved: 1 findings\n  - resolved1: Resolved Finding 1\n[!] Regressions: 1 findings\n  - regression1: Regression Finding 1\n"

	if output != expected {
		t.Errorf("FormatDiff() output mismatch:\nGot:\n%s\nExpected:\n%s", output, expected)
	}
}

func TestFormatDiff_Empty(t *testing.T) {
	diff := &StateDiff{}

	output := FormatDiff(diff)

	expected := "No changes detected\n"

	if output != expected {
		t.Errorf("FormatDiff() empty diff output mismatch:\nGot:\n%s\nExpected:\n%s", output, expected)
	}
}
