package miner

import (
	"context"
	"crypto/rand"
	"math"
	"time"

	"golang.org/x/xerrors"

	"github.com/filecoin-project/go-bitfield"
	"github.com/filecoin-project/go-state-types/abi"
	proof7 "github.com/filecoin-project/specs-actors/v7/actors/runtime/proof"

	"github.com/filecoin-project/lotus/chain/types"
)

func (m *Miner) winPoStWarmup(ctx context.Context) error {
	deadlines, err := m.api.StateMinerDeadlines(ctx, m.address, types.EmptyTSK)
	if err != nil {
		return xerrors.Errorf("getting deadlines: %w", err)
	}

	var sector abi.SectorNumber = math.MaxUint64

out:
	for dlIdx := range deadlines {
		partitions, err := m.api.StateMinerPartitions(ctx, m.address, uint64(dlIdx), types.EmptyTSK)
		if err != nil {
			return xerrors.Errorf("getting partitions for deadline %d: %w", dlIdx, err)
		}

		for _, partition := range partitions {
			b, err := partition.ActiveSectors.First()
			if err == bitfield.ErrNoBitsSet {
				continue
			}
			if err != nil {
				return err
			}

			sector = abi.SectorNumber(b)
			break out
		}
	}

	if sector == math.MaxUint64 {
		log.Info("skipping winning PoSt warmup, no sectors")
		return nil
	}

	log.Infow("starting winning PoSt warmup", "sector", sector)
	start := time.Now()

	var r abi.PoStRandomness = make([]byte, abi.RandomnessLength)
	_, _ = rand.Read(r)

	si, err := m.api.StateSectorGetInfo(ctx, m.address, sector, types.EmptyTSK)
	if err != nil {
		return xerrors.Errorf("getting sector info: %w", err)
	}
	if si == nil {
		return xerrors.Errorf("sector not found %d", sector)
	}

	ts, err := m.api.ChainHead(ctx)
	if err != nil {
		return xerrors.Errorf("getting chain head")
	}
	nv, err := m.api.StateNetworkVersion(ctx, ts.Key())
	if err != nil {
		return xerrors.Errorf("getting network version")
	}

	_, err = m.epp.ComputeProof(ctx, []proof7.ExtendedSectorInfo{
		{
			SealProof:    si.SealProof,
			SectorNumber: sector,
			SealedCID:    si.SealedCID,
			SectorKey:    si.SectorKeyCID,
		},
	}, r, ts.Height(), nv)
	if err != nil {
		return xerrors.Errorf("failed to compute proof: %w", err)
	}

	log.Infow("winning PoSt warmup successful", "took", time.Now().Sub(start))
	return nil
}

func (m *Miner) doWinPoStWarmup(ctx context.Context) {
	err := m.winPoStWarmup(ctx)
	if err != nil {
		log.Errorw("winning PoSt warmup failed", "error", err)
	}
}