package service

import (
	"context"
	"fmt"

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

// PreferencesResult is the result of a preference operation.
type PreferencesResult struct {
	UserID      string
	Preferences map[string]any
}

// PreferenceService handles preference business logic.
type PreferenceService struct {
	repo   port.PreferenceRepository
	logger *logging.Logger
}

// NewPreferenceService creates a new preference service.
func NewPreferenceService(repo port.PreferenceRepository, logger *logging.Logger) *PreferenceService {
	return &PreferenceService{
		repo:   repo,
		logger: logger.WithService("PreferenceService"),
	}
}

// GetPreferences returns all preferences for a user, merged with defaults.
func (s *PreferenceService) GetPreferences(ctx context.Context, userID string) (*PreferencesResult, error) {
	if err := domain.ValidateUserID(userID); err != nil {
		return nil, err
	}

	rows, err := s.repo.GetByUserID(ctx, userID)
	if err != nil {
		return nil, err
	}

	stored := make(map[domain.PreferenceKey]string, len(rows))
	for _, row := range rows {
		stored[domain.PreferenceKey(row.Key)] = row.Value
	}

	merged := domain.MergeWithDefaults(stored)
	serialized := domain.SerializeForResponse(merged)

	return &PreferencesResult{
		UserID:      userID,
		Preferences: serialized,
	}, nil
}

// UpdatePreferences validates and upserts the provided preferences, then returns the full merged result.
func (s *PreferenceService) UpdatePreferences(ctx context.Context, userID string, input map[string]any) (*PreferencesResult, error) {
	if err := domain.ValidateUserID(userID); err != nil {
		return nil, err
	}

	// Validate and serialize each input key-value pair
	toUpsert := make(map[domain.PreferenceKey]string, len(input))
	for key, val := range input {
		if err := domain.ValidateKey(key); err != nil {
			return nil, err
		}

		// Serialize value to string
		strVal, err := serializeValue(val)
		if err != nil {
			return nil, fmt.Errorf("%w: %s must have a valid value", domain.ErrInvalidPreferenceValue, key)
		}

		pk := domain.PreferenceKey(key)
		if err := domain.ValidateValue(pk, strVal); err != nil {
			return nil, err
		}

		toUpsert[pk] = strVal
	}

	// Upsert each preference
	for key, value := range toUpsert {
		if err := s.repo.Upsert(ctx, userID, string(key), value); err != nil {
			return nil, err
		}
	}

	s.logger.Info("preferences updated", "user_id", userID, "keys_updated", len(toUpsert))

	// Fetch and return full merged preferences
	return s.GetPreferences(ctx, userID)
}

// serializeValue converts an any value to a string representation.
func serializeValue(val any) (string, error) {
	switch v := val.(type) {
	case string:
		return v, nil
	case bool:
		if v {
			return "true", nil
		}
		return "false", nil
	case float64:
		// JSON numbers are decoded as float64
		if v == 1 {
			return "true", nil
		}
		if v == 0 {
			return "false", nil
		}
		return "", fmt.Errorf("unsupported numeric value")
	default:
		return "", fmt.Errorf("unsupported value type %T", val)
	}
}
