/*
SPDX-License-Identifier: Apache-2.0
*/

package auction

import (
	"encoding/base64"
	"fmt"

	"github.com/golang/protobuf/proto"
	"github.com/hyperledger/fabric-chaincode-go/shim"
	"github.com/hyperledger/fabric-contract-api-go/contractapi"
	"github.com/hyperledger/fabric-protos-go/common"
	"github.com/hyperledger/fabric-protos-go/msp"
)

func (s *SmartContract) GetSubmittingClientIdentity(ctx contractapi.TransactionContextInterface) (string, error) {

	b64ID, err := ctx.GetClientIdentity().GetID()
	if err != nil {
		return "", fmt.Errorf("Failed to read clientID: %v", err)
	}
	decodeID, err := base64.StdEncoding.DecodeString(b64ID)
	if err != nil {
		return "", fmt.Errorf("failed to base64 decode clientID: %v", err)
	}
	return string(decodeID), nil
}

// getCollectionName is an internal helper function to get collection of submitting client identity.
func getCollectionName(ctx contractapi.TransactionContextInterface) (string, error) {

	// Get the MSP ID of submitting client identity
	clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
	if err != nil {
		return "", fmt.Errorf("failed to get verified MSPID: %v", err)
	}

	// Create the collection name
	orgCollection := "_implicit_org_" + clientMSPID

	return orgCollection, nil
}

// verifyClientOrgMatchesPeerOrg is an internal function used to verify that client org id matches peer org id.
func verifyClientOrgMatchesPeerOrg(ctx contractapi.TransactionContextInterface) error {

	clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
	if err != nil {
		return fmt.Errorf("failed getting the client's MSPID: %v", err)
	}
	peerMSPID, err := shim.GetMSPID()
	if err != nil {
		return fmt.Errorf("failed getting the peer's MSPID: %v", err)
	}

	if clientMSPID != peerMSPID {
		return fmt.Errorf("client from org %v is not authorized to read or write private data from an org %v peer", clientMSPID, peerMSPID)
	}

	return nil
}

func contains(sli []string, str string) bool {
	for _, a := range sli {
		if a == str {
			return true
		}
	}
	return false
}

func setAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface, assetId string, mspids []string, auditor bool) error {

	principals := make([]*msp.MSPPrincipal, len(mspids))
	participantSigsPolicy := make([]*common.SignaturePolicy, len(mspids))

	for i, id := range mspids {
		principal, err := proto.Marshal(
			&msp.MSPRole{
				Role:          msp.MSPRole_PEER,
				MspIdentifier: id,
			},
		)
		if err != nil {
			return err
		}
		principals[i] = &msp.MSPPrincipal{
			PrincipalClassification: msp.MSPPrincipal_ROLE,
			Principal:               principal,
		}
		participantSigsPolicy[i] = &common.SignaturePolicy{
			Type: &common.SignaturePolicy_SignedBy{
				SignedBy: int32(i),
			},
		}
	}

	if auditor == false {
		// create the defalt policy for an auction without an auditor

		policy := &common.SignaturePolicyEnvelope{
			Version: 0,
			Rule: &common.SignaturePolicy{
				Type: &common.SignaturePolicy_NOutOf_{
					NOutOf: &common.SignaturePolicy_NOutOf{
						N:     int32(len(mspids)),
						Rules: participantSigsPolicy,
					},
				},
			},
			Identities: principals,
		}

		spBytes, err := proto.Marshal(policy)
		if err != nil {
			return err
		}
		err = ctx.GetStub().SetStateValidationParameter(assetId, spBytes)
		if err != nil {
			return fmt.Errorf("failed to set validation parameter on auction: %v", err)
		}
	} else {

		// create the defalt policy for an auction with an auditor

		// create the auditor identity and signature policy
		auditorMSP, err := proto.Marshal(
			&msp.MSPRole{
				Role:          msp.MSPRole_PEER,
				MspIdentifier: "Org3MSP",
			},
		)
		if err != nil {
			return err
		}
		principals = append(principals, &msp.MSPPrincipal{
			PrincipalClassification: msp.MSPPrincipal_ROLE,
			Principal:               auditorMSP,
		},
		)
		// Create the policies in case the auditor is needed. In this case, an
		// auditor and 1 participant can update the auction.
		auditorPolicies := make([]*common.SignaturePolicy, 2)
		auditorPolicies[0] = &common.SignaturePolicy{
			Type: &common.SignaturePolicy_SignedBy{
				SignedBy: int32(len(principals) - 1),
			},
		}
		auditorPolicies[1] = &common.SignaturePolicy{
			Type: &common.SignaturePolicy_NOutOf_{
				NOutOf: &common.SignaturePolicy_NOutOf{
					N:     1,
					Rules: participantSigsPolicy,
				},
			},
		}

		// For two organizations, the auditor policy below is equivilent to
		// AND(auditor, OR(Org1, Org2))
		policies := make([]*common.SignaturePolicy, 2)
		policies[0] = &common.SignaturePolicy{
			Type: &common.SignaturePolicy_NOutOf_{
				NOutOf: &common.SignaturePolicy_NOutOf{
					N:     2,
					Rules: auditorPolicies,
				},
			},
		}
		// Participants can also update the auction without an auditor
		policies[1] = &common.SignaturePolicy{
			Type: &common.SignaturePolicy_NOutOf_{
				NOutOf: &common.SignaturePolicy_NOutOf{
					N:     int32(len(mspids)),
					Rules: participantSigsPolicy,
				},
			},
		}
		// Either the auditor policy or the participant policy can update
		// the auction. For example, for two organizations, the full policy would be
		// equivilent to OR(AND(Org1, Org2),AND(auditor, OR(Org1, Org2)))
		policy := &common.SignaturePolicyEnvelope{
			Version: 0,
			Rule: &common.SignaturePolicy{
				Type: &common.SignaturePolicy_NOutOf_{
					NOutOf: &common.SignaturePolicy_NOutOf{
						N:     1,
						Rules: policies,
					},
				},
			},
			Identities: principals,
		}
		spBytes, err := proto.Marshal(policy)
		if err != nil {
			return err
		}
		err = ctx.GetStub().SetStateValidationParameter(assetId, spBytes)
		if err != nil {
			return fmt.Errorf("failed to set validation parameter on auction: %v", err)
		}
	}
	return nil
}
