package settings

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

	"gorm.io/gorm"

	"security-center-backend/internal/model"
	"security-center-backend/internal/pkg/cache"
	"security-center-backend/internal/repository"
	"security-center-backend/internal/service/audit"
)

const (
	cacheKeySettings = "system:config:snapshot"
	cacheTTL         = 2 * time.Minute
)

var (
	ErrVersionConflict       = errors.New("settings version mismatch")
	ErrRepositoryUnavailable = errors.New("settings repository unavailable")
	ErrChecksumMismatch      = errors.New("settings checksum mismatch")
	ErrInvalidSettings       = errors.New("invalid settings")
)

// OrgSyncReconfigureFunc is a callback function for reconfiguring org sync when settings change.
type OrgSyncReconfigureFunc func(settings DataSyncSettings) error

// AlertReconfigureFunc is a callback function for reconfiguring alert runtime when notification settings change.
type AlertReconfigureFunc func(settings NotificationSettings) error

type Service struct {
	repo                repository.SystemSettingsRepository
	audit               *audit.Service
	cache               *cache.Cache
	orgSyncReconfigurer OrgSyncReconfigureFunc
	alertReconfigurer   AlertReconfigureFunc
}

func New(repo repository.SystemSettingsRepository, auditLogger *audit.Service, cacheLayer *cache.Cache) *Service {
	return &Service{
		repo:  repo,
		audit: auditLogger,
		cache: cacheLayer,
	}
}

// SetOrgSyncReconfigurer sets the callback function for org sync reconfiguration.
func (s *Service) SetOrgSyncReconfigurer(fn OrgSyncReconfigureFunc) {
	s.orgSyncReconfigurer = fn
}

// SetAlertReconfigurer sets the callback function for alert runtime reconfiguration.
func (s *Service) SetAlertReconfigurer(fn AlertReconfigureFunc) {
	s.alertReconfigurer = fn
}

func (s *Service) Get(ctx context.Context) (*Settings, error) {
	if s.repo == nil {
		snapshot := defaultSettingsSnapshot()
		return &snapshot, nil
	}
	if cached := s.loadFromCache(ctx); cached != nil {
		return cached, nil
	}
	entity, err := s.repo.Get(ctx)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			entity, err = s.repo.Create(ctx, defaultSettingsModel())
			if err != nil {
				return nil, err
			}
		} else {
			return nil, err
		}
	}
	snapshot := entityToSettings(entity)
	s.saveToCache(ctx, snapshot)
	return snapshot, nil
}

// GetPublic returns a settings snapshot safe for UI consumption (sensitive values are masked).
func (s *Service) GetPublic(ctx context.Context) (*Settings, error) {
	raw, err := s.Get(ctx)
	if err != nil {
		return nil, err
	}
	return MaskForAPI(raw), nil
}

func MaskForAPI(snapshot *Settings) *Settings {
	if snapshot == nil {
		return snapshot
	}
	clone := *snapshot

	clone.Authentication = snapshot.Authentication
	clone.Notifications = snapshot.Notifications
	clone.DataSync = snapshot.DataSync
	clone.Notifications.LarkReceiveIDType = normalizeLarkReceiveIDType(snapshot.Notifications.LarkReceiveIDType)

	clone.Authentication.Auth0ClientSecretConfigured = snapshot.Authentication.Auth0ClientSecret != ""
	clone.Notifications.LarkWebhookConfigured = snapshot.Notifications.LarkWebhook != ""
	clone.Notifications.LarkAppSecretConfigured = snapshot.Notifications.LarkAppSecret != ""
	clone.Notifications.IncidentWebhookConfigured = snapshot.Notifications.IncidentWebhook != ""
	clone.DataSync.LarkAppSecretConfigured = snapshot.DataSync.LarkAppSecret != ""

	clone.Authentication.Auth0ClientSecret = ""
	clone.Notifications.LarkWebhook = ""
	clone.Notifications.LarkAppSecret = ""
	clone.Notifications.IncidentWebhook = ""
	clone.DataSync.LarkAppSecret = ""

	return &clone
}

