package services

import (
	"regexp"
	"testing"
	"time"

	"github.com/google/uuid"
)

// TestDocumentService_CreateDocument tests creating a new legal document
func TestDocumentService_CreateDocument(t *testing.T) {
	tests := []struct {
		name          string
		docType       string
		docName       string
		description   string
		isMandatory   bool
		expectError   bool
		errorContains string
	}{
		{
			name:        "valid mandatory document",
			docType:     "terms",
			docName:     "Terms of Service",
			description: "Our terms and conditions",
			isMandatory: true,
			expectError: false,
		},
		{
			name:        "valid optional document",
			docType:     "cookies",
			docName:     "Cookie Policy",
			description: "How we use cookies",
			isMandatory: false,
			expectError: false,
		},
		{
			name:          "empty document type",
			docType:       "",
			docName:       "Test Document",
			description:   "Test",
			isMandatory:   true,
			expectError:   true,
			errorContains: "type",
		},
		{
			name:          "empty document name",
			docType:       "privacy",
			docName:       "",
			description:   "Test",
			isMandatory:   true,
			expectError:   true,
			errorContains: "name",
		},
		{
			name:          "invalid document type",
			docType:       "invalid_type",
			docName:       "Test",
			description:   "Test",
			isMandatory:   false,
			expectError:   true,
			errorContains: "type",
		},
	}

	validTypes := map[string]bool{
		"terms":               true,
		"privacy":             true,
		"cookies":             true,
		"community_guidelines": true,
		"imprint":             true,
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Validate inputs
			var err error
			if tt.docType == "" {
				err = &ValidationError{Field: "type", Message: "required"}
			} else if !validTypes[tt.docType] {
				err = &ValidationError{Field: "type", Message: "invalid document type"}
			} else if tt.docName == "" {
				err = &ValidationError{Field: "name", Message: "required"}
			}

			// Assert
			if tt.expectError {
				if err == nil {
					t.Errorf("Expected error containing '%s', got nil", tt.errorContains)
				}
			} else {
				if err != nil {
					t.Errorf("Expected no error, got %v", err)
				}
			}
		})
	}
}

// TestDocumentService_UpdateDocument tests updating a document
func TestDocumentService_UpdateDocument(t *testing.T) {
	tests := []struct {
		name        string
		documentID  uuid.UUID
		newName     string
		newActive   bool
		expectError bool
	}{
		{
			name:        "valid update",
			documentID:  uuid.New(),
			newName:     "Updated Name",
			newActive:   true,
			expectError: false,
		},
		{
			name:        "deactivate document",
			documentID:  uuid.New(),
			newName:     "Test",
			newActive:   false,
			expectError: false,
		},
		{
			name:        "invalid document ID",
			documentID:  uuid.Nil,
			newName:     "Test",
			newActive:   true,
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var err error
			if tt.documentID == uuid.Nil {
				err = &ValidationError{Field: "document ID", Message: "required"}
			}

			if tt.expectError && err == nil {
				t.Error("Expected error, got nil")
			}
			if !tt.expectError && err != nil {
				t.Errorf("Expected no error, got %v", err)
			}
		})
	}
}

