package analytics

import (
	"context"
	"errors"
	"fmt"
	"sort"
	"strings"
	"tercul/internal/domain"
	"tercul/internal/jobs/linguistics"
	"tercul/internal/platform/log"
	"time"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/trace"
)

type Service interface {
	IncrementWorkViews(ctx context.Context, workID uint) error
	IncrementWorkLikes(ctx context.Context, workID uint) error
	IncrementWorkComments(ctx context.Context, workID uint) error
	IncrementWorkBookmarks(ctx context.Context, workID uint) error
	IncrementWorkShares(ctx context.Context, workID uint) error
	IncrementWorkTranslationCount(ctx context.Context, workID uint) error
	IncrementTranslationViews(ctx context.Context, translationID uint) error
	IncrementTranslationLikes(ctx context.Context, translationID uint) error
	DecrementWorkLikes(ctx context.Context, workID uint) error
	DecrementTranslationLikes(ctx context.Context, translationID uint) error
	IncrementTranslationComments(ctx context.Context, translationID uint) error
	IncrementTranslationShares(ctx context.Context, translationID uint) error
	GetOrCreateWorkStats(ctx context.Context, workID uint) (*domain.WorkStats, error)
	GetOrCreateTranslationStats(ctx context.Context, translationID uint) (*domain.TranslationStats, error)

	UpdateWorkReadingTime(ctx context.Context, workID uint) error
	UpdateWorkComplexity(ctx context.Context, workID uint) error
	UpdateWorkSentiment(ctx context.Context, workID uint) error
	UpdateTranslationReadingTime(ctx context.Context, translationID uint) error
	UpdateTranslationSentiment(ctx context.Context, translationID uint) error

	UpdateUserEngagement(ctx context.Context, userID uint, eventType string) error
	UpdateTrending(ctx context.Context) error
	GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*domain.Work, error)
	UpdateWorkStats(ctx context.Context, workID uint, stats domain.WorkStats) error
}

type service struct {
	repo              Repository
	analysisRepo      linguistics.AnalysisRepository
	translationRepo   domain.TranslationRepository
	workRepo          domain.WorkRepository
	sentimentProvider linguistics.SentimentProvider
	tracer            trace.Tracer
}

func NewService(repo Repository, analysisRepo linguistics.AnalysisRepository, translationRepo domain.TranslationRepository, workRepo domain.WorkRepository, sentimentProvider linguistics.SentimentProvider) Service {
	return &service{
		repo:              repo,
		analysisRepo:      analysisRepo,
		translationRepo:   translationRepo,
		workRepo:          workRepo,
		sentimentProvider: sentimentProvider,
		tracer:            otel.Tracer("analytics.service"),
	}
}

func (s *service) IncrementWorkViews(ctx context.Context, workID uint) error {
	ctx, span := s.tracer.Start(ctx, "IncrementWorkViews")
	defer span.End()
	return s.repo.IncrementWorkCounter(ctx, workID, "views", 1)
}

func (s *service) IncrementWorkLikes(ctx context.Context, workID uint) error {
	ctx, span := s.tracer.Start(ctx, "IncrementWorkLikes")
	defer span.End()
	return s.repo.IncrementWorkCounter(ctx, workID, "likes", 1)
}

func (s *service) DecrementWorkLikes(ctx context.Context, workID uint) error {
	ctx, span := s.tracer.Start(ctx, "DecrementWorkLikes")
	defer span.End()
	return s.repo.IncrementWorkCounter(ctx, workID, "likes", -1)
}

func (s *service) IncrementWorkComments(ctx context.Context, workID uint) error {
	ctx, span := s.tracer.Start(ctx, "IncrementWorkComments")
	defer span.End()
	return s.repo.IncrementWorkCounter(ctx, workID, "comments", 1)
}

func (s *service) IncrementWorkBookmarks(ctx context.Context, workID uint) error {
	ctx, span := s.tracer.Start(ctx, "IncrementWorkBookmarks")
	defer span.End()
	return s.repo.IncrementWorkCounter(ctx, workID, "bookmarks", 1)
}

func (s *service) IncrementWorkShares(ctx context.Context, workID uint) error {
	ctx, span := s.tracer.Start(ctx, "IncrementWorkShares")
	defer span.End()
	return s.repo.IncrementWorkCounter(ctx, workID, "shares", 1)
}

func (s *service) IncrementWorkTranslationCount(ctx context.Context, workID uint) error {
	ctx, span := s.tracer.Start(ctx, "IncrementWorkTranslationCount")
	defer span.End()
	return s.repo.IncrementWorkCounter(ctx, workID, "translation_count", 1)
}

