package auth

import (
	"fmt"
	"reflect"

	"github.com/go-logr/logr"
	client "github.com/kyverno/kyverno/pkg/dclient"
	authorizationv1 "k8s.io/api/authorization/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
)

//CanIOptions provides utility to check if user has authorization for the given operation
type CanIOptions struct {
	namespace string
	verb      string
	kind      string
	client    *client.Client
	log       logr.Logger
}

//NewCanI returns a new instance of operation access controller evaluator
func NewCanI(client *client.Client, kind, namespace, verb string, log logr.Logger) *CanIOptions {
	o := CanIOptions{
		client: client,
		log:    log,
	}

	o.namespace = namespace
	o.kind = kind
	o.verb = verb

	return &o
}

//RunAccessCheck checks if the caller can perform the operation
// - operation is a combination of namespace, kind, verb
// - can only evaluate a single verb
// - group version resource is determined from the kind using the discovery client REST mapper
// - If disallowed, the reason and evaluationError is available in the logs
// - each can generates a SelfSubjectAccessReview resource and response is evaluated for permissions
func (o *CanIOptions) RunAccessCheck() (bool, error) {
	// get GroupVersionResource from RESTMapper
	// get GVR from kind
	gvr, err := o.client.DiscoveryClient.GetGVRFromKind(o.kind)
	if err != nil {
		return false, fmt.Errorf("failed to get GVR for kind %s", o.kind)
	}

	if reflect.DeepEqual(gvr, schema.GroupVersionResource{}) {
		// cannot find GVR
		return false, fmt.Errorf("failed to get the Group Version Resource for kind %s", o.kind)
	}

	sar := &authorizationv1.SelfSubjectAccessReview{
		Spec: authorizationv1.SelfSubjectAccessReviewSpec{
			ResourceAttributes: &authorizationv1.ResourceAttributes{
				Namespace: o.namespace,
				Verb:      o.verb,
				Group:     gvr.Group,
				Resource:  gvr.Resource,
			},
		},
	}
	// Set self subject access review
	// - namespace
	// - verb
	// - resource
	// - subresource
	logger := o.log.WithValues("kind", sar.Kind, "namespace", sar.Namespace, "name", sar.Name)

	// Create the Resource
	resp, err := o.client.CreateResource("", "SelfSubjectAccessReview", "", sar, false)
	if err != nil {
		logger.Error(err, "failed to create resource")
		return false, err
	}

	// status.allowed
	allowed, ok, err := unstructured.NestedBool(resp.Object, "status", "allowed")
	if !ok {
		if err != nil {
			logger.Error(err, "failed to get the field", "field", "status.allowed")
		}
		logger.Info("field not found", "field", "status.allowed")
	}

	if !allowed {
		// status.reason
		reason, ok, err := unstructured.NestedString(resp.Object, "status", "reason")
		if !ok {
			if err != nil {
				logger.Error(err, "failed to get the field", "field", "status.reason")
			}
			logger.Info("field not found", "field", "status.reason")
		}
		// status.evaluationError
		evaluationError, ok, err := unstructured.NestedString(resp.Object, "status", "evaluationError")
		if !ok {
			if err != nil {
				logger.Error(err, "failed to get the field", "field", "status.evaluationError")
			}
			logger.Info("field not found", "field", "status.evaluationError")
		}

		// Reporting ? (just logs)
		logger.Info("disallowed operation", "reason", reason, "evaluationError", evaluationError)
	}

	return allowed, nil
}
