package seal

import(
	"context"
	"io"
	"io/ioutil"
	"os"
	// "bytes"
	"math/rand"

	saproof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof"

	"golang.org/x/xerrors"

	proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof"
	logging "github.com/ipfs/go-log/v2"
	"github.com/mitchellh/go-homedir"
	// commcid "github.com/filecoin-project/go-fil-commcid"
	ffi "github.com/filecoin-project/filecoin-ffi"
	"fil_integrate/extern/sector-storage/ffiwrapper"
	"fil_integrate/extern/sector-storage/ffiwrapper/basicfs"
	"fil_integrate/extern/sector-storage/storiface"

	"github.com/filecoin-project/specs-storage/storage"
	"github.com/filecoin-project/go-state-types/abi"
	"github.com/filecoin-project/go-state-types/network"
	"github.com/ipfs/go-cid"
	"github.com/minio/blake2b-simd"
	"fil_integrate/build"
)

var log = logging.Logger("sealing")

//32字节，总共256位
//[has_pre][MetaLen1..MetaLen4][PieceLen1..PieceLen4]
const TagLen int = 8


const NewestNetworkVersion = network.Version13

func AddPiece(
	sb *ffiwrapper.Sealer, 
	ctx context.Context, 
	sector storage.SectorRef, 
	existingPieceSizes []abi.UnpaddedPieceSize, 
	pieceSize abi.UnpaddedPieceSize, 
	file storage.Data,
) (abi.PieceInfo, error) {

	return sb.AddPiece(ctx, sector, existingPieceSizes, pieceSize, file)

}

// 没有测试
// func EncodeDataToPieces(sb *ffiwrapper.Sealer, ctx context.Context, sector storage.SectorRef, sectorSize abi.SectorSize, file storage.Data) (abi.PieceInfo, error) {
// 	var piecesID []byte
// 	var FinalPiece abi.PieceInfo
// 	var pieceNumber int32 = 1

// 	UnpaddedSectorSize := abi.PaddedPieceSize(sectorSize).Unpadded()
// 	buf := make([]byte, UnpaddedSectorSize)

// 	DataLen := UnpaddedSectorSize-TagLen

// 	for{
// 		copy(buf[:TagLen], 0)
// 		var MetaLen int = 0
// 		var rerr error
// 		for rbuf := buf[TagLen:]; len(rbuf) > 0; {
// 			n, rerr := file.read(buf[TagLen:])

// 			if rerr != nil && rerr != io.EOF{
// 				return nil, rerr
// 			}
// 			rbuf = rbuf[n:]
// 			MetaLen += n

// 			if rerr == io.EOF{
// 				break
// 			}
// 		}
// 		if rerr == io.EOF{
// 			//encode first sector
// 			pieceNumber, FinalPiece, err = EncodeData(buf, MetaLen, DataLen, piecesID, pieceNumber)
// 			if err != nil{
// 				return nil, err
// 			}
// 			break
// 		}
// 		copy(buf[:4], []byte(MetaLen))

// 		piece, err := sb.AddPiece(ctx, sector, nil, UnpaddedSectorSize, bytes.NewReader(buf), pieceNumber)
// 		if err != nil{
// 			return nil, err
// 		}
// 		piecesID = append(pieces, commcid.CIDToReplicaCommitmentV1(piece.PieceCID)...)
// 		pieceNumber += 1
// 	}
// 	return pieceNumber, FinalPiece, nil
// }

// func EncodeData(buf []byte, MetaLen int, DataLen int, piecesID []byte, pieceNumber int32) (int32, abi.PieceInfo, error) {
// 	//encode first sector
// 	remain := len(piecesID)
// 	CommLen := min(remain, ((DataLen-MetaLen)/32) * 32)
// 	rbuf := buf[MetaLen+TagLen:]
	
//     copy(buf[:4], []byte(MetaLen))
// 	copy(buf[4:8], []byte(CommLen))
// 	copy(rbuf, piecesID[:CommLen])
// 	copy(rbuf[CommLen:], 0)

// 	piecesID = piecesID[CommLen:]
// 	MetaLen = 0
// 	remain -= CommLen
// 	rbuf = buf[TagLen+32:]

// 	prePiece, err := sb.AddPiece(ctx, sector, nil, UnpaddedSectorSize, bytes.NewReader(buf), pieceNumber)
// 	pieceNumber += 1
// 	if err != nil{
// 		return nil, err
// 	}

// 	for ;remain > 0; {
// 		//encode next n sector
// 		CommLen := min(remain, ((DataLen-32)/32) * 32)

