package main

import (
	"path/filepath"
	"testing"
	"time"
)

// TestDeltaSyncDeletedItemResurrection verifies deleted items don't resurrect
func TestDeltaSyncDeletedItemResurrection(t *testing.T) {
	tempDir := t.TempDir()
	server := &SyncServer{
		dataFile:  filepath.Join(tempDir, "test.json"),
		imagesDir: filepath.Join(tempDir, "images"),
		authToken: "test-token",
	}

	// Initial state: Server has one gym, one problem, one session with 8 attempts
	now := time.Now().UTC()
	gymID := "gym-1"
	problemID := "problem-1"
	sessionID := "session-1"

	initialBackup := &ClimbDataBackup{
		Version:       "2.0",
		FormatVersion: "2.0",
		Gyms: []BackupGym{
			{
				ID:                  gymID,
				Name:                "Test Gym",
				SupportedClimbTypes: []string{"BOULDER"},
				DifficultySystems:   []string{"V"},
				CreatedAt:           now.Add(-1 * time.Hour).Format(time.RFC3339),
				UpdatedAt:           now.Add(-1 * time.Hour).Format(time.RFC3339),
			},
		},
		Problems: []BackupProblem{
			{
				ID:        problemID,
				GymID:     gymID,
				ClimbType: "BOULDER",
				Difficulty: DifficultyGrade{
					System:       "V",
					Grade:        "V5",
					NumericValue: 5,
				},
				IsActive:  true,
				CreatedAt: now.Add(-1 * time.Hour).Format(time.RFC3339),
				UpdatedAt: now.Add(-1 * time.Hour).Format(time.RFC3339),
			},
		},
		Sessions: []BackupClimbSession{
			{
				ID:        sessionID,
				GymID:     gymID,
				Date:      now.Format("2006-01-02"),
				Status:    "completed",
				CreatedAt: now.Add(-30 * time.Minute).Format(time.RFC3339),
				UpdatedAt: now.Add(-30 * time.Minute).Format(time.RFC3339),
			},
		},
		Attempts:     []BackupAttempt{},
		DeletedItems: []DeletedItem{},
	}

	// Add 8 attempts
	for i := 0; i < 8; i++ {
		attempt := BackupAttempt{
			ID:        "attempt-" + string(rune('1'+i)),
			SessionID: sessionID,
			ProblemID: problemID,
			Result:    "COMPLETED",
			Timestamp: now.Add(time.Duration(-25+i) * time.Minute).Format(time.RFC3339),
			CreatedAt: now.Add(time.Duration(-25+i) * time.Minute).Format(time.RFC3339),
		}
		initialBackup.Attempts = append(initialBackup.Attempts, attempt)
	}

	if err := server.saveData(initialBackup); err != nil {
		t.Fatalf("Failed to save initial data: %v", err)
	}

	// Client 1 syncs - gets all data
	client1LastSync := now.Add(-2 * time.Hour).Format(time.RFC3339)
	deltaRequest1 := DeltaSyncRequest{
		LastSyncTime: client1LastSync,
		Gyms:         []BackupGym{},
		Problems:     []BackupProblem{},
		Sessions:     []BackupClimbSession{},
		Attempts:     []BackupAttempt{},
		DeletedItems: []DeletedItem{},
	}

	// Simulate delta sync for client 1
	serverBackup, _ := server.loadData()
	serverBackup.DeletedItems = server.mergeDeletedItems(serverBackup.DeletedItems, deltaRequest1.DeletedItems)
	server.applyDeletions(serverBackup, serverBackup.DeletedItems)

	if len(serverBackup.Sessions) != 1 {
		t.Errorf("Expected 1 session after client1 sync, got %d", len(serverBackup.Sessions))
	}
	if len(serverBackup.Attempts) != 8 {
		t.Errorf("Expected 8 attempts after client1 sync, got %d", len(serverBackup.Attempts))
	}

	// Client 1 deletes the session locally
	deleteTime := now.Format(time.RFC3339)
	deletions := []DeletedItem{
		{ID: sessionID, Type: "session", DeletedAt: deleteTime},
	}
	// Also track attempt deletions
	for _, attempt := range initialBackup.Attempts {
		deletions = append(deletions, DeletedItem{
			ID:        attempt.ID,
			Type:      "attempt",
			DeletedAt: deleteTime,
		})
	}

	// Client 1 syncs deletion
	deltaRequest2 := DeltaSyncRequest{
		LastSyncTime: now.Add(-5 * time.Minute).Format(time.RFC3339),
		Gyms:         []BackupGym{},
		Problems:     []BackupProblem{},
		Sessions:     []BackupClimbSession{},
		Attempts:     []BackupAttempt{},
		DeletedItems: deletions,
	}

	// Server processes deletion
	serverBackup, _ = server.loadData()
	serverBackup.DeletedItems = server.mergeDeletedItems(serverBackup.DeletedItems, deltaRequest2.DeletedItems)
	server.applyDeletions(serverBackup, serverBackup.DeletedItems)
	server.saveData(serverBackup)

	// Verify deletions were applied on server
	serverBackup, _ = server.loadData()
	if len(serverBackup.Sessions) != 0 {
		t.Errorf("Expected 0 sessions after deletion, got %d", len(serverBackup.Sessions))
	}
	if len(serverBackup.Attempts) != 0 {
		t.Errorf("Expected 0 attempts after deletion, got %d", len(serverBackup.Attempts))
	}
	if len(serverBackup.DeletedItems) != 9 {
		t.Errorf("Expected 9 deletion records, got %d", len(serverBackup.DeletedItems))
	}

	// Client does local reset and pulls from server
	deltaRequest3 := DeltaSyncRequest{
		LastSyncTime: time.Time{}.Format(time.RFC3339),
		Gyms:         []BackupGym{},
		Problems:     []BackupProblem{},
		Sessions:     []BackupClimbSession{},
		Attempts:     []BackupAttempt{},
		DeletedItems: []DeletedItem{},
	}

	serverBackup, _ = server.loadData()
	clientLastSync, _ := time.Parse(time.RFC3339, deltaRequest3.LastSyncTime)

	// Build response
	response := DeltaSyncResponse{
		ServerTime:   time.Now().UTC().Format(time.RFC3339),
		Gyms:         []BackupGym{},
		Problems:     []BackupProblem{},
		Sessions:     []BackupClimbSession{},
		Attempts:     []BackupAttempt{},
		DeletedItems: []DeletedItem{},
	}

	// Build deleted item map
	deletedItemMap := make(map[string]bool)
	for _, item := range serverBackup.DeletedItems {
		key := item.Type + ":" + item.ID
		deletedItemMap[key] = true
	}

	// Filter sessions (excluding deleted)
	for _, session := range serverBackup.Sessions {
		if deletedItemMap["session:"+session.ID] {
			continue
		}
		sessionTime, _ := time.Parse(time.RFC3339, session.UpdatedAt)
		if sessionTime.After(clientLastSync) {
			response.Sessions = append(response.Sessions, session)
		}
	}

	// Filter attempts (excluding deleted)
	for _, attempt := range serverBackup.Attempts {
		if deletedItemMap["attempt:"+attempt.ID] {
			continue
		}
		attemptTime, _ := time.Parse(time.RFC3339, attempt.CreatedAt)
		if attemptTime.After(clientLastSync) {
			response.Attempts = append(response.Attempts, attempt)
		}
	}

	// Send deletion records
	for _, deletion := range serverBackup.DeletedItems {
		deletionTime, _ := time.Parse(time.RFC3339, deletion.DeletedAt)
		if deletionTime.After(clientLastSync) {
			response.DeletedItems = append(response.DeletedItems, deletion)
		}
	}

	if len(response.Sessions) != 0 {
		t.Errorf("Deleted session was resurrected! Got %d sessions in response", len(response.Sessions))
	}
	if len(response.Attempts) != 0 {
		t.Errorf("Deleted attempts were resurrected! Got %d attempts in response", len(response.Attempts))
	}
	if len(response.DeletedItems) < 9 {
		t.Errorf("Expected at least 9 deletion records in response, got %d", len(response.DeletedItems))
	}
}

