package service

import (
	"context"
	"errors"
	"testing"

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

// mockPreferencesRepository implements port.PreferencesRepository for testing.
type mockPreferencesRepository struct {
	prefs map[string]*domain.UserPreferences
	err   error // inject error for testing error paths
}

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

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

func (m *mockPreferencesRepository) Get(ctx context.Context, userID string) (*domain.UserPreferences, error) {
	if m.err != nil {
		return nil, m.err
	}
	p, ok := m.prefs[userID]
	if !ok {
		return nil, nil
	}
	cp := *p
	return &cp, nil
}

func (m *mockPreferencesRepository) Upsert(ctx context.Context, userID string, prefs map[string]any) (*domain.UserPreferences, error) {
	if m.err != nil {
		return nil, m.err
	}

	existing, ok := m.prefs[userID]
	if !ok {
		existing = &domain.UserPreferences{
			UserID:      userID,
			Preferences: map[string]any{},
		}
	}

	// Merge preferences
	merged := make(map[string]any)
	for k, v := range existing.Preferences {
		merged[k] = v
	}
	for k, v := range prefs {
		merged[k] = v
	}

	result := &domain.UserPreferences{
		UserID:      userID,
		Preferences: merged,
	}
	m.prefs[userID] = result
	return result, nil
}

func TestPreferencesService_Get(t *testing.T) {
	t.Run("returns empty preferences for new user", func(t *testing.T) {
		repo := newMockPreferencesRepository()
		svc := NewPreferencesService(repo, logging.Nop())

		prefs, err := svc.Get(context.Background(), "user-1")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if prefs.UserID != "user-1" {
			t.Errorf("expected user_id 'user-1', got '%s'", prefs.UserID)
		}
		if len(prefs.Preferences) != 0 {
			t.Errorf("expected empty preferences, got %v", prefs.Preferences)
		}
	})

	t.Run("returns existing preferences", func(t *testing.T) {
		repo := newMockPreferencesRepository()
		repo.prefs["user-1"] = &domain.UserPreferences{
			UserID:      "user-1",
			Preferences: map[string]any{"theme": "dark"},
		}
		svc := NewPreferencesService(repo, logging.Nop())

		prefs, err := svc.Get(context.Background(), "user-1")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if prefs.Preferences["theme"] != "dark" {
			t.Errorf("expected theme 'dark', got '%v'", prefs.Preferences["theme"])
		}
	})

	t.Run("returns error on repository failure", func(t *testing.T) {
		repo := newMockPreferencesRepository()
		repo.err = errors.New("db connection failed")
		svc := NewPreferencesService(repo, logging.Nop())

		_, err := svc.Get(context.Background(), "user-1")
		if err == nil {
			t.Fatal("expected error, got nil")
		}
	})
}

func TestPreferencesService_Update(t *testing.T) {
	t.Run("updates with valid preferences", func(t *testing.T) {
		repo := newMockPreferencesRepository()
		svc := NewPreferencesService(repo, logging.Nop())

		result, err := svc.Update(context.Background(), "user-1", map[string]any{
			"theme": "dark",
		})
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if result.Preferences["theme"] != "dark" {
			t.Errorf("expected theme 'dark', got '%v'", result.Preferences["theme"])
		}
	})

	t.Run("rejects unknown preference key", func(t *testing.T) {
		repo := newMockPreferencesRepository()
		svc := NewPreferencesService(repo, logging.Nop())

		_, err := svc.Update(context.Background(), "user-1", map[string]any{
			"unknown_key": "value",
		})
		if !errors.Is(err, domain.ErrInvalidPreferenceKey) {
			t.Errorf("expected ErrInvalidPreferenceKey, got %v", err)
		}
	})

	t.Run("rejects invalid theme value", func(t *testing.T) {
		repo := newMockPreferencesRepository()
		svc := NewPreferencesService(repo, logging.Nop())

		_, err := svc.Update(context.Background(), "user-1", map[string]any{
			"theme": "blue",
		})
		if !errors.Is(err, domain.ErrInvalidPreferenceValue) {
			t.Errorf("expected ErrInvalidPreferenceValue, got %v", err)
		}
	})

	t.Run("rejects invalid language format", func(t *testing.T) {
		repo := newMockPreferencesRepository()
		svc := NewPreferencesService(repo, logging.Nop())

		_, err := svc.Update(context.Background(), "user-1", map[string]any{
			"language": "english",
		})
		if !errors.Is(err, domain.ErrInvalidPreferenceValue) {
			t.Errorf("expected ErrInvalidPreferenceValue, got %v", err)
		}
	})

	t.Run("rejects non-boolean notifications_enabled", func(t *testing.T) {
		repo := newMockPreferencesRepository()
		svc := NewPreferencesService(repo, logging.Nop())

		_, err := svc.Update(context.Background(), "user-1", map[string]any{
			"notifications_enabled": "yes",
		})
		if !errors.Is(err, domain.ErrInvalidPreferenceValue) {
			t.Errorf("expected ErrInvalidPreferenceValue, got %v", err)
		}
	})

	t.Run("returns error on repository failure", func(t *testing.T) {
		repo := newMockPreferencesRepository()
		repo.err = errors.New("db write failed")
		svc := NewPreferencesService(repo, logging.Nop())

		_, err := svc.Update(context.Background(), "user-1", map[string]any{
			"theme": "dark",
		})
		if err == nil {
			t.Fatal("expected error, got nil")
		}
	})

	t.Run("merges with existing preferences", func(t *testing.T) {
		repo := newMockPreferencesRepository()
		svc := NewPreferencesService(repo, logging.Nop())

		// Set initial preference
		_, err := svc.Update(context.Background(), "user-1", map[string]any{
			"theme": "dark",
		})
		if err != nil {
			t.Fatalf("unexpected error on first update: %v", err)
		}

		// Update with different key
		result, err := svc.Update(context.Background(), "user-1", map[string]any{
			"language": "en",
		})
		if err != nil {
			t.Fatalf("unexpected error on second update: %v", err)
		}

		if result.Preferences["theme"] != "dark" {
			t.Errorf("expected theme 'dark' to be preserved, got '%v'", result.Preferences["theme"])
		}
		if result.Preferences["language"] != "en" {
			t.Errorf("expected language 'en', got '%v'", result.Preferences["language"])
		}
	})
}
