// Package scoring provides importance score calculation for observations.
package scoring

import (
	"context"
	"sync"
	"time"

	"github.com/rs/zerolog"

	"github.com/lukaszraczylo/claude-mnemonic/internal/db/gorm"
	"github.com/lukaszraczylo/claude-mnemonic/pkg/models"
)

// ObservationStore defines the interface for observation storage operations needed by the recalculator.
type ObservationStore interface {
	GetObservationsNeedingScoreUpdate(ctx context.Context, threshold time.Duration, limit int) ([]*models.Observation, error)
	UpdateImportanceScores(ctx context.Context, scores map[int64]float64) error
	GetConceptWeights(ctx context.Context) (map[string]float64, error)
}

// Recalculator periodically recalculates importance scores for observations.
type Recalculator struct {
	log        zerolog.Logger
	store      ObservationStore
	calculator *Calculator
	stopCh     chan struct{}
	doneCh     chan struct{}
	interval   time.Duration
	batchSize  int
	mu         sync.Mutex
	running    bool
}

// NewRecalculator creates a new background recalculator.
func NewRecalculator(store ObservationStore, calc *Calculator, log zerolog.Logger) *Recalculator {
	return &Recalculator{
		store:      store,
		calculator: calc,
		log:        log.With().Str("component", "recalculator").Logger(),
		interval:   1 * time.Hour, // Run every hour
		batchSize:  500,           // Process 500 observations at a time
		stopCh:     make(chan struct{}),
		doneCh:     make(chan struct{}),
	}
}

// Start begins the background recalculation loop.
// This should be called in a goroutine.
func (r *Recalculator) Start(ctx context.Context) {
	r.mu.Lock()
	if r.running {
		r.mu.Unlock()
		return
	}
	r.running = true
	r.mu.Unlock()

	defer func() {
		r.mu.Lock()
		r.running = false
		r.mu.Unlock()
		close(r.doneCh)
	}()

	// Initial run
	r.recalculate(ctx)

	r.mu.Lock()
	interval := r.interval
	r.mu.Unlock()

	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			r.log.Info().Msg("recalculator shutting down due to context cancellation")
			return
		case <-r.stopCh:
			r.log.Info().Msg("recalculator stopping")
			return
		case <-ticker.C:
			r.recalculate(ctx)
		}
	}
}

// Stop stops the background recalculation loop.
func (r *Recalculator) Stop() {
	r.mu.Lock()
	if !r.running {
		r.mu.Unlock()
		return
	}
	r.mu.Unlock()

	close(r.stopCh)
	<-r.doneCh
}

// recalculate performs a single recalculation batch.
func (r *Recalculator) recalculate(ctx context.Context) {
	now := time.Now()
	threshold := r.calculator.RecalculateThreshold()

	r.mu.Lock()
	batchSize := r.batchSize
	r.mu.Unlock()

	observations, err := r.store.GetObservationsNeedingScoreUpdate(ctx, threshold, batchSize)
	if err != nil {
		r.log.Error().Err(err).Msg("failed to get observations for score update")
		return
	}

	if len(observations) == 0 {
		return
	}

	scores := r.calculator.BatchCalculate(observations, now)

	if err := r.store.UpdateImportanceScores(ctx, scores); err != nil {
		r.log.Error().Err(err).Msg("failed to update importance scores")
		return
	}

	r.log.Info().
		Int("count", len(scores)).
		Dur("elapsed", time.Since(now)).
		Msg("recalculated importance scores")
}

// RecalculateNow triggers an immediate recalculation.
// This is useful for testing or when scores need to be updated urgently.
func (r *Recalculator) RecalculateNow(ctx context.Context) error {
	r.recalculate(ctx)
	return nil
}

// RefreshConceptWeights reloads concept weights from the database.
// Call this after updating concept weights to apply changes.
func (r *Recalculator) RefreshConceptWeights(ctx context.Context) error {
	weights, err := r.store.GetConceptWeights(ctx)
	if err != nil {
		return err
	}

	config := r.calculator.GetConfig()
	config.ConceptWeights = weights
	r.calculator.UpdateConfig(config)

	r.log.Info().Int("count", len(weights)).Msg("refreshed concept weights")
	return nil
}

// Stats returns statistics about the recalculator.
type Stats struct {
	Running     bool          `json:"running"`
	Interval    time.Duration `json:"interval"`
	BatchSize   int           `json:"batch_size"`
	HalfLife    float64       `json:"half_life_days"`
	MinScore    float64       `json:"min_score"`
	ConceptsLen int           `json:"concepts_count"`
}

// GetStats returns current recalculator statistics.
func (r *Recalculator) GetStats() Stats {
	r.mu.Lock()
	defer r.mu.Unlock()

	config := r.calculator.GetConfig()

	return Stats{
		Running:     r.running,
		Interval:    r.interval,
		BatchSize:   r.batchSize,
		HalfLife:    config.RecencyHalfLifeDays,
		MinScore:    config.MinScore,
		ConceptsLen: len(config.ConceptWeights),
	}
}

// Ensure ObservationStore satisfies the interface
var _ ObservationStore = (*gorm.ObservationStore)(nil)
