package usecase

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"strings"

	"apps/backend/internal/module/radar/domain"
)

// JudgeCandidate is the metered boundary. A successful structured result is
// cached by source identity and DemandMap version; cache hits return zero
// credits and never call the provider again.
func (s *Service) JudgeCandidate(ctx context.Context, ownerUID int64, profile *domain.ServiceProfile, watch *domain.RadarWatch, cand *domain.CandidatePost) (*JudgeResult, int, error) {
	key := s.judgeCacheKey(ctx, ownerUID, watch, cand)
	if key != "" {
		if result, ok := s.cachedJudgeResult(key); ok {
			return result, 0, nil
		}
	}
	result, credits, err := s.judgeCandidateUncached(ctx, ownerUID, profile, watch, cand)
	if err == nil && result != nil && result.Cacheable && key != "" {
		s.storeJudgeResult(key, result)
	}
	return result, credits, err
}

func (s *Service) judgeCacheKey(ctx context.Context, ownerUID int64, watch *domain.RadarWatch, cand *domain.CandidatePost) string {
	if cand == nil || strings.TrimSpace(cand.ExternalID) == "" {
		return ""
	}
	contextKey := "generic"
	mapVersion := int64(0)
	if watch != nil {
		contextKey = watch.ContextMode + ":" + watch.ProductID
		if watch.ContextMode == domain.WatchContextProduct && s.Repo != nil {
			if dm, err := s.GetDemandMap(ctx, ownerUID, watch.ProductID); err == nil && dm != nil {
				mapVersion = dm.MapVersion
			}
		}
	}
	raw := fmt.Sprintf("%d|%s|%d|%s|%s|%d", ownerUID, contextKey, mapVersion, cand.ExternalID, cand.Text, cand.PostedAt)
	sum := sha256.Sum256([]byte(raw))
	return hex.EncodeToString(sum[:])
}

func (s *Service) cachedJudgeResult(key string) (*JudgeResult, bool) {
	s.judgeCacheMu.Lock()
	defer s.judgeCacheMu.Unlock()
	if s.judgeCache == nil {
		s.judgeCache = map[string]cachedJudge{}
	}
	value, ok := s.judgeCache[key]
	if !ok || domain.NowNano()-value.createdAt > int64(24*60*60*1e9) {
		if ok {
			delete(s.judgeCache, key)
		}
		return nil, false
	}
	return cloneJudgeResult(value.result), true
}

func (s *Service) storeJudgeResult(key string, result *JudgeResult) {
	s.judgeCacheMu.Lock()
	defer s.judgeCacheMu.Unlock()
	if s.judgeCache == nil {
		s.judgeCache = map[string]cachedJudge{}
	}
	if len(s.judgeCache) >= 2048 {
		for oldKey := range s.judgeCache {
			delete(s.judgeCache, oldKey)
			break
		}
	}
	s.judgeCache[key] = cachedJudge{result: cloneJudgeResult(result), createdAt: domain.NowNano()}
}

func cloneJudgeResult(in *JudgeResult) *JudgeResult {
	if in == nil {
		return nil
	}
	out := *in
	out.Reasons = append([]domain.OpportunityReason(nil), in.Reasons...)
	if in.ProductMatch != nil {
		out.ProductMatch = domain.CloneProductMatch(in.ProductMatch)
	}
	return &out
}
