package main

import (
	"encoding/json"
	"strings"
	"testing"
)

func TestDataFormatCompatibility(t *testing.T) {
	t.Run("JSON Marshaling and Unmarshaling", func(t *testing.T) {
		originalBackup := ClimbDataBackup{
			ExportedAt:    "2024-01-01T10:00:00Z",
			Version:       "2.0",
			FormatVersion: "2.0",
			Gyms: []BackupGym{
				{
					ID:                     "gym1",
					Name:                   "Test Gym",
					Location:               stringPtr("Test Location"),
					SupportedClimbTypes:    []string{"BOULDER", "ROPE"},
					DifficultySystems:      []string{"V", "YDS"},
					CustomDifficultyGrades: []string{"V0+", "V1+"},
					Notes:                  stringPtr("Test notes"),
					CreatedAt:              "2024-01-01T10:00:00Z",
					UpdatedAt:              "2024-01-01T10:00:00Z",
				},
			},
			Problems: []BackupProblem{
				{
					ID:          "problem1",
					GymID:       "gym1",
					Name:        stringPtr("Test Problem"),
					Description: stringPtr("A challenging problem"),
					ClimbType:   "BOULDER",
					Difficulty: DifficultyGrade{
						System:       "V",
						Grade:        "V5",
						NumericValue: 5,
					},
					Tags:       []string{"overhang", "crimpy"},
					Location:   stringPtr("Wall A"),
					ImagePaths: []string{"image1.jpg", "image2.jpg"},
					IsActive:   true,
					DateSet:    stringPtr("2024-01-01"),
					Notes:      stringPtr("Watch the start"),
					CreatedAt:  "2024-01-01T10:00:00Z",
					UpdatedAt:  "2024-01-01T10:00:00Z",
				},
			},
			Sessions: []BackupClimbSession{
				{
					ID:        "session1",
					GymID:     "gym1",
					Date:      "2024-01-01",
					StartTime: stringPtr("2024-01-01T10:00:00Z"),
					EndTime:   stringPtr("2024-01-01T12:00:00Z"),
					Duration:  int64Ptr(7200),
					Status:    "completed",
					Notes:     stringPtr("Great session"),
					CreatedAt: "2024-01-01T10:00:00Z",
					UpdatedAt: "2024-01-01T12:00:00Z",
				},
			},
			Attempts: []BackupAttempt{
				{
					ID:          "attempt1",
					SessionID:   "session1",
					ProblemID:   "problem1",
					Result:      "completed",
					HighestHold: stringPtr("Top"),
					Notes:       stringPtr("Clean send"),
					Duration:    int64Ptr(300),
					RestTime:    int64Ptr(120),
					Timestamp:   "2024-01-01T10:30:00Z",
					CreatedAt:   "2024-01-01T10:30:00Z",
				},
			},
		}

		jsonData, err := json.Marshal(originalBackup)
		if err != nil {
			t.Fatalf("Failed to marshal backup: %v", err)
		}

		var unmarshaledBackup ClimbDataBackup
		if err := json.Unmarshal(jsonData, &unmarshaledBackup); err != nil {
			t.Fatalf("Failed to unmarshal backup: %v", err)
		}

		if originalBackup.Version != unmarshaledBackup.Version {
			t.Errorf("Version mismatch: expected %s, got %s", originalBackup.Version, unmarshaledBackup.Version)
		}

		if len(originalBackup.Gyms) != len(unmarshaledBackup.Gyms) {
			t.Errorf("Gyms count mismatch: expected %d, got %d", len(originalBackup.Gyms), len(unmarshaledBackup.Gyms))
		}

		if len(originalBackup.Problems) != len(unmarshaledBackup.Problems) {
			t.Errorf("Problems count mismatch: expected %d, got %d", len(originalBackup.Problems), len(unmarshaledBackup.Problems))
		}

		if len(originalBackup.Sessions) != len(unmarshaledBackup.Sessions) {
			t.Errorf("Sessions count mismatch: expected %d, got %d", len(originalBackup.Sessions), len(unmarshaledBackup.Sessions))
		}

		if len(originalBackup.Attempts) != len(unmarshaledBackup.Attempts) {
			t.Errorf("Attempts count mismatch: expected %d, got %d", len(originalBackup.Attempts), len(unmarshaledBackup.Attempts))
		}
	})

	t.Run("Required Fields Validation", func(t *testing.T) {
		testCases := []struct {
			name        string
			jsonInput   string
			shouldError bool
		}{
			{
				name: "Valid minimal backup",
				jsonInput: `{
					"exportedAt": "2024-01-01T10:00:00Z",
					"version": "2.0",
					"formatVersion": "2.0",
					"gyms": [],
					"problems": [],
					"sessions": [],
					"attempts": []
				}`,
				shouldError: false,
			},
			{
				name: "Missing version field",
				jsonInput: `{
					"exportedAt": "2024-01-01T10:00:00Z",
					"formatVersion": "2.0",
					"gyms": [],
					"problems": [],
					"sessions": [],
					"attempts": []
				}`,
				shouldError: false,
			},
			{
				name: "Invalid JSON structure",
				jsonInput: `{
					"exportedAt": "2024-01-01T10:00:00Z",
					"version": "2.0",
					"formatVersion": "2.0",
					"gyms": "not an array"
				}`,
				shouldError: true,
			},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				var backup ClimbDataBackup
				err := json.Unmarshal([]byte(tc.jsonInput), &backup)

				if tc.shouldError && err == nil {
					t.Error("Expected error but got none")
				}

				if !tc.shouldError && err != nil {
					t.Errorf("Unexpected error: %v", err)
				}
			})
		}
	})

	t.Run("Difficulty Grade Format", func(t *testing.T) {
		testGrades := []DifficultyGrade{
			{System: "V", Grade: "V0", NumericValue: 0},
			{System: "V", Grade: "V5", NumericValue: 5},
			{System: "V", Grade: "V10", NumericValue: 10},
			{System: "YDS", Grade: "5.10a", NumericValue: 100},
			{System: "YDS", Grade: "5.12d", NumericValue: 124},
			{System: "Font", Grade: "6A", NumericValue: 60},
			{System: "Custom", Grade: "Beginner", NumericValue: 1},
		}

		for _, grade := range testGrades {
			jsonData, err := json.Marshal(grade)
			if err != nil {
				t.Errorf("Failed to marshal grade %+v: %v", grade, err)
				continue
			}

			var unmarshaledGrade DifficultyGrade
			if err := json.Unmarshal(jsonData, &unmarshaledGrade); err != nil {
				t.Errorf("Failed to unmarshal grade %s: %v", string(jsonData), err)
				continue
			}

			if grade.System != unmarshaledGrade.System {
				t.Errorf("System mismatch for grade %+v: expected %s, got %s", grade, grade.System, unmarshaledGrade.System)
			}

			if grade.Grade != unmarshaledGrade.Grade {
				t.Errorf("Grade mismatch for grade %+v: expected %s, got %s", grade, grade.Grade, unmarshaledGrade.Grade)
			}

			if grade.NumericValue != unmarshaledGrade.NumericValue {
				t.Errorf("NumericValue mismatch for grade %+v: expected %d, got %d", grade, grade.NumericValue, unmarshaledGrade.NumericValue)
			}
		}
	})

	t.Run("Null and Optional Fields", func(t *testing.T) {
		jsonWithNulls := `{
			"exportedAt": "2024-01-01T10:00:00Z",
			"version": "2.0",
			"formatVersion": "2.0",
			"gyms": [{
				"id": "gym1",
				"name": "Test Gym",
				"location": null,
				"supportedClimbTypes": ["boulder"],
				"difficultySystems": ["V"],
				"customDifficultyGrades": [],
				"notes": null,
				"createdAt": "2024-01-01T10:00:00Z",
				"updatedAt": "2024-01-01T10:00:00Z"
			}],
			"problems": [{
				"id": "problem1",
				"gymId": "gym1",
				"name": null,
				"description": null,
				"climbType": "boulder",
				"difficulty": {
					"system": "V",
					"grade": "V5",
					"numericValue": 5
				},
				"tags": [],
				"location": null,
				"imagePaths": [],
				"isActive": true,
				"dateSet": null,
				"notes": null,
				"createdAt": "2024-01-01T10:00:00Z",
				"updatedAt": "2024-01-01T10:00:00Z"
			}],
			"sessions": [],
			"attempts": []
		}`

		var backup ClimbDataBackup
		if err := json.Unmarshal([]byte(jsonWithNulls), &backup); err != nil {
			t.Fatalf("Failed to unmarshal JSON with nulls: %v", err)
		}

		if backup.Gyms[0].Location != nil {
			t.Error("Expected location to be nil")
		}

		if backup.Gyms[0].Notes != nil {
			t.Error("Expected notes to be nil")
		}

		if backup.Problems[0].Name != nil {
			t.Error("Expected problem name to be nil")
		}
	})

	t.Run("Date Format Validation", func(t *testing.T) {
		validDates := []string{
			"2024-01-01T10:00:00Z",
			"2024-12-31T23:59:59Z",
			"2024-06-15T12:30:45Z",
			"2024-01-01T00:00:00Z",
		}

		invalidDates := []string{
			"2024-01-01 10:00:00",
			"2024/01/01T10:00:00Z",
			"2024-1-1T10:00:00Z",
		}

		for _, date := range validDates {
			if !isValidISODate(date) {
				t.Errorf("Valid date %s was marked as invalid", date)
			}
		}

		for _, date := range invalidDates {
			if isValidISODate(date) {
				t.Errorf("Invalid date %s was marked as valid", date)
			}
		}
	})

	t.Run("Field Length Limits", func(t *testing.T) {
		longString := strings.Repeat("a", 10000)

		gym := BackupGym{
			ID:                     "gym1",
			Name:                   longString,
			Location:               &longString,
			SupportedClimbTypes:    []string{"boulder"},
			DifficultySystems:      []string{"V"},
			CustomDifficultyGrades: []string{},
			Notes:                  &longString,
			CreatedAt:              "2024-01-01T10:00:00Z",
			UpdatedAt:              "2024-01-01T10:00:00Z",
		}

		jsonData, err := json.Marshal(gym)
		if err != nil {
			t.Errorf("Failed to marshal gym with long strings: %v", err)
		}

		var unmarshaledGym BackupGym
		if err := json.Unmarshal(jsonData, &unmarshaledGym); err != nil {
			t.Errorf("Failed to unmarshal gym with long strings: %v", err)
		}

		if unmarshaledGym.Name != longString {
			t.Error("Long name was not preserved")
		}
	})

	t.Run("Array Field Validation", func(t *testing.T) {
		backup := ClimbDataBackup{
			ExportedAt:    "2024-01-01T10:00:00Z",
			Version:       "2.0",
			FormatVersion: "2.0",
			Gyms:          nil,
			Problems:      []BackupProblem{},
			Sessions:      []BackupClimbSession{},
			Attempts:      []BackupAttempt{},
		}

		jsonData, err := json.Marshal(backup)
		if err != nil {
			t.Fatalf("Failed to marshal backup with nil gyms: %v", err)
		}

		var unmarshaledBackup ClimbDataBackup
		if err := json.Unmarshal(jsonData, &unmarshaledBackup); err != nil {
			t.Fatalf("Failed to unmarshal backup with nil gyms: %v", err)
		}

		if len(unmarshaledBackup.Gyms) != 0 {
			t.Error("Expected gyms to be empty or nil")
		}
	})
}