// 		copy(buf[:4], []byte(MetaLen | 0x80000000))
// 		copy(buf[4:8], []byte(CommLen))
// 		copy(buf[8:40], commcid.CIDToReplicaCommitmentV1(prePiece.PieceCID)...)
// 		copy(rbuf, piecesID[:CommLen])
// 		copy(rbuf[CommLen:], 0)

// 		piecesID = piecesID[CommLen:]
// 		MetaLen = 0
// 		remain -= CommLen
// 		rbuf = buf[TagLen:]

// 		prePiece, err = sb.AddPiece(ctx, sector, nil, UnpaddedSectorSize, bytes.NewReader(buf), pieceNumber)
// 		pieceNumber += 1
// 		if err != nil{
// 			return nil, err
// 		}
// 	}

// 	return pieceNumber, prePiece, nil
// }

// func DecodePieceToData(sb *ffiwrapper.Sealer, ctx context.Context, out io.Writer, finalPiece abi.PieceInfo, pieceNum int32, sectorSize abi.SectorSize) ([]abi.PieceInfo, error){
// 	var piecesID []abi.PieceInfo
// 	var commds []byte

// 	unpaddedSectorSize := abi.PaddedPieceSize(sectorSize).Unpadded()
// 	buf := make([]byte, unpaddedSectorSize)
// 	DataLen := unpaddedSectorSize-TagLen

// 	for ;pieceNum > 0; PieceNum-- {
// 		ok, err := sb.ReadPiece(ctx, sid, bytes.NewWriter(buf), 0, unpaddedSectorSize, pieceNum)
// 		if err != nil {
// 			return nil
// 		}
// 		if !ok {
// 			return xerrors.Errorf("Read pieces error!")
// 		}

// 		MetaLen := buf[0:4] & 0x7fffffff
// 		HasPre := buf[0:4] >> 31
// 		CommLen := buf[4:8]
// 		rbuf := buf[8:]

// 		if HasPre {
// 			prePiece := buf[8:40]
// 			CommLen -= 32
// 			rbuf = rbuf[32:]
// 			commd, _ := commcid.DataCommitmentV1ToCID(prePiece)
// 			piecesID = append(piecesID, abi.PieceInfo{abi.PaddedPieceSize(sectorSize), commd})
// 		}
// 		data := rbuf[:MetaLen]
// 		commds = append(commds, rbuf[MetaLen:MetaLen+CommLen]...)
// 		//**data顺序错了，需要
// 		n, werr := out.write(data[:])
// 		if werr != nil {
// 			return nil, werr
// 		}
// 	}

// 	for cbuf := commds; len(cbuf) > 0; {
// 		commd, _ := cbuf.DataCommitmentV1ToCID(commds[32])
// 		piecesID = append(piecesID, abi.PieceInfo{abi.PaddedPieceSize(sectorSize), commd})
// 		cbuf = cbuf[32:]
// 	}
// 	return piecesID, nil
// }

func AggregateWindowPoStProofs(
	sb *ffiwrapper.Sealer, 
	ctx context.Context, 
	randomnesses []abi.SealRandomness, 
	aggregate abi.RegisteredAggregationProof, 
	windowPoStProofs []proof5.PoStProof,
	sectorCount uint,
) ([]byte, error) {
	return sb.AggregateWindowPoStProofs(ctx, ffi.AggregateWindowPostInfos{
		Randomnesses: randomnesses,
		AggregateType: aggregate,
		Proofs: windowPoStProofs,
		SectorCount: sectorCount,
	})
}

func VerifyAggregateWindowPostProofs(
	ctx context.Context,
	proofType abi.RegisteredPoStProof,
	aggregateType abi.RegisteredAggregationProof,
	miner abi.ActorID,
	aggregateProof []byte,
	randomnesses []abi.SealRandomness,
	sealedSectors [][]proof5.SectorInfo,
) (bool, error) {
	var sectorInfos []proof5.SectorInfo
	arr := make([]uint, len(sealedSectors))
	for i, sectors := range(sealedSectors) {
		arr[i] = uint(len(sectors))
		for _, sector := range(sectors) {
			sectorInfos = append(sectorInfos, sector)
		}
	}
	return ffiwrapper.ProofVerifier.VerifyAggregateWindowPostProofs(ctx, ffi.AggregateWindowPostInfos{
		PoStType: proofType,
		AggregateType: aggregateType,
		Miner: miner,
		AggregationProof: aggregateProof,
		Randomnesses: randomnesses,
		ChallengedSectors: sectorInfos,
		Arr: arr,
	})
}

