package service

import (
	"context"
	"sync"
	"testing"

	"git.threesix.ai/jordan/slack5-1770603014/pkg/logging"
	"git.threesix.ai/jordan/slack5-1770603014/services/preferences-api/internal/domain"
	"git.threesix.ai/jordan/slack5-1770603014/services/preferences-api/internal/port"
)

// mockPreferencesRepository implements port.PreferencesRepository for testing.
type mockPreferencesRepository struct {
	mu    sync.RWMutex
	store map[domain.UserID]*domain.UserPreferences
}

var _ port.PreferencesRepository = (*mockPreferencesRepository)(nil)

func newMockPreferencesRepository() *mockPreferencesRepository {
	return &mockPreferencesRepository{
		store: make(map[domain.UserID]*domain.UserPreferences),
	}
}

func (m *mockPreferencesRepository) Get(ctx context.Context, userID domain.UserID) (*domain.UserPreferences, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	p, ok := m.store[userID]
	if !ok {
		return nil, nil
	}
	cp := *p
	return &cp, nil
}

func (m *mockPreferencesRepository) Upsert(ctx context.Context, prefs *domain.UserPreferences) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	cp := *prefs
	m.store[prefs.UserID] = &cp
	return nil
}

func strPtr(s string) *string { return &s }
func boolPtr(b bool) *bool    { return &b }

func TestPreferencesService_GetPreferences(t *testing.T) {
	repo := newMockPreferencesRepository()
	svc := NewPreferencesService(repo, logging.Nop())

	t.Run("returns defaults for unknown user", func(t *testing.T) {
		prefs, err := svc.GetPreferences(context.Background(), "unknown-user")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if prefs.Preferences.Theme != "system" {
			t.Errorf("expected theme 'system', got '%s'", prefs.Preferences.Theme)
		}
		if prefs.Preferences.Language != "en" {
			t.Errorf("expected language 'en', got '%s'", prefs.Preferences.Language)
		}
		if prefs.Preferences.Notifications.Email != true {
			t.Error("expected notifications.email true")
		}
		if prefs.Preferences.Notifications.Push != true {
			t.Error("expected notifications.push true")
		}
		if prefs.Preferences.Notifications.Digest != "weekly" {
			t.Errorf("expected notifications.digest 'weekly', got '%s'", prefs.Preferences.Notifications.Digest)
		}
	})

	t.Run("returns stored preferences for existing user", func(t *testing.T) {
		// Seed data
		repo.mu.Lock()
		repo.store["user-1"] = &domain.UserPreferences{
			UserID: "user-1",
			Preferences: domain.Preferences{
				Theme:    "dark",
				Language: "fr",
				Notifications: domain.NotificationSettings{
					Email:  false,
					Push:   true,
					Digest: "daily",
				},
			},
		}
		repo.mu.Unlock()

		prefs, err := svc.GetPreferences(context.Background(), "user-1")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if prefs.Preferences.Theme != "dark" {
			t.Errorf("expected theme 'dark', got '%s'", prefs.Preferences.Theme)
		}
		if prefs.Preferences.Language != "fr" {
			t.Errorf("expected language 'fr', got '%s'", prefs.Preferences.Language)
		}
	})
}