// TestDeltaSyncAttemptCount verifies all attempts are preserved
func TestDeltaSyncAttemptCount(t *testing.T) {
	tempDir := t.TempDir()
	server := &SyncServer{
		dataFile:  filepath.Join(tempDir, "test.json"),
		imagesDir: filepath.Join(tempDir, "images"),
		authToken: "test-token",
	}

	now := time.Now().UTC()
	gymID := "gym-1"
	problemID := "problem-1"
	sessionID := "session-1"

	// Create session with 8 attempts
	initialBackup := &ClimbDataBackup{
		Version:       "2.0",
		FormatVersion: "2.0",
		Gyms:          []BackupGym{{ID: gymID, Name: "Test Gym", SupportedClimbTypes: []string{"BOULDER"}, DifficultySystems: []string{"V"}, CreatedAt: now.Format(time.RFC3339), UpdatedAt: now.Format(time.RFC3339)}},
		Problems:      []BackupProblem{{ID: problemID, GymID: gymID, ClimbType: "BOULDER", Difficulty: DifficultyGrade{System: "V", Grade: "V5", NumericValue: 5}, IsActive: true, CreatedAt: now.Format(time.RFC3339), UpdatedAt: now.Format(time.RFC3339)}},
		Sessions:      []BackupClimbSession{{ID: sessionID, GymID: gymID, Date: now.Format("2006-01-02"), Status: "completed", CreatedAt: now.Format(time.RFC3339), UpdatedAt: now.Format(time.RFC3339)}},
		Attempts:      []BackupAttempt{},
		DeletedItems:  []DeletedItem{},
	}

	// Add 8 attempts at different times
	baseTime := now.Add(-30 * time.Minute)
	for i := 0; i < 8; i++ {
		attempt := BackupAttempt{
			ID:        "attempt-" + string(rune('1'+i)),
			SessionID: sessionID,
			ProblemID: problemID,
			Result:    "COMPLETED",
			Timestamp: baseTime.Add(time.Duration(i) * time.Minute).Format(time.RFC3339),
			CreatedAt: baseTime.Add(time.Duration(i) * time.Minute).Format(time.RFC3339),
		}
		initialBackup.Attempts = append(initialBackup.Attempts, attempt)
	}

	if err := server.saveData(initialBackup); err != nil {
		t.Fatalf("Failed to save initial data: %v", err)
	}

	// Client syncs with lastSyncTime BEFORE all attempts were created
	clientLastSync := baseTime.Add(-1 * time.Hour)

	serverBackup, _ := server.loadData()

	// Count attempts that should be returned
	attemptCount := 0
	for _, attempt := range serverBackup.Attempts {
		attemptTime, _ := time.Parse(time.RFC3339, attempt.CreatedAt)
		if attemptTime.After(clientLastSync) {
			attemptCount++
		}
	}

	if attemptCount != 8 {
		t.Errorf("Expected all 8 attempts to be returned, got %d", attemptCount)
	}

}