// TestDocumentService_CreateVersion tests creating a document version
func TestDocumentService_CreateVersion(t *testing.T) {
	tests := []struct {
		name          string
		documentID    uuid.UUID
		version       string
		language      string
		title         string
		content       string
		expectError   bool
		errorContains string
	}{
		{
			name:        "valid version - German",
			documentID:  uuid.New(),
			version:     "1.0.0",
			language:    "de",
			title:       "Nutzungsbedingungen",
			content:     "<h1>Terms</h1><p>Content...</p>",
			expectError: false,
		},
		{
			name:        "valid version - English",
			documentID:  uuid.New(),
			version:     "1.0.0",
			language:    "en",
			title:       "Terms of Service",
			content:     "<h1>Terms</h1><p>Content...</p>",
			expectError: false,
		},
		{
			name:          "invalid version format",
			documentID:    uuid.New(),
			version:       "1.0",
			language:      "de",
			title:         "Test",
			content:       "Content",
			expectError:   true,
			errorContains: "version",
		},
		{
			name:          "invalid language",
			documentID:    uuid.New(),
			version:       "1.0.0",
			language:      "fr",
			title:         "Test",
			content:       "Content",
			expectError:   true,
			errorContains: "language",
		},
		{
			name:          "empty title",
			documentID:    uuid.New(),
			version:       "1.0.0",
			language:      "de",
			title:         "",
			content:       "Content",
			expectError:   true,
			errorContains: "title",
		},
		{
			name:          "empty content",
			documentID:    uuid.New(),
			version:       "1.0.0",
			language:      "de",
			title:         "Test",
			content:       "",
			expectError:   true,
			errorContains: "content",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Validate semver format (X.Y.Z pattern)
			validVersion := regexp.MustCompile(`^\d+\.\d+\.\d+$`).MatchString(tt.version)
			validLanguage := tt.language == "de" || tt.language == "en"

			var err error
			if !validVersion {
				err = &ValidationError{Field: "version", Message: "invalid format"}
			} else if !validLanguage {
				err = &ValidationError{Field: "language", Message: "must be 'de' or 'en'"}
			} else if tt.title == "" {
				err = &ValidationError{Field: "title", Message: "required"}
			} else if tt.content == "" {
				err = &ValidationError{Field: "content", Message: "required"}
			}

			if tt.expectError {
				if err == nil {
					t.Errorf("Expected error containing '%s', got nil", tt.errorContains)
				}
			} else {
				if err != nil {
					t.Errorf("Expected no error, got %v", err)
				}
			}
		})
	}
}

// TestDocumentService_VersionStatusTransitions tests version status workflow
func TestDocumentService_VersionStatusTransitions(t *testing.T) {
	tests := []struct {
		name        string
		fromStatus  string
		toStatus    string
		isAllowed   bool
	}{
		// Valid transitions
		{"draft to review", "draft", "review", true},
		{"review to approved", "review", "approved", true},
		{"review to rejected", "review", "rejected", true},
		{"approved to published", "approved", "published", true},
		{"approved to scheduled", "approved", "scheduled", true},
		{"scheduled to published", "scheduled", "published", true},
		{"published to archived", "published", "archived", true},
		{"rejected to draft", "rejected", "draft", true},

		// Invalid transitions
		{"draft to published", "draft", "published", false},
		{"draft to approved", "draft", "approved", false},
		{"review to published", "review", "published", false},
		{"published to draft", "published", "draft", false},
		{"published to review", "published", "review", false},
		{"archived to draft", "archived", "draft", false},
		{"archived to published", "archived", "published", false},
	}

	// Define valid transitions
	validTransitions := map[string][]string{
		"draft":     {"review"},
		"review":    {"approved", "rejected"},
		"approved":  {"published", "scheduled"},
		"scheduled": {"published"},
		"published": {"archived"},
		"rejected":  {"draft"},
		"archived":  {}, // terminal state
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Check if transition is allowed
			allowed := false
			if transitions, ok := validTransitions[tt.fromStatus]; ok {
				for _, validTo := range transitions {
					if validTo == tt.toStatus {
						allowed = true
						break
					}
				}
			}

			if allowed != tt.isAllowed {
				t.Errorf("Transition %s->%s: expected allowed=%v, got %v",
					tt.fromStatus, tt.toStatus, tt.isAllowed, allowed)
			}
		})
	}
}

// TestDocumentService_PublishVersion tests publishing a version
func TestDocumentService_PublishVersion(t *testing.T) {
	tests := []struct {
		name          string
		versionID     uuid.UUID
		currentStatus string
		expectError   bool
		errorContains string
	}{
		{
			name:          "publish approved version",
			versionID:     uuid.New(),
			currentStatus: "approved",
			expectError:   false,
		},
		{
			name:          "publish scheduled version",
			versionID:     uuid.New(),
			currentStatus: "scheduled",
			expectError:   false,
		},
		{
			name:          "cannot publish draft",
			versionID:     uuid.New(),
			currentStatus: "draft",
			expectError:   true,
			errorContains: "draft",
		},
		{
			name:          "cannot publish review",
			versionID:     uuid.New(),
			currentStatus: "review",
			expectError:   true,
			errorContains: "review",
		},
		{
			name:          "invalid version ID",
			versionID:     uuid.Nil,
			currentStatus: "approved",
			expectError:   true,
			errorContains: "ID",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var err error
			if tt.versionID == uuid.Nil {
				err = &ValidationError{Field: "version ID", Message: "required"}
			} else if tt.currentStatus != "approved" && tt.currentStatus != "scheduled" {
				err = &ValidationError{Field: "status", Message: "only approved or scheduled versions can be published"}
			}

			if tt.expectError {
				if err == nil {
					t.Errorf("Expected error containing '%s', got nil", tt.errorContains)
				}
			} else {
				if err != nil {
					t.Errorf("Expected no error, got %v", err)
				}
			}
		})
	}
}