func (s *service) IncrementTranslationViews(ctx context.Context, translationID uint) error {
	ctx, span := s.tracer.Start(ctx, "IncrementTranslationViews")
	defer span.End()
	return s.repo.IncrementTranslationCounter(ctx, translationID, "views", 1)
}

func (s *service) IncrementTranslationLikes(ctx context.Context, translationID uint) error {
	ctx, span := s.tracer.Start(ctx, "IncrementTranslationLikes")
	defer span.End()
	return s.repo.IncrementTranslationCounter(ctx, translationID, "likes", 1)
}

func (s *service) DecrementTranslationLikes(ctx context.Context, translationID uint) error {
	ctx, span := s.tracer.Start(ctx, "DecrementTranslationLikes")
	defer span.End()
	return s.repo.IncrementTranslationCounter(ctx, translationID, "likes", -1)
}

func (s *service) IncrementTranslationComments(ctx context.Context, translationID uint) error {
	ctx, span := s.tracer.Start(ctx, "IncrementTranslationComments")
	defer span.End()
	return s.repo.IncrementTranslationCounter(ctx, translationID, "comments", 1)
}

func (s *service) IncrementTranslationShares(ctx context.Context, translationID uint) error {
	ctx, span := s.tracer.Start(ctx, "IncrementTranslationShares")
	defer span.End()
	return s.repo.IncrementTranslationCounter(ctx, translationID, "shares", 1)
}

func (s *service) GetOrCreateWorkStats(ctx context.Context, workID uint) (*domain.WorkStats, error) {
	ctx, span := s.tracer.Start(ctx, "GetOrCreateWorkStats")
	defer span.End()
	return s.repo.GetOrCreateWorkStats(ctx, workID)
}

func (s *service) GetOrCreateTranslationStats(ctx context.Context, translationID uint) (*domain.TranslationStats, error) {
	ctx, span := s.tracer.Start(ctx, "GetOrCreateTranslationStats")
	defer span.End()
	return s.repo.GetOrCreateTranslationStats(ctx, translationID)
}

func (s *service) UpdateWorkReadingTime(ctx context.Context, workID uint) error {
	ctx, span := s.tracer.Start(ctx, "UpdateWorkReadingTime")
	defer span.End()
	stats, err := s.repo.GetOrCreateWorkStats(ctx, workID)
	if err != nil {
		return err
	}

	textMetadata, _, _, err := s.analysisRepo.GetAnalysisData(ctx, workID)
	if err != nil {
		return err
	}

	if textMetadata == nil {
		return errors.New("text metadata not found")
	}

	readingTime := 0
	if textMetadata.WordCount > 0 {
		readingTime = (textMetadata.WordCount + 199) / 200 // Ceil division
	}

	stats.ReadingTime = readingTime

	return s.repo.UpdateWorkStats(ctx, workID, *stats)
}

func (s *service) UpdateWorkComplexity(ctx context.Context, workID uint) error {
	ctx, span := s.tracer.Start(ctx, "UpdateWorkComplexity")
	defer span.End()
	logger := log.FromContext(ctx).With("workID", workID)
	stats, err := s.repo.GetOrCreateWorkStats(ctx, workID)
	if err != nil {
		return err
	}

	_, readabilityScore, _, err := s.analysisRepo.GetAnalysisData(ctx, workID)
	if err != nil {
		logger.Error(err, "could not get readability score for work")
		return nil
	}

	if readabilityScore == nil {
		return errors.New("readability score not found")
	}

	stats.Complexity = readabilityScore.Score

	return s.repo.UpdateWorkStats(ctx, workID, *stats)
}

func (s *service) UpdateWorkSentiment(ctx context.Context, workID uint) error {
	ctx, span := s.tracer.Start(ctx, "UpdateWorkSentiment")
	defer span.End()
	logger := log.FromContext(ctx).With("workID", workID)
	stats, err := s.repo.GetOrCreateWorkStats(ctx, workID)
	if err != nil {
		return err
	}

	_, _, languageAnalysis, err := s.analysisRepo.GetAnalysisData(ctx, workID)
	if err != nil {
		logger.Error(err, "could not get language analysis for work")
		return nil
	}

	if languageAnalysis == nil {
		return errors.New("language analysis not found")
	}

	sentiment, ok := languageAnalysis.Analysis["sentiment"].(float64)
	if !ok {
		return errors.New("sentiment score not found in language analysis")
	}

	stats.Sentiment = sentiment

	return s.repo.UpdateWorkStats(ctx, workID, *stats)
}