func (s *Service) Update(ctx context.Context, input UpdateInput) (*Settings, error) {
	if s.repo == nil {
		return nil, ErrRepositoryUnavailable
	}
	current, err := s.repo.Get(ctx)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			current, err = s.repo.Create(ctx, defaultSettingsModel())
			if err != nil {
				return nil, err
			}
		} else {
			return nil, err
		}
	}

	before := entityToSettings(current)
	if input.ExpectedChecksum != "" && input.ExpectedChecksum != before.Checksum {
		return nil, ErrChecksumMismatch
	}

	// Preserve existing sensitive values when the incoming payload leaves them blank.
	// This prevents accidental secret resets when UIs mask hidden secrets.
	if input.Authentication.Auth0ClientSecret == "" && before.Authentication.Auth0ClientSecret != "" {
		input.Authentication.Auth0ClientSecret = before.Authentication.Auth0ClientSecret
	}
	if input.Notifications.LarkWebhook == "" && before.Notifications.LarkWebhook != "" {
		input.Notifications.LarkWebhook = before.Notifications.LarkWebhook
	}
	if input.Notifications.LarkAppSecret == "" && before.Notifications.LarkAppSecret != "" {
		input.Notifications.LarkAppSecret = before.Notifications.LarkAppSecret
	}
	if input.Notifications.IncidentWebhook == "" && before.Notifications.IncidentWebhook != "" {
		input.Notifications.IncidentWebhook = before.Notifications.IncidentWebhook
	}
	if input.DataSync.LarkAppSecret == "" && before.DataSync.LarkAppSecret != "" {
		input.DataSync.LarkAppSecret = before.DataSync.LarkAppSecret
	}
	normalizeNotificationSettings(&input.Notifications)
	if err := validateDataSyncSettings(input.DataSync); err != nil {
		return nil, err
	}

	// Ensure derived/configured flags are never persisted.
	input.Authentication.Auth0ClientSecretConfigured = false
	input.Notifications.LarkWebhookConfigured = false
	input.Notifications.LarkAppSecretConfigured = false
	input.Notifications.IncidentWebhookConfigured = false
	input.DataSync.LarkAppSecretConfigured = false

	current.Platform = marshalSection(input.Platform)
	current.Authentication = marshalSection(input.Authentication)
	current.Notifications = marshalSection(input.Notifications)
	current.DataSync = marshalSection(input.DataSync)
	current.UpdatedBy = input.ActorID

	updated, err := s.repo.Update(ctx, current, input.Version)
	if err != nil {
		if errors.Is(err, repository.ErrSettingsVersionConflict) {
			return nil, ErrVersionConflict
		}
		return nil, err
	}

	result := entityToSettings(updated)
	s.logAudit(ctx, before, result, input, updated.ID)
	s.evictCache(ctx)

	// Trigger org sync reconfiguration if settings changed
	if s.orgSyncReconfigurer != nil {
		if orgSyncChanged(before.DataSync, result.DataSync) {
			err := s.orgSyncReconfigurer(result.DataSync)
			if err != nil {
				// Log error but don't fail the settings update
				// This allows settings to be saved even if runtime reconfiguration fails
				// Note: Using fmt.Printf since we don't have structured logger in this service
				fmt.Printf("failed to reconfigure org sync runtime: %v\n", err)
			}
		}
	}

	if s.alertReconfigurer != nil {
		if notificationsChanged(before.Notifications, result.Notifications) {
			err := s.alertReconfigurer(result.Notifications)
			if err != nil {
				fmt.Printf("failed to reconfigure alert runtime: %v\n", err)
			}
		}
	}

	return result, nil
}

func (s *Service) logAudit(ctx context.Context, before *Settings, after *Settings, input UpdateInput, resourceID uint64) {
	if s.audit == nil {
		return
	}
	metadata := model.JSONB{}
	if input.Note != "" {
		metadata["note"] = input.Note
	}
	s.audit.Log(ctx, audit.Entry{
		UserID:       input.ActorID,
		Action:       "system.settings.update",
		ResourceType: "system_settings",
		ResourceID:   fmt.Sprintf("%d", resourceID),
		Metadata:     metadata,
		BeforeState:  settingsToJSONB(MaskForAPI(before)),
		AfterState:   settingsToJSONB(MaskForAPI(after)),
	})
}

func marshalSection[T any](payload T) model.JSONB {
	data, err := json.Marshal(payload)
	if err != nil {
		return model.JSONB{}
	}
	var out model.JSONB
	if err := json.Unmarshal(data, &out); err != nil {
		return model.JSONB{}
	}
	return out
}

func decodeSection[T any](data model.JSONB, fallback T) T {
	result := fallback
	if data == nil {
		return result
	}
	raw, err := json.Marshal(data)
	if err != nil {
		return result
	}
	if err := json.Unmarshal(raw, &result); err != nil {
		return fallback
	}
	return result
}