// TestDocumentService_ArchiveVersion tests archiving a version
func TestDocumentService_ArchiveVersion(t *testing.T) {
	tests := []struct {
		name        string
		versionID   uuid.UUID
		expectError bool
	}{
		{
			name:        "archive valid version",
			versionID:   uuid.New(),
			expectError: false,
		},
		{
			name:        "invalid version ID",
			versionID:   uuid.Nil,
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var err error
			if tt.versionID == uuid.Nil {
				err = &ValidationError{Field: "version ID", Message: "required"}
			}

			if tt.expectError && err == nil {
				t.Error("Expected error, got nil")
			}
			if !tt.expectError && err != nil {
				t.Errorf("Expected no error, got %v", err)
			}
		})
	}
}

// TestDocumentService_DeleteVersion tests deleting a version
func TestDocumentService_DeleteVersion(t *testing.T) {
	tests := []struct {
		name          string
		versionID     uuid.UUID
		status        string
		canDelete     bool
		expectError   bool
	}{
		{
			name:        "delete draft version",
			versionID:   uuid.New(),
			status:      "draft",
			canDelete:   true,
			expectError: false,
		},
		{
			name:        "delete rejected version",
			versionID:   uuid.New(),
			status:      "rejected",
			canDelete:   true,
			expectError: false,
		},
		{
			name:        "cannot delete published version",
			versionID:   uuid.New(),
			status:      "published",
			canDelete:   false,
			expectError: true,
		},
		{
			name:        "cannot delete approved version",
			versionID:   uuid.New(),
			status:      "approved",
			canDelete:   false,
			expectError: true,
		},
		{
			name:        "cannot delete archived version",
			versionID:   uuid.New(),
			status:      "archived",
			canDelete:   false,
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Only draft and rejected can be deleted
			canDelete := tt.status == "draft" || tt.status == "rejected"

			var err error
			if !canDelete {
				err = &ValidationError{Field: "status", Message: "only draft or rejected versions can be deleted"}
			}

			if tt.expectError {
				if err == nil {
					t.Error("Expected error, got nil")
				}
			} else {
				if err != nil {
					t.Errorf("Expected no error, got %v", err)
				}
			}

			if canDelete != tt.canDelete {
				t.Errorf("Expected canDelete=%v, got %v", tt.canDelete, canDelete)
			}
		})
	}
}

// TestDocumentService_GetLatestVersion tests retrieving the latest version
func TestDocumentService_GetLatestVersion(t *testing.T) {
	tests := []struct {
		name         string
		documentID   uuid.UUID
		language     string
		status       string
		expectError  bool
	}{
		{
			name:        "get latest German version",
			documentID:  uuid.New(),
			language:    "de",
			status:      "published",
			expectError: false,
		},
		{
			name:        "get latest English version",
			documentID:  uuid.New(),
			language:    "en",
			status:      "published",
			expectError: false,
		},
		{
			name:        "invalid document ID",
			documentID:  uuid.Nil,
			language:    "de",
			status:      "published",
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var err error
			if tt.documentID == uuid.Nil {
				err = &ValidationError{Field: "document ID", Message: "required"}
			}

			if tt.expectError && err == nil {
				t.Error("Expected error, got nil")
			}
			if !tt.expectError && err != nil {
				t.Errorf("Expected no error, got %v", err)
			}
		})
	}
}

// TestDocumentService_CompareVersions tests version comparison
func TestDocumentService_CompareVersions(t *testing.T) {
	tests := []struct {
		name       string
		version1   string
		version2   string
		isDifferent bool
	}{
		{
			name:       "same version",
			version1:   "1.0.0",
			version2:   "1.0.0",
			isDifferent: false,
		},
		{
			name:       "different major version",
			version1:   "2.0.0",
			version2:   "1.0.0",
			isDifferent: true,
		},
		{
			name:       "different minor version",
			version1:   "1.1.0",
			version2:   "1.0.0",
			isDifferent: true,
		},
		{
			name:       "different patch version",
			version1:   "1.0.1",
			version2:   "1.0.0",
			isDifferent: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			isDifferent := tt.version1 != tt.version2

			if isDifferent != tt.isDifferent {
				t.Errorf("Expected isDifferent=%v, got %v", tt.isDifferent, isDifferent)
			}
		})
	}
}

