/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package cauthdsl

import (
	"fmt"

	"github.com/golang/protobuf/proto"
	cb "github.com/hyperledger/fabric-protos-go/common"
	"github.com/hyperledger/fabric/common/policies"
	"github.com/hyperledger/fabric/msp"
	"github.com/hyperledger/fabric/protoutil"
	"github.com/pkg/errors"
)

type provider struct {
	deserializer msp.IdentityDeserializer
}

// NewPolicyProvider provides a policy generator for cauthdsl type policies
func NewPolicyProvider(deserializer msp.IdentityDeserializer) policies.Provider {
	return &provider{
		deserializer: deserializer,
	}
}

// NewPolicy creates a new policy based on the policy bytes
func (pr *provider) NewPolicy(data []byte) (policies.Policy, proto.Message, error) {
	sigPolicy := &cb.SignaturePolicyEnvelope{}
	if err := proto.Unmarshal(data, sigPolicy); err != nil {
		return nil, nil, fmt.Errorf("Error unmarshaling to SignaturePolicy: %s", err)
	}

	if sigPolicy.Version != 0 {
		return nil, nil, fmt.Errorf("This evaluator only understands messages of version 0, but version was %d", sigPolicy.Version)
	}

	compiled, err := compile(sigPolicy.Rule, sigPolicy.Identities)
	if err != nil {
		return nil, nil, err
	}

	return &policy{
		evaluator:               compiled,
		deserializer:            pr.deserializer,
		signaturePolicyEnvelope: sigPolicy,
	}, sigPolicy, nil

}

// EnvelopeBasedPolicyProvider allows to create a new policy from SignaturePolicyEnvelope struct instead of []byte
type EnvelopeBasedPolicyProvider struct {
	Deserializer msp.IdentityDeserializer
}

// NewPolicy creates a new policy from the policy envelope
func (pp *EnvelopeBasedPolicyProvider) NewPolicy(sigPolicy *cb.SignaturePolicyEnvelope) (policies.Policy, error) {
	if sigPolicy == nil {
		return nil, errors.New("invalid arguments")
	}

	compiled, err := compile(sigPolicy.Rule, sigPolicy.Identities)
	if err != nil {
		return nil, err
	}

	return &policy{
		evaluator:               compiled,
		deserializer:            pp.Deserializer,
		signaturePolicyEnvelope: sigPolicy,
	}, nil
}

type policy struct {
	signaturePolicyEnvelope *cb.SignaturePolicyEnvelope
	evaluator               func([]msp.Identity, []bool) bool
	deserializer            msp.IdentityDeserializer
}

// EvaluateSignedData takes a set of SignedData and evaluates whether
// 1) the signatures are valid over the related message
// 2) the signing identities satisfy the policy
func (p *policy) EvaluateSignedData(signatureSet []*protoutil.SignedData) error {
	if p == nil {
		return errors.New("no such policy")
	}

	ids := policies.SignatureSetToValidIdentities(signatureSet, p.deserializer)

	return p.EvaluateIdentities(ids)
}

// EvaluateIdentities takes an array of identities and evaluates whether
// they satisfy the policy
func (p *policy) EvaluateIdentities(identities []msp.Identity) error {
	if p == nil {
		return fmt.Errorf("No such policy")
	}

	ok := p.evaluator(identities, make([]bool, len(identities)))
	if !ok {
		return errors.New("signature set did not satisfy policy")
	}
	return nil
}

func (p *policy) Convert() (*cb.SignaturePolicyEnvelope, error) {
	if p.signaturePolicyEnvelope == nil {
		return nil, errors.New("nil policy field")
	}

	return p.signaturePolicyEnvelope, nil
}