func entityToSettings(entity *model.SystemSettings) *Settings {
	if entity == nil {
		snapshot := defaultSettingsSnapshot()
		snapshot.Checksum = computeSettingsChecksum(&snapshot)
		return &snapshot
	}
	snapshot := &Settings{
		Platform:       decodeSection(entity.Platform, defaultPlatformSettings()),
		Authentication: decodeSection(entity.Authentication, defaultAuthenticationSettings()),
		Notifications:  decodeSection(entity.Notifications, defaultNotificationSettings()),
		DataSync:       decodeSection(entity.DataSync, defaultDataSyncSettings()),
		Version:        entity.Version,
		UpdatedAt:      entity.UpdatedAt,
		UpdatedBy:      userRefFromID(entity.UpdatedBy),
	}
	normalizeNotificationSettings(&snapshot.Notifications)
	normalizeDataSyncSettings(&snapshot.DataSync)
	snapshot.Checksum = computeSettingsChecksum(snapshot)
	return snapshot
}

func settingsToJSONB(snapshot *Settings) model.JSONB {
	if snapshot == nil {
		return model.JSONB{}
	}
	clone := *snapshot
	clone.Checksum = ""
	data, err := json.Marshal(clone)
	if err != nil {
		// If marshalling fails, return a minimal valid JSONB instead of empty
		// This ensures audit logs are still recorded even if full state can't be captured
		return model.JSONB{
			"error": fmt.Sprintf("failed to marshal settings: %v", err),
		}
	}
	var out model.JSONB
	if err := json.Unmarshal(data, &out); err != nil {
		// If unmarshalling fails, return a minimal valid JSONB instead of empty
		return model.JSONB{
			"error": fmt.Sprintf("failed to unmarshal settings: %v", err),
		}
	}
	return out
}

func userRefFromID(id *uint64) *UserRef {
	if id == nil {
		return nil
	}
	return &UserRef{ID: fmt.Sprintf("%d", *id)}
}

func defaultSettingsSnapshot() Settings {
	return Settings{
		Platform:       defaultPlatformSettings(),
		Authentication: defaultAuthenticationSettings(),
		Notifications:  defaultNotificationSettings(),
		DataSync:       defaultDataSyncSettings(),
		Version:        1,
	}
}

func defaultSettingsModel() *model.SystemSettings {
	snapshot := defaultSettingsSnapshot()
	snapshot.Checksum = ""
	return &model.SystemSettings{
		Version:        snapshot.Version,
		Platform:       marshalSection(snapshot.Platform),
		Authentication: marshalSection(snapshot.Authentication),
		Notifications:  marshalSection(snapshot.Notifications),
		DataSync:       marshalSection(snapshot.DataSync),
	}
}

func defaultPlatformSettings() PlatformSettings {
	return PlatformSettings{
		DisplayName:     "Security Center",
		SupportEmail:    "secops@example.com",
		DefaultTimezone: "UTC",
		MaintenanceMode: false,
		LogoURL:         "",
	}
}

func (s *Service) loadFromCache(ctx context.Context) *Settings {
	if s.cache == nil {
		return nil
	}
	payload, err := s.cache.Get(ctx, cacheKeySettings)
	if err != nil || payload == "" {
		return nil
	}
	var snapshot Settings
	if err := json.Unmarshal([]byte(payload), &snapshot); err != nil {
		return nil
	}
	snapshot.Notifications.LarkReceiveIDType = normalizeLarkReceiveIDType(snapshot.Notifications.LarkReceiveIDType)
	normalizeDataSyncSettings(&snapshot.DataSync)
	return &snapshot
}

func (s *Service) saveToCache(ctx context.Context, snapshot *Settings) {
	if s.cache == nil || snapshot == nil {
		return
	}
	data, err := json.Marshal(snapshot)
	if err != nil {
		return
	}
	_ = s.cache.Set(ctx, cacheKeySettings, data, cacheTTL)
}

func (s *Service) evictCache(ctx context.Context) {
	if s.cache == nil {
		return
	}
	_ = s.cache.Delete(ctx, cacheKeySettings)
}

func computeSettingsChecksum(snapshot *Settings) string {
	if snapshot == nil {
		return ""
	}
	clone := *snapshot
	clone.Checksum = ""
	data, err := json.Marshal(clone)
	if err != nil {
		return ""
	}
	sum := sha256.Sum256(data)
	return hex.EncodeToString(sum[:])
}

func defaultAuthenticationSettings() AuthenticationSettings {
	return AuthenticationSettings{
		SSOProvider:           "google",
		EnforceSSO:            true,
		DefaultRole:           "viewer",
		SessionTimeoutMinutes: 60,
		AllowLocalUsers:       false,
		MFARequired:           false,
		Auth0Domain:           "",
		Auth0ClientID:         "",
		Auth0ClientSecret:     "",
		Auth0Audience:         "",
	}
}