func isValidISODate(date string) bool {
	// More robust ISO date validation
	if !strings.Contains(date, "T") || !strings.HasSuffix(date, "Z") {
		return false
	}

	// Check basic format: YYYY-MM-DDTHH:MM:SSZ
	parts := strings.Split(date, "T")
	if len(parts) != 2 {
		return false
	}

	datePart := parts[0]
	timePart := strings.TrimSuffix(parts[1], "Z")

	// Date part should be YYYY-MM-DD
	dateComponents := strings.Split(datePart, "-")
	if len(dateComponents) != 3 || len(dateComponents[0]) != 4 || len(dateComponents[1]) != 2 || len(dateComponents[2]) != 2 {
		return false
	}

	// Time part should be HH:MM:SS
	timeComponents := strings.Split(timePart, ":")
	if len(timeComponents) != 3 || len(timeComponents[0]) != 2 || len(timeComponents[1]) != 2 || len(timeComponents[2]) != 2 {
		return false
	}

	return true
}

func TestVersionCompatibility(t *testing.T) {
	testCases := []struct {
		version       string
		formatVersion string
		shouldSupport bool
	}{
		{"2.0", "2.0", true},
		{"1.0", "1.0", true},
		{"2.1", "2.0", false},
		{"3.0", "2.0", false},
		{"1.0", "2.0", false},
	}

	for _, tc := range testCases {
		t.Run(tc.version+"/"+tc.formatVersion, func(t *testing.T) {
			backup := ClimbDataBackup{
				Version:       tc.version,
				FormatVersion: tc.formatVersion,
			}

			// Only exact version matches are supported for now
			isSupported := backup.Version == "2.0" && backup.FormatVersion == "2.0"
			if backup.Version == "1.0" && backup.FormatVersion == "1.0" {
				isSupported = true
			}

			if isSupported != tc.shouldSupport {
				t.Errorf("Version %s support expectation mismatch: expected %v, got %v",
					tc.version, tc.shouldSupport, isSupported)
			}
		})
	}
}