// TestTombstoneCleanup verifies old deletion records are cleaned up
func TestTombstoneCleanup(t *testing.T) {
	server := &SyncServer{}

	now := time.Now().UTC()
	oldDeletion := DeletedItem{
		ID:        "old-item",
		Type:      "session",
		DeletedAt: now.Add(-31 * 24 * time.Hour).Format(time.RFC3339), // 31 days old
	}
	recentDeletion := DeletedItem{
		ID:        "recent-item",
		Type:      "session",
		DeletedAt: now.Add(-1 * 24 * time.Hour).Format(time.RFC3339), // 1 day old
	}

	existing := []DeletedItem{oldDeletion}
	updates := []DeletedItem{recentDeletion}

	merged := server.mergeDeletedItems(existing, updates)

	// Old deletion should be cleaned up, only recent one remains
	if len(merged) != 1 {
		t.Errorf("Expected 1 deletion record after cleanup, got %d", len(merged))
	}
	if len(merged) > 0 && merged[0].ID != "recent-item" {
		t.Errorf("Expected recent deletion to remain, got %s", merged[0].ID)
	}

}

// TestMergeDeletedItemsDeduplication verifies duplicate deletions are handled
func TestMergeDeletedItemsDeduplication(t *testing.T) {
	server := &SyncServer{}

	now := time.Now().UTC()
	deletion1 := DeletedItem{
		ID:        "item-1",
		Type:      "session",
		DeletedAt: now.Add(-1 * time.Hour).Format(time.RFC3339),
	}
	deletion2 := DeletedItem{
		ID:        "item-1",
		Type:      "session",
		DeletedAt: now.Format(time.RFC3339), // Newer timestamp
	}

	existing := []DeletedItem{deletion1}
	updates := []DeletedItem{deletion2}

	merged := server.mergeDeletedItems(existing, updates)

	if len(merged) != 1 {
		t.Errorf("Expected 1 deletion record, got %d", len(merged))
	}
	if len(merged) > 0 && merged[0].DeletedAt != deletion2.DeletedAt {
		t.Errorf("Expected newer deletion timestamp to be kept")
	}

}