func CheckPieceAndDataRoot(
	sid storage.SectorRef, 
	comm_d cid.Cid, 
	pieces []abi.PieceInfo,
) (bool, error) {
	UnsealedCID, err := ffiwrapper.GenerateUnsealedCID(sid.ProofType, pieces)
	if err != nil{
		return false, err
	}

	return comm_d == UnsealedCID, nil
}

func Sealed(
	sb *ffiwrapper.Sealer, 
	ctx context.Context, 
	sid storage.SectorRef, 
	seed abi.InteractiveSealRandomness, 
	ticket abi.SealRandomness, 
	pieces []abi.PieceInfo,
) (storage.SectorCids, storage.Proof, storage.Commit1Out, error) {
	// var sealedSectors saproof2.SectorInfo

	log.Infof("[%d] Running replication(1)...", sid.ID.Number)

	pc1out, err := sb.SealPreCommit1(ctx, sid, ticket, pieces)
	if err != nil {
		return storage.SectorCids{}, nil, nil, xerrors.Errorf("commit: %w", err)
	}

	log.Infof("[%d] Running replication(2)...", sid.ID.Number)
	cids, err := sb.SealPreCommit2(ctx, sid, pc1out)
	if err != nil {
		return storage.SectorCids{}, nil, nil, xerrors.Errorf("commit: %w", err)
	}

	log.Infof("[%d] Generating PoRep for sector (1)", sid.ID.Number)
	c1o, err := sb.SealCommit1(ctx, sid, ticket, seed, pieces, cids)
	if err != nil {
		return storage.SectorCids{}, nil, nil, err
	}

	log.Infof("[%d] Generating PoRep for sector (2)", sid.ID.Number)

	var proof storage.Proof
	proof, err = sb.SealCommit2(ctx, sid, c1o)
	if err != nil {
		return storage.SectorCids{}, nil, nil, err
	}

	return cids, proof, c1o, nil
}

func GenProofForC2(
	sb *ffiwrapper.Sealer, 
	ctx context.Context, 
	sid storage.SectorRef, 
	c1Out storage.Commit1Out,
) (storage.Proof, error) {
	return sb.SealCommit2(ctx, sid, c1Out)
}

func GenProofForWindowPost(
	sb *ffiwrapper.Sealer, 
	ctx context.Context, 
	minerID abi.ActorID, 
	sectorInfo []proof5.SectorInfo, 
	randomness abi.PoStRandomness,
) ([]proof5.PoStProof, []abi.SectorID, error) {
	return sb.GenerateWindowPoSt(ctx, minerID, sectorInfo, randomness)
}

func UnsealedRange(
	sb *ffiwrapper.Sealer, 
	ctx context.Context, 
	sbfs *basicfs.Provider, 
	sid storage.SectorRef, 
	sectorSize abi.SectorSize, 
	ticket abi.SealRandomness, 
	commd cid.Cid, 
	out io.Writer, 
	offset storiface.UnpaddedByteIndex, 
	size abi.UnpaddedPieceSize,
) error {
	log.Infof("[%d] Unsealing sector", sid.ID.Number)
	{
		p, done, err := sbfs.AcquireSector(ctx, sid, storiface.FTUnsealed, storiface.FTNone, storiface.PathSealing)
		if err != nil {
			return xerrors.Errorf("acquire unsealed sector for removing: %w", err)
		}
		done()

		if err := os.Remove(p.Unsealed); err != nil {
			return xerrors.Errorf("removing unsealed sector: %w", err)
		}
	}

	err := sb.UnsealPiece(ctx, sid, 0, abi.PaddedPieceSize(sectorSize).Unpadded(), ticket, commd)
	if err != nil {
		return err
	}

	ok, err := sb.ReadPiece(ctx, out, sid, offset, size)
	if err != nil{
		return err
	}

	if !ok {
		return xerrors.Errorf("Read pieces error!")
	}

	return nil

}

func spt(ssize abi.SectorSize) abi.RegisteredSealProof {
	spt, err := build.SealProofTypeFromSectorSize(ssize, NewestNetworkVersion)
	if err != nil {
		panic(err)
	}

	return spt
}