func TestClimbTypeValidation(t *testing.T) {
	validClimbTypes := []string{"boulder", "sport", "trad", "toprope", "aid", "ice", "mixed"}
	invalidClimbTypes := []string{"", "invalid", "BOULDER", "Sport", "unknown"}

	for _, climbType := range validClimbTypes {
		if !isValidClimbType(climbType) {
			t.Errorf("Valid climb type %s was marked as invalid", climbType)
		}
	}

	for _, climbType := range invalidClimbTypes {
		if isValidClimbType(climbType) {
			t.Errorf("Invalid climb type %s was marked as valid", climbType)
		}
	}
}

func isValidClimbType(climbType string) bool {
	validTypes := map[string]bool{
		"boulder": true,
		"sport":   true,
		"trad":    true,
		"toprope": true,
		"aid":     true,
		"ice":     true,
		"mixed":   true,
	}
	return validTypes[climbType]
}

func TestAttemptResultValidation(t *testing.T) {
	validResults := []string{"completed", "failed", "flash", "project", "attempt"}
	invalidResults := []string{"", "invalid", "COMPLETED", "Failed", "unknown"}

	for _, result := range validResults {
		if !isValidAttemptResult(result) {
			t.Errorf("Valid attempt result %s was marked as invalid", result)
		}
	}

	for _, result := range invalidResults {
		if isValidAttemptResult(result) {
			t.Errorf("Invalid attempt result %s was marked as valid", result)
		}
	}
}

// Helper functions for creating pointers
func stringPtr(s string) *string {
	return &s
}

func int64Ptr(i int64) *int64 {
	return &i
}

func isValidAttemptResult(result string) bool {
	validResults := map[string]bool{
		"completed": true,
		"failed":    true,
		"flash":     true,
		"project":   true,
		"attempt":   true,
	}
	return validResults[result]
}
