package service

import (
	"context"
	"fmt"
	"time"

	"golang.org/x/text/language"

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

// ValidationError carries per-field validation details.
// It wraps domain.ErrInvalidPreferenceValue for errors.Is() matching.
type ValidationError struct {
	Details map[string]string
}

func (e *ValidationError) Error() string { return "invalid preference values" }
func (e *ValidationError) Unwrap() error { return domain.ErrInvalidPreferenceValue }

// validThemes is the set of allowed values for the "theme" preference.
var validThemes = map[string]bool{
	"light":  true,
	"dark":   true,
	"system": true,
}

// PreferenceService handles preference-related 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"),
	}
}

// Get returns preferences for a user.
// Returns nil when no preferences exist.
func (s *PreferenceService) Get(ctx context.Context, userID string) (*domain.UserPreferences, error) {
	return s.repo.Get(ctx, userID)
}

// UpsertInput contains the data needed to upsert preferences.
type UpsertInput struct {
	UserID      string
	Preferences map[string]any
}

// Upsert validates and stores preferences for a user.
// Known keys are validated; unknown keys are accepted as-is.
// Incoming keys are merged on top of existing preferences.
func (s *PreferenceService) Upsert(ctx context.Context, input UpsertInput) (*domain.UserPreferences, error) {
	// Validate known preference keys
	if err := validatePreferences(input.Preferences); err != nil {
		return nil, err
	}

	// Fetch existing preferences for merge
	existing, err := s.repo.Get(ctx, input.UserID)
	if err != nil {
		return nil, err
	}

	merged := make(map[string]any)
	if existing != nil {
		for k, v := range existing.Preferences {
			merged[k] = v
		}
	}
	for k, v := range input.Preferences {
		merged[k] = v
	}

	now := time.Now().UTC()
	prefs := &domain.UserPreferences{
		UserID:      input.UserID,
		Preferences: merged,
		UpdatedAt:   now,
	}
	if existing != nil {
		prefs.CreatedAt = existing.CreatedAt
	} else {
		prefs.CreatedAt = now
	}

	if err := s.repo.Upsert(ctx, prefs); err != nil {
		return nil, err
	}

	s.logger.Info("preferences upserted", "user_id", input.UserID)
	return prefs, nil
}

// validatePreferences checks known preference keys against their allowed values.
func validatePreferences(prefs map[string]any) error {
	details := make(map[string]string)

	for key, value := range prefs {
		switch key {
		case "theme":
			s, ok := value.(string)
			if !ok || !validThemes[s] {
				details[key] = "must be one of: light, dark, system"
			}
		case "language":
			s, ok := value.(string)
			if !ok {
				details[key] = "must be a valid BCP-47 language tag"
				continue
			}
			if _, err := language.Parse(s); err != nil {
				details[key] = fmt.Sprintf("must be a valid BCP-47 language tag")
			}
		case "notifications_enabled":
			if _, ok := value.(bool); !ok {
				details[key] = "must be a boolean"
			}
		}
	}

	if len(details) > 0 {
		return &ValidationError{Details: details}
	}
	return nil
}
