package seal

import(
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"math/rand"
	"time"

	"golang.org/x/xerrors"
	"github.com/mitchellh/go-homedir"
	"github.com/minio/blake2b-simd"
	"github.com/filecoin-project/go-state-types/abi"
	
	spproof "fil_integrate/build/proof"
	"fil_integrate/build"
	"fil_integrate/build/storage"
	// "fil_integrate/extern/sector-storage/ffiwrapper"
	"fil_integrate/seal/basicfs"
)

func TestAggregateWindowPoSt(
	sectorSize abi.SectorSize,
	numSectors int,
	numAggregate int,
) error {
	sdir, err := homedir.Expand("~/tmp/bench")
	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 && !os.IsExist(err){
		return err
	}
	sbfs := &basicfs.Provider{
			Root: tsdir,
		}
	sb ,err := New(sbfs)
	if err != nil{
		return err
	}

	file := rand.New(rand.NewSource(1587))
	trand := blake2b.Sum256([]byte("ticket-preimage"))
	ticket := abi.SealRandomness(trand[:])
	ctx := context.TODO()
	var challenge [32]byte
	rand.Read(challenge[:])

	var randomnesses []abi.PoStRandomness
	var sealedSectorsinfo [][]spproof.SectorInfo
	var sectorCount []uint
	var proofs []spproof.PoStProof
	sealProofType := spt(sectorSize)

	start := time.Now()
	for i := 0; i < numAggregate; i++{
		for j := 0; j < numSectors; j++{
			var pieces []abi.PieceInfo

			sid := storage.SectorRef{
				ID: abi.SectorID{
					Miner:  1000,
					Number: abi.SectorNumber(i*numSectors+j),
				},
				ProofType: sealProofType,
			}

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

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

			cids, err := sb.SealPreCommit2(ctx, sid, pc1out)
			if err != nil {
				return xerrors.Errorf("commit: %w", err)
			}

			comm_r := cids.Sealed
			sb.PutCommRIntoDir(sid.ID, comm_r)
		}
	}
	log.Infof("Sealed [%d] Sectors Done", numSectors*numAggregate)
	sealed := time.Now()

	for i := 0; i < numAggregate; i++{
		var sealedSectors []spproof.SectorInfo
		for j := 0; j < numSectors; j++{
			sectorID := abi.SectorID{
				Miner:  1000,
				Number: abi.SectorNumber(i*numSectors+j),
			}
			comm_r, err := sb.GetCommRFromDir(sectorID)
			if err != nil {
				return err
			}
			sealedSectors = append(sealedSectors, spproof.SectorInfo{
				SealedCID:    comm_r,
				SectorNumber: sectorID.Number,
				SealProof:    sealProofType,
			})
		}
		sealedSectorsinfo = append(sealedSectorsinfo, sealedSectors)
	}
	log.Infof("Read [%d] Commitment Rplication Done", numSectors*numAggregate)
	loadCommr := time.Now()

	for i := 0; i < numAggregate; i++{
		log.Infof("[%d] Generating Window-Post", i)
		proof, _, err := sb.GenProofForWindowPoSt(ctx, 1000, sealedSectorsinfo[i], challenge[:])
		if err != nil {
			return err
		}

		proofs = append(proofs, proof...)
		randomnesses = append(randomnesses, challenge[:])
		sectorCount = append(sectorCount, uint(numSectors))
	}
	log.Infof("Generate [%d] Window-PoSt Done", numAggregate)
	genWindowPoSt := time.Now()

	aggregateProof1, err := sb.AggregateWindowPoStProofs(spproof.AggregateWindowPostInfos{
			AggregateType: DefaultAggregationType(),
			Randomnesses:  randomnesses, 
			SectorCount:   sectorCount,
		}, proofs)
	if err != nil {
		return err
	}

	aggregateProofsCold := time.Now()

	aggregateProof2, err := sb.AggregateWindowPoStProofs(spproof.AggregateWindowPostInfos{
			AggregateType: DefaultAggregationType(),
			Randomnesses:  randomnesses, 
			SectorCount:   sectorCount,
		}, proofs)
	if err != nil {
		return err
	}
	aggregateProofsHot := time.Now()

	poStType, _ := sealProofType.RegisteredWindowPoStProof()
	svi1 := spproof.AggregateWindowPostInfos{
			PoStType:         poStType,
			AggregateType:    DefaultAggregationType(),
			Miner:            abi.ActorID(1000),
			AggregationProof: aggregateProof1,
			Randomnesses:     randomnesses,      
		}
	svi2 := spproof.AggregateWindowPostInfos{
			PoStType:         poStType,
			AggregateType:    DefaultAggregationType(),
			Miner:            abi.ActorID(1000),
			AggregationProof: aggregateProof2,
			Randomnesses:     randomnesses,      
		}

	ok, err := ProofVerifier.VerifyAggregateWindowPostProofs(svi1, sealedSectorsinfo)
	if err != nil {
		return err
	}
	if ok {
		fmt.Println("Aggregated proof is true")
	} else{
		fmt.Println("Aggregated proof is false")
	}
	verifyProofsCold := time.Now()

	ok, err = ProofVerifier.VerifyAggregateWindowPostProofs(svi2, sealedSectorsinfo)
	if err != nil {
		return err
	}
	if ok {
		fmt.Println("Aggregated proof is true")
	} else{
		fmt.Println("Aggregated proof is false")
	}
	verifyProofsHot := time.Now()

	fmt.Printf("Seal %d sectors using %s\n", numSectors*numAggregate, sealed.Sub(start))
	fmt.Printf("Read %d comm_r using %s\n", numAggregate*numSectors, loadCommr.Sub(sealed))
	fmt.Printf("Generate %d window-post using %s\n", numAggregate, genWindowPoSt.Sub(loadCommr))
	fmt.Printf("Aggregate %d window-post Proofs(cold) using %s\n", numAggregate, aggregateProofsCold.Sub(genWindowPoSt))
	fmt.Printf("Aggregate %d window-post Proofs(hot) using %s\n", numAggregate, aggregateProofsHot.Sub(aggregateProofsCold))
	fmt.Printf("Verify Aggregation Window-PoSt Proofs(cold) using %s\n", verifyProofsCold.Sub(aggregateProofsHot))
	fmt.Printf("Verify Aggregation Window-PoSt Proofs(hot) using %s\n", verifyProofsHot.Sub(verifyProofsCold))

	return nil
}

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 := 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 []spproof.SectorInfo

	piece, err := sb.AddPiece(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 = sb.AddPiece(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, err := sb.Sealed(ctx, sid, seed, ticket, pieces)
	if err != nil {
		return err
	}

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

	proof2, err := sb.GenProofForC2(ctx, sid, seed, ticket, pieces, cids)
	if err != nil {
		return err
	}

	ok, err := sb.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 := spproof.SealVerifyInfo{
		SectorID:              sid.ID,
		SealedCID:             cids.Sealed,
		SealProof:             sid.ProofType,
		Proof:                 proof1,
		DealIDs:               nil,
		Randomness:            ticket,
		InteractiveRandomness: seed,
		UnsealedCID:           cids.Unsealed,
	}

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

	ok, err = 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 = 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 := sb.GenProofForWindowPoSt(ctx, sid.ID.Miner, sealedSectors, challenge[:])

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

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

	return nil

}

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

	return spt
}