func defaultNotificationSettings() NotificationSettings {
	return NotificationSettings{
		EmailAlerts:                        true,
		EmailFromAddress:                   "alerts@example.com",
		LarkAlerts:                         false,
		LarkWebhook:                        "",
		LarkAppID:                          "",
		LarkAppSecret:                      "",
		LarkReceiveIDType:                  "open_id",
		VulnerabilitySLARemindersEnabled:   false,
		VulnerabilitySLADueSoonBeforeHours: 24,
		VulnerabilitySLAOverdueRepeatHours: 24,
		IncidentWebhook:                    "",
		SeverityThreshold:                  "high",
		DigestEnabled:                      true,
	}
}

func normalizeLarkReceiveIDType(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "", "open_id", "user_id":
		return "open_id"
	case "email":
		return "email"
	default:
		return "open_id"
	}
}

func normalizeNotificationSettings(settings *NotificationSettings) {
	if settings == nil {
		return
	}
	settings.LarkReceiveIDType = normalizeLarkReceiveIDType(settings.LarkReceiveIDType)
	if settings.VulnerabilitySLADueSoonBeforeHours <= 0 {
		settings.VulnerabilitySLADueSoonBeforeHours = 24
	}
	if settings.VulnerabilitySLAOverdueRepeatHours <= 0 {
		settings.VulnerabilitySLAOverdueRepeatHours = 24
	}
}

func defaultDataSyncSettings() DataSyncSettings {
	return DataSyncSettings{
		OrgSyncEnabled:           false,
		OrgProvider:              "feishu",
		OrgSyncCron:              "0 * * * *",
		OrgSyncStaleAfterHours:   24,
		OrgSyncExpiredAfterHours: 72,
		LarkAppID:                "",
		LarkAppSecret:            "",
	}
}

func normalizeDataSyncSettings(settings *DataSyncSettings) {
	if settings == nil {
		return
	}
	defaults := defaultDataSyncSettings()
	if settings.OrgSyncStaleAfterHours == 0 {
		settings.OrgSyncStaleAfterHours = defaults.OrgSyncStaleAfterHours
	}
	if settings.OrgSyncExpiredAfterHours == 0 {
		settings.OrgSyncExpiredAfterHours = defaults.OrgSyncExpiredAfterHours
	}
}

func validateDataSyncSettings(settings DataSyncSettings) error {
	if settings.OrgSyncStaleAfterHours <= 0 {
		return fmt.Errorf("%w: org_sync_stale_after_hours must be a positive integer", ErrInvalidSettings)
	}
	if settings.OrgSyncExpiredAfterHours <= 0 {
		return fmt.Errorf("%w: org_sync_expired_after_hours must be a positive integer", ErrInvalidSettings)
	}
	if settings.OrgSyncStaleAfterHours >= settings.OrgSyncExpiredAfterHours {
		return fmt.Errorf("%w: org_sync_stale_after_hours must be less than org_sync_expired_after_hours", ErrInvalidSettings)
	}
	return nil
}

// orgSyncChanged checks if any org sync related settings have changed.
func orgSyncChanged(before DataSyncSettings, after DataSyncSettings) bool {
	return before.OrgSyncEnabled != after.OrgSyncEnabled ||
		before.OrgSyncCron != after.OrgSyncCron ||
		before.OrgSyncStaleAfterHours != after.OrgSyncStaleAfterHours ||
		before.OrgSyncExpiredAfterHours != after.OrgSyncExpiredAfterHours ||
		before.LarkAppID != after.LarkAppID ||
		before.LarkAppSecret != after.LarkAppSecret
}

func notificationsChanged(before NotificationSettings, after NotificationSettings) bool {
	return before.LarkAlerts != after.LarkAlerts ||
		before.LarkWebhook != after.LarkWebhook ||
		before.LarkAppID != after.LarkAppID ||
		before.LarkAppSecret != after.LarkAppSecret ||
		before.LarkReceiveIDType != after.LarkReceiveIDType ||
		before.VulnerabilitySLARemindersEnabled != after.VulnerabilitySLARemindersEnabled ||
		before.VulnerabilitySLADueSoonBeforeHours != after.VulnerabilitySLADueSoonBeforeHours ||
		before.VulnerabilitySLAOverdueRepeatHours != after.VulnerabilitySLAOverdueRepeatHours
}