func TestSealAndUnseal() error {
	//********************need (sb,ctx,sid,sectorSize,file,seed,ticket,challenge)****************//
	sdir, err := homedir.Expand("~/tmp")
	if err != nil {
		return err
	}

	err = os.MkdirAll(sdir, 0775) //nolint:gosec
	if err != nil {
		return xerrors.Errorf("creating sectorbuilder dir: %w", err)
	}

	tsdir, err := ioutil.TempDir(sdir, "bench")
	if err != nil {
		return err
	}
	// defer func() {
	// 	if err := os.RemoveAll(tsdir); err != nil {
	// 		log.Warn("remove all: ", err)
	// 	}
	// }()

	// TODO: pretty sure this isnt even needed?
	if err := os.MkdirAll(tsdir, 0775); err != nil {
		return err
	}
	sbfs := &basicfs.Provider{
			Root: tsdir,
		}
	sb ,err := ffiwrapper.New(sbfs)
	if err != nil{
		return err
	}
	ctx := context.TODO()
	sectorSize := abi.SectorSize(8*1024*1024)
	sid := storage.SectorRef{
		ID: abi.SectorID{
			Miner:  1000,
			Number: 0,
		},
		ProofType: spt(sectorSize),
	}
	file := rand.New(rand.NewSource(1587))
	seed := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 255}
	trand := blake2b.Sum256([]byte("ticket-preimage"))
	ticket := abi.SealRandomness(trand[:])
	var challenge [32]byte
	rand.Read(challenge[:])
	//ADD PIECES
	var existingPieceSizes []abi.UnpaddedPieceSize
	var pieces []abi.PieceInfo
	var sealedSectors []saproof2.SectorInfo

	piece, err := AddPiece(sb, ctx, sid, existingPieceSizes, abi.PaddedPieceSize(sectorSize/2).Unpadded(), file)
	if err != nil {
		return err
	}

	existingPieceSizes = append(existingPieceSizes, piece.Size.Unpadded())
	pieces = append(pieces, piece)

	piece, err = AddPiece(sb, ctx, sid, existingPieceSizes, abi.PaddedPieceSize(sectorSize/2).Unpadded(), file)
	if err != nil {
		return err
	}

	existingPieceSizes = append(existingPieceSizes, piece.Size.Unpadded())
	pieces = append(pieces, piece)

	//SEAL
	cids, proof1, c1o, err := Sealed(sb, ctx, sid, seed, ticket, pieces)
	if err != nil {
		return err
	}

	sealedSectors = append(sealedSectors, saproof2.SectorInfo{
		SealedCID:    cids.Sealed,
		SectorNumber: sid.ID.Number,
		SealProof:    sid.ProofType,
	})

	proof2, err := GenProofForC2(sb , ctx, sid, c1o)
	if err != nil {
		return err
	}

	ok, err := CheckPieceAndDataRoot(sid, cids.Unsealed, pieces)
	if err != nil {
		return err
	}

	if !ok {
		return xerrors.Errorf("commd and pieces info don't match")
	}

	//verify proof
	svi1 := saproof2.SealVerifyInfo{
		SectorID:              sid.ID,
		SealedCID:             cids.Sealed,
		SealProof:             sid.ProofType,
		Proof:                 proof1,
		DealIDs:               nil,
		Randomness:            ticket,
		InteractiveRandomness: seed,
		UnsealedCID:           cids.Unsealed,
	}

	svi2 := saproof2.SealVerifyInfo{
		SectorID:              sid.ID,
		SealedCID:             cids.Sealed,
		SealProof:             sid.ProofType,
		Proof:                 proof2,
		DealIDs:               nil,
		Randomness:            ticket,
		InteractiveRandomness: seed,
		UnsealedCID:           cids.Unsealed,
	}

	ok, err = ffiwrapper.ProofVerifier.VerifySeal(svi1)
	if err != nil {
		return err
	}
	if !ok {
		return xerrors.Errorf("porep proof for sector %d was invalid", sid.ID.Number)
	}

	ok, err = ffiwrapper.ProofVerifier.VerifySeal(svi2)
	if err != nil {
		return err
	}
	if !ok {
		return xerrors.Errorf("porep proof for sector %d was invalid", sid.ID.Number)
	}

	proof, _, err := GenProofForWindowPost(sb, ctx, sid.ID.Miner, sealedSectors, challenge[:])

	wpvi := saproof2.WindowPoStVerifyInfo{
		Randomness:        challenge[:],
		Proofs:            proof,
		ChallengedSectors: sealedSectors,
		Prover:            sid.ID.Miner,
	}

	ok, err = ffiwrapper.ProofVerifier.VerifyWindowPoSt(ctx, wpvi)
	if err != nil {
		return err
	}
	if !ok {
		log.Error("window post verification failed")
	}

	return nil

}