func (s *service) UpdateTranslationReadingTime(ctx context.Context, translationID uint) error {
	ctx, span := s.tracer.Start(ctx, "UpdateTranslationReadingTime")
	defer span.End()
	stats, err := s.repo.GetOrCreateTranslationStats(ctx, translationID)
	if err != nil {
		return err
	}

	translation, err := s.translationRepo.GetByID(ctx, translationID)
	if err != nil {
		return err
	}

	if translation == nil {
		return errors.New("translation not found")
	}

	wordCount := len(strings.Fields(translation.Content))
	readingTime := 0
	if wordCount > 0 {
		readingTime = (wordCount + 199) / 200 // Ceil division
	}

	stats.ReadingTime = readingTime

	return s.repo.UpdateTranslationStats(ctx, translationID, *stats)
}

func (s *service) UpdateTranslationSentiment(ctx context.Context, translationID uint) error {
	ctx, span := s.tracer.Start(ctx, "UpdateTranslationSentiment")
	defer span.End()
	stats, err := s.repo.GetOrCreateTranslationStats(ctx, translationID)
	if err != nil {
		return err
	}

	translation, err := s.translationRepo.GetByID(ctx, translationID)
	if err != nil {
		return err
	}

	if translation == nil {
		return errors.New("translation not found")
	}

	sentiment, err := s.sentimentProvider.Score(translation.Content, translation.Language)
	if err != nil {
		return err
	}

	stats.Sentiment = sentiment

	return s.repo.UpdateTranslationStats(ctx, translationID, *stats)
}

func (s *service) UpdateUserEngagement(ctx context.Context, userID uint, eventType string) error {
	ctx, span := s.tracer.Start(ctx, "UpdateUserEngagement")
	defer span.End()
	today := time.Now().UTC().Truncate(24 * time.Hour)
	engagement, err := s.repo.GetOrCreateUserEngagement(ctx, userID, today)
	if err != nil {
		return err
	}

	switch eventType {
	case "work_read":
		engagement.WorksRead++
	case "comment_made":
		engagement.CommentsMade++
	case "like_given":
		engagement.LikesGiven++
	case "bookmark_made":
		engagement.BookmarksMade++
	case "translation_made":
		engagement.TranslationsMade++
	default:
		return errors.New("invalid engagement event type")
	}

	return s.repo.UpdateUserEngagement(ctx, engagement)
}

func (s *service) GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*domain.Work, error) {
	ctx, span := s.tracer.Start(ctx, "GetTrendingWorks")
	defer span.End()
	return s.repo.GetTrendingWorks(ctx, timePeriod, limit)
}

func (s *service) UpdateWorkStats(ctx context.Context, workID uint, stats domain.WorkStats) error {
	ctx, span := s.tracer.Start(ctx, "UpdateWorkStats")
	defer span.End()
	return s.repo.UpdateWorkStats(ctx, workID, stats)
}

func (s *service) UpdateTrending(ctx context.Context) error {
	ctx, span := s.tracer.Start(ctx, "UpdateTrending")
	defer span.End()
	logger := log.FromContext(ctx)
	logger.Info("Updating trending works")

	works, err := s.workRepo.ListAll(ctx)
	if err != nil {
		return fmt.Errorf("failed to list works: %w", err)
	}

	trendingWorks := make([]*domain.Trending, 0, len(works))
	for _, aWork := range works {
		stats, err := s.repo.GetOrCreateWorkStats(ctx, aWork.ID)
		if err != nil {
			logger.With("workID", aWork.ID).Error(err, "failed to get work stats")
			continue
		}

		score := float64(stats.Views*1 + stats.Likes*2 + stats.Comments*3)

		trendingWorks = append(trendingWorks, &domain.Trending{
			EntityType: "Work",
			EntityID:   aWork.ID,
			Score:      score,
			TimePeriod: "daily", // Hardcoded for now
			Date:       time.Now().UTC(),
		})
	}

	// Sort by score
	sort.Slice(trendingWorks, func(i, j int) bool {
		return trendingWorks[i].Score > trendingWorks[j].Score
	})

	// Get top 10
	if len(trendingWorks) > 10 {
		trendingWorks = trendingWorks[:10]
	}

	// Set ranks
	for i := range trendingWorks {
		trendingWorks[i].Rank = i + 1
	}

	return s.repo.UpdateTrendingWorks(ctx, "daily", trendingWorks)
}