// TestDocumentService_ScheduledPublishing tests scheduled publishing
func TestDocumentService_ScheduledPublishing(t *testing.T) {
	now := time.Now()

	tests := []struct {
		name             string
		scheduledAt      time.Time
		shouldPublish    bool
	}{
		{
			name:          "scheduled for past - should publish",
			scheduledAt:   now.Add(-1 * time.Hour),
			shouldPublish: true,
		},
		{
			name:          "scheduled for now - should publish",
			scheduledAt:   now,
			shouldPublish: true,
		},
		{
			name:          "scheduled for future - should not publish",
			scheduledAt:   now.Add(1 * time.Hour),
			shouldPublish: false,
		},
		{
			name:          "scheduled for tomorrow - should not publish",
			scheduledAt:   now.AddDate(0, 0, 1),
			shouldPublish: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			shouldPublish := tt.scheduledAt.Before(now) || tt.scheduledAt.Equal(now)

			if shouldPublish != tt.shouldPublish {
				t.Errorf("Expected shouldPublish=%v, got %v", tt.shouldPublish, shouldPublish)
			}
		})
	}
}

// TestDocumentService_ApprovalWorkflow tests the approval workflow
func TestDocumentService_ApprovalWorkflow(t *testing.T) {
	tests := []struct {
		name        string
		action      string
		userRole    string
		isAllowed   bool
	}{
		// Admin permissions
		{"admin submit for review", "submit_review", "admin", true},
		{"admin cannot approve", "approve", "admin", false},
		{"admin can publish", "publish", "admin", true},

		// DSB permissions
		{"dsb can approve", "approve", "data_protection_officer", true},
		{"dsb can reject", "reject", "data_protection_officer", true},
		{"dsb can publish", "publish", "data_protection_officer", true},

		// User permissions
		{"user cannot submit", "submit_review", "user", false},
		{"user cannot approve", "approve", "user", false},
		{"user cannot publish", "publish", "user", false},
	}

	permissions := map[string]map[string]bool{
		"admin": {
			"submit_review": true,
			"approve":       false,
			"reject":        false,
			"publish":       true,
		},
		"data_protection_officer": {
			"submit_review": true,
			"approve":       true,
			"reject":        true,
			"publish":       true,
		},
		"user": {
			"submit_review": false,
			"approve":       false,
			"reject":        false,
			"publish":       false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			rolePerms, ok := permissions[tt.userRole]
			if !ok {
				t.Fatalf("Unknown role: %s", tt.userRole)
			}

			isAllowed := rolePerms[tt.action]

			if isAllowed != tt.isAllowed {
				t.Errorf("Role %s action %s: expected allowed=%v, got %v",
					tt.userRole, tt.action, tt.isAllowed, isAllowed)
			}
		})
	}
}

// TestDocumentService_FourEyesPrinciple tests the four-eyes principle
func TestDocumentService_FourEyesPrinciple(t *testing.T) {
	tests := []struct {
		name        string
		createdBy   uuid.UUID
		approver    uuid.UUID
		approverRole string
		canApprove  bool
	}{
		{
			name:        "different users - DSB can approve",
			createdBy:   uuid.New(),
			approver:    uuid.New(),
			approverRole: "data_protection_officer",
			canApprove:  true,
		},
		{
			name:        "same user - DSB cannot approve own",
			createdBy:   uuid.MustParse("123e4567-e89b-12d3-a456-426614174000"),
			approver:    uuid.MustParse("123e4567-e89b-12d3-a456-426614174000"),
			approverRole: "data_protection_officer",
			canApprove:  false,
		},
		{
			name:        "same user - admin CAN approve own (exception)",
			createdBy:   uuid.MustParse("123e4567-e89b-12d3-a456-426614174000"),
			approver:    uuid.MustParse("123e4567-e89b-12d3-a456-426614174000"),
			approverRole: "admin",
			canApprove:  true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Four-eyes principle: DSB cannot approve their own work
			// Exception: Admins can (for development/testing)
			canApprove := tt.createdBy != tt.approver || tt.approverRole == "admin"

			if canApprove != tt.canApprove {
				t.Errorf("Expected canApprove=%v, got %v", tt.canApprove, canApprove)
			}
		})
	}
}
