package activity

import (
	"bindbox-game/internal/repository/mysql"
	"bindbox-game/internal/repository/mysql/dao"
	"context"
	"crypto/rand"
	"crypto/sha256"
	"errors"
)

type ActivityCommitmentService struct {
	read  *dao.Query
	write *dao.Query
	repo  mysql.Repo
}

func NewActivityCommitmentService(read *dao.Query, write *dao.Query, repo mysql.Repo) *ActivityCommitmentService {
	return &ActivityCommitmentService{read: read, write: write, repo: repo}
}

func (s *ActivityCommitmentService) Generate(ctx context.Context, activityID int64) (int32, error) {
	act, err := s.read.Activities.WithContext(ctx).Where(s.read.Activities.ID.Eq(activityID)).First()
	if err != nil || act == nil {
		return 0, errors.New("activity not found")
	}
	var cur int32
	_ = s.repo.GetDbR().Raw("SELECT IFNULL(commitment_state_version,0) FROM activities WHERE id=?", activityID).Scan(&cur)
	seed := make([]byte, 32)
	_, _ = rand.Read(seed)
	seedHash := sha256.Sum256(seed)
	// compute items_root by aggregating all issues' reward slots
	issueIDs := make([]int64, 0)
	issues, _ := s.read.ActivityIssues.WithContext(ctx).ReadDB().Where(s.read.ActivityIssues.ActivityID.Eq(activityID)).Find()
	for _, is := range issues {
		issueIDs = append(issueIDs, is.ID)
	}
	var itemsRoot []byte
	if len(issueIDs) > 0 {
		// fetch rewards per issue and build slots
		slots := make([]int64, 0)
		for _, iid := range issueIDs {
			rs, _ := s.read.ActivityRewardSettings.WithContext(ctx).ReadDB().Where(s.read.ActivityRewardSettings.IssueID.Eq(iid)).Order(
				s.read.ActivityRewardSettings.IsBoss.Desc(),
				s.read.ActivityRewardSettings.Level.Asc(),
				s.read.ActivityRewardSettings.Sort.Asc(),
			).Find()
			for _, r := range rs {
				for i := int64(0); i < r.OriginalQty; i++ {
					slots = append(slots, r.ID)
				}
			}
		}
		if len(slots) > 0 {
			// naive JSON encoding to compute root
			// Note: avoid importing encoding/json at top, compute hash over formatted bytes
			// but simpler: use []byte with little-endian concatenation
			h := sha256.New()
			for _, v := range slots {
				var buf [8]byte
				// big endian
				buf[0] = byte(v >> 56)
				buf[1] = byte(v >> 48)
				buf[2] = byte(v >> 40)
				buf[3] = byte(v >> 32)
				buf[4] = byte(v >> 24)
				buf[5] = byte(v >> 16)
				buf[6] = byte(v >> 8)
				buf[7] = byte(v)
				h.Write(buf[:])
			}
			itemsRoot = h.Sum(nil)
		}
	}
	ver := cur + 1
	_, err = s.write.Activities.WithContext(ctx).Where(s.write.Activities.ID.Eq(act.ID)).Updates(map[string]any{
		"commitment_algo":          "commit-v1",
		"commitment_seed_master":   seed,
		"commitment_seed_hash":     seedHash[:],
		"commitment_state_version": ver,
		"commitment_items_root":    itemsRoot,
	})
	if err != nil {
		return 0, err
	}
	return ver, nil
}

type ActivityCommitmentSummary struct {
	SeedVersion int32
	Algo        string
	HasSeed     bool
	ItemsRoot   []byte
}

func (s *ActivityCommitmentService) Summary(ctx context.Context, activityID int64) (ActivityCommitmentSummary, error) {
	var ver int32
	var algo string
	var hasSeed bool
	// read version
	_ = s.repo.GetDbR().Raw("SELECT IFNULL(commitment_state_version,0) FROM activities WHERE id=?", activityID).Scan(&ver)
	_ = s.repo.GetDbR().Raw("SELECT commitment_algo FROM activities WHERE id=?", activityID).Scan(&algo)
	var seedLen *int
	_ = s.repo.GetDbR().Raw("SELECT LENGTH(commitment_seed_master) FROM activities WHERE id=?", activityID).Scan(&seedLen)
	hasSeed = seedLen != nil && *seedLen > 0
	return ActivityCommitmentSummary{SeedVersion: ver, Algo: algo, HasSeed: hasSeed, ItemsRoot: nil}, nil
}