func TestPreferencesService_UpdatePreferences(t *testing.T) {
	repo := newMockPreferencesRepository()
	svc := NewPreferencesService(repo, logging.Nop())

	t.Run("creates new preferences from defaults", func(t *testing.T) {
		prefs, err := svc.UpdatePreferences(context.Background(), "new-user", UpdateInput{
			Theme: strPtr("dark"),
		})
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if prefs.Preferences.Theme != "dark" {
			t.Errorf("expected theme 'dark', got '%s'", prefs.Preferences.Theme)
		}
		// Other fields should be defaults
		if prefs.Preferences.Language != "en" {
			t.Errorf("expected language 'en', got '%s'", prefs.Preferences.Language)
		}
		if prefs.Preferences.Notifications.Digest != "weekly" {
			t.Errorf("expected digest 'weekly', got '%s'", prefs.Preferences.Notifications.Digest)
		}
	})

	t.Run("merges partial data with existing preferences", func(t *testing.T) {
		// First set full preferences
		_, _ = svc.UpdatePreferences(context.Background(), "merge-user", UpdateInput{
			Theme:    strPtr("light"),
			Language: strPtr("es"),
			Notifications: &NotificationsInput{
				Email:  boolPtr(false),
				Push:   boolPtr(false),
				Digest: strPtr("daily"),
			},
		})

		// Now partial update - only change push notification
		prefs, err := svc.UpdatePreferences(context.Background(), "merge-user", UpdateInput{
			Notifications: &NotificationsInput{
				Push: boolPtr(true),
			},
		})
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		// Push should be updated
		if prefs.Preferences.Notifications.Push != true {
			t.Error("expected notifications.push true")
		}
		// Other fields should be unchanged
		if prefs.Preferences.Theme != "light" {
			t.Errorf("expected theme 'light', got '%s'", prefs.Preferences.Theme)
		}
		if prefs.Preferences.Language != "es" {
			t.Errorf("expected language 'es', got '%s'", prefs.Preferences.Language)
		}
		if prefs.Preferences.Notifications.Email != false {
			t.Error("expected notifications.email false")
		}
		if prefs.Preferences.Notifications.Digest != "daily" {
			t.Errorf("expected digest 'daily', got '%s'", prefs.Preferences.Notifications.Digest)
		}
	})

	t.Run("updates theme only", func(t *testing.T) {
		prefs, err := svc.UpdatePreferences(context.Background(), "theme-user", UpdateInput{
			Theme: strPtr("light"),
		})
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if prefs.Preferences.Theme != "light" {
			t.Errorf("expected theme 'light', got '%s'", prefs.Preferences.Theme)
		}
	})

	t.Run("updates language only", func(t *testing.T) {
		prefs, err := svc.UpdatePreferences(context.Background(), "lang-user", UpdateInput{
			Language: strPtr("fr"),
		})
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if prefs.Preferences.Language != "fr" {
			t.Errorf("expected language 'fr', got '%s'", prefs.Preferences.Language)
		}
	})

	t.Run("updates all fields together", func(t *testing.T) {
		prefs, err := svc.UpdatePreferences(context.Background(), "all-user", UpdateInput{
			Theme:    strPtr("dark"),
			Language: strPtr("de"),
			Notifications: &NotificationsInput{
				Email:  boolPtr(false),
				Push:   boolPtr(true),
				Digest: strPtr("never"),
			},
		})
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if prefs.Preferences.Theme != "dark" {
			t.Errorf("expected theme 'dark', got '%s'", prefs.Preferences.Theme)
		}
		if prefs.Preferences.Language != "de" {
			t.Errorf("expected language 'de', got '%s'", prefs.Preferences.Language)
		}
		if prefs.Preferences.Notifications.Email != false {
			t.Error("expected notifications.email false")
		}
		if prefs.Preferences.Notifications.Push != true {
			t.Error("expected notifications.push true")
		}
		if prefs.Preferences.Notifications.Digest != "never" {
			t.Errorf("expected digest 'never', got '%s'", prefs.Preferences.Notifications.Digest)
		}
	})

	t.Run("rejects invalid theme", func(t *testing.T) {
		_, err := svc.UpdatePreferences(context.Background(), "invalid-theme-user", UpdateInput{
			Theme: strPtr("purple"),
		})
		if err != domain.ErrInvalidTheme {
			t.Errorf("expected ErrInvalidTheme, got %v", err)
		}
	})

	t.Run("rejects invalid digest", func(t *testing.T) {
		_, err := svc.UpdatePreferences(context.Background(), "invalid-digest-user", UpdateInput{
			Notifications: &NotificationsInput{
				Digest: strPtr("monthly"),
			},
		})
		if err != domain.ErrInvalidDigest {
			t.Errorf("expected ErrInvalidDigest, got %v", err)
		}
	})

	t.Run("rejects empty language", func(t *testing.T) {
		_, err := svc.UpdatePreferences(context.Background(), "empty-lang-user", UpdateInput{
			Language: strPtr(""),
		})
		if err != domain.ErrInvalidLanguage {
			t.Errorf("expected ErrInvalidLanguage, got %v", err)
		}
	})
}