// TestApplyDeletions verifies deletions are applied correctly
func TestApplyDeletions(t *testing.T) {
	server := &SyncServer{}

	now := time.Now().UTC()
	backup := &ClimbDataBackup{
		Version:       "2.0",
		FormatVersion: "2.0",
		Gyms:          []BackupGym{{ID: "gym-1", Name: "Test Gym", SupportedClimbTypes: []string{}, DifficultySystems: []string{}, CreatedAt: now.Format(time.RFC3339), UpdatedAt: now.Format(time.RFC3339)}},
		Problems:      []BackupProblem{{ID: "problem-1", GymID: "gym-1", ClimbType: "BOULDER", Difficulty: DifficultyGrade{System: "V", Grade: "V5", NumericValue: 5}, IsActive: true, CreatedAt: now.Format(time.RFC3339), UpdatedAt: now.Format(time.RFC3339)}},
		Sessions:      []BackupClimbSession{{ID: "session-1", GymID: "gym-1", Date: now.Format("2006-01-02"), Status: "completed", CreatedAt: now.Format(time.RFC3339), UpdatedAt: now.Format(time.RFC3339)}},
		Attempts:      []BackupAttempt{{ID: "attempt-1", SessionID: "session-1", ProblemID: "problem-1", Result: "COMPLETED", Timestamp: now.Format(time.RFC3339), CreatedAt: now.Format(time.RFC3339)}},
		DeletedItems:  []DeletedItem{},
	}

	deletions := []DeletedItem{
		{ID: "session-1", Type: "session", DeletedAt: now.Format(time.RFC3339)},
		{ID: "attempt-1", Type: "attempt", DeletedAt: now.Format(time.RFC3339)},
	}

	server.applyDeletions(backup, deletions)

	if len(backup.Sessions) != 0 {
		t.Errorf("Expected 0 sessions after deletion, got %d", len(backup.Sessions))
	}
	if len(backup.Attempts) != 0 {
		t.Errorf("Expected 0 attempts after deletion, got %d", len(backup.Attempts))
	}
	if len(backup.Gyms) != 1 {
		t.Errorf("Expected gym to remain, got %d gyms", len(backup.Gyms))
	}
	if len(backup.Problems) != 1 {
		t.Errorf("Expected problem to remain, got %d problems", len(backup.Problems))
	}

}

// TestCascadingDeletions verifies related items are handled properly
func TestCascadingDeletions(t *testing.T) {
	server := &SyncServer{}

	now := time.Now().UTC()
	sessionID := "session-1"
	backup := &ClimbDataBackup{
		Version:       "2.0",
		FormatVersion: "2.0",
		Gyms:          []BackupGym{{ID: "gym-1", Name: "Test Gym", SupportedClimbTypes: []string{}, DifficultySystems: []string{}, CreatedAt: now.Format(time.RFC3339), UpdatedAt: now.Format(time.RFC3339)}},
		Problems:      []BackupProblem{{ID: "problem-1", GymID: "gym-1", ClimbType: "BOULDER", Difficulty: DifficultyGrade{System: "V", Grade: "V5", NumericValue: 5}, IsActive: true, CreatedAt: now.Format(time.RFC3339), UpdatedAt: now.Format(time.RFC3339)}},
		Sessions:      []BackupClimbSession{{ID: sessionID, GymID: "gym-1", Date: now.Format("2006-01-02"), Status: "completed", CreatedAt: now.Format(time.RFC3339), UpdatedAt: now.Format(time.RFC3339)}},
		Attempts:      []BackupAttempt{},
		DeletedItems:  []DeletedItem{},
	}

	// Add multiple attempts for the session
	for i := 0; i < 5; i++ {
		backup.Attempts = append(backup.Attempts, BackupAttempt{
			ID:        "attempt-" + string(rune('1'+i)),
			SessionID: sessionID,
			ProblemID: "problem-1",
			Result:    "COMPLETED",
			Timestamp: now.Format(time.RFC3339),
			CreatedAt: now.Format(time.RFC3339),
		})
	}

	// Delete session - attempts should also be tracked as deleted
	deletions := []DeletedItem{
		{ID: sessionID, Type: "session", DeletedAt: now.Format(time.RFC3339)},
	}
	for _, attempt := range backup.Attempts {
		deletions = append(deletions, DeletedItem{
			ID:        attempt.ID,
			Type:      "attempt",
			DeletedAt: now.Format(time.RFC3339),
		})
	}

	server.applyDeletions(backup, deletions)

	if len(backup.Sessions) != 0 {
		t.Errorf("Expected session to be deleted, got %d sessions", len(backup.Sessions))
	}
	if len(backup.Attempts) != 0 {
		t.Errorf("Expected all attempts to be deleted, got %d attempts", len(backup.Attempts))
	}

}

// TestFullSyncAfterReset verifies the reported user scenario
func TestFullSyncAfterReset(t *testing.T) {
	tempDir := t.TempDir()
	server := &SyncServer{
		dataFile:  filepath.Join(tempDir, "test.json"),
		imagesDir: filepath.Join(tempDir, "images"),
		authToken: "test-token",
	}

	now := time.Now().UTC()

	// Initial sync with data
	initialData := &ClimbDataBackup{
		Version:       "2.0",
		FormatVersion: "2.0",
		Gyms:          []BackupGym{{ID: "gym-1", Name: "Test Gym", SupportedClimbTypes: []string{"BOULDER"}, DifficultySystems: []string{"V"}, CreatedAt: now.Format(time.RFC3339), UpdatedAt: now.Format(time.RFC3339)}},
		Problems:      []BackupProblem{{ID: "problem-1", GymID: "gym-1", ClimbType: "BOULDER", Difficulty: DifficultyGrade{System: "V", Grade: "V5", NumericValue: 5}, IsActive: true, CreatedAt: now.Format(time.RFC3339), UpdatedAt: now.Format(time.RFC3339)}},
		Sessions:      []BackupClimbSession{{ID: "session-1", GymID: "gym-1", Date: now.Format("2006-01-02"), Status: "completed", CreatedAt: now.Format(time.RFC3339), UpdatedAt: now.Format(time.RFC3339)}},
		Attempts:      []BackupAttempt{},
		DeletedItems:  []DeletedItem{},
	}
	for i := 0; i < 8; i++ {
		initialData.Attempts = append(initialData.Attempts, BackupAttempt{
			ID:        "attempt-" + string(rune('1'+i)),
			SessionID: "session-1",
			ProblemID: "problem-1",
			Result:    "COMPLETED",
			Timestamp: now.Add(time.Duration(i) * time.Minute).Format(time.RFC3339),
			CreatedAt: now.Add(time.Duration(i) * time.Minute).Format(time.RFC3339),
		})
	}
	server.saveData(initialData)

	// Client deletes everything and syncs
	deletions := []DeletedItem{
		{ID: "gym-1", Type: "gym", DeletedAt: now.Add(10 * time.Minute).Format(time.RFC3339)},
		{ID: "problem-1", Type: "problem", DeletedAt: now.Add(10 * time.Minute).Format(time.RFC3339)},
		{ID: "session-1", Type: "session", DeletedAt: now.Add(10 * time.Minute).Format(time.RFC3339)},
	}
	for i := 0; i < 8; i++ {
		deletions = append(deletions, DeletedItem{
			ID:        "attempt-" + string(rune('1'+i)),
			Type:      "attempt",
			DeletedAt: now.Add(10 * time.Minute).Format(time.RFC3339),
		})
	}

	serverBackup, _ := server.loadData()
	serverBackup.DeletedItems = server.mergeDeletedItems(serverBackup.DeletedItems, deletions)
	server.applyDeletions(serverBackup, serverBackup.DeletedItems)
	server.saveData(serverBackup)

	// Client does local reset and pulls from server
	serverBackup, _ = server.loadData()

	if len(serverBackup.Gyms) != 0 {
		t.Errorf("Expected 0 gyms, got %d", len(serverBackup.Gyms))
	}
	if len(serverBackup.Problems) != 0 {
		t.Errorf("Expected 0 problems, got %d", len(serverBackup.Problems))
	}
	if len(serverBackup.Sessions) != 0 {
		t.Errorf("Expected 0 sessions, got %d", len(serverBackup.Sessions))
	}
	if len(serverBackup.Attempts) != 0 {
		t.Errorf("Expected 0 attempts, got %d", len(serverBackup.Attempts))
	}
	if len(serverBackup.DeletedItems) == 0 {
		t.Errorf("Expected deletion records, got 0")
	}
}
