package pkg

import (
	"fmt"

	"github.com/linkerd/linkerd2/pkg/k8s"
	metricsPb "github.com/linkerd/linkerd2/viz/metrics-api/gen/viz"
	"github.com/linkerd/linkerd2/viz/pkg/util"
	tapPb "github.com/linkerd/linkerd2/viz/tap/gen/tap"
)

// ValidTapDestinations specifies resource types allowed as a tap destination:
// - destination resource on an outbound 'to' query
var ValidTapDestinations = []string{
	k8s.CronJob,
	k8s.DaemonSet,
	k8s.Deployment,
	k8s.Job,
	k8s.Namespace,
	k8s.Pod,
	k8s.ReplicaSet,
	k8s.ReplicationController,
	k8s.Service,
	k8s.StatefulSet,
}

// TapRequestParams contains parameters that are used to build a
// TapByResourceRequest.
type TapRequestParams struct {
	Resource      string
	Namespace     string
	ToResource    string
	ToNamespace   string
	MaxRps        float32
	Scheme        string
	Method        string
	Authority     string
	Path          string
	Extract       bool
	LabelSelector string
}

// BuildTapByResourceRequest builds a Public API TapByResourceRequest from a
// TapRequestParams.
func BuildTapByResourceRequest(params TapRequestParams) (*tapPb.TapByResourceRequest, error) {
	target, err := util.BuildResource(params.Namespace, params.Resource)
	if err != nil {
		return nil, fmt.Errorf("target resource invalid: %w", err)
	}
	if !contains(util.ValidTargets, target.Type) {
		return nil, fmt.Errorf("unsupported resource type [%s]", target.Type)
	}

	matches := []*tapPb.TapByResourceRequest_Match{}

	if params.ToResource != "" {
		destination, err := util.BuildResource(params.ToNamespace, params.ToResource)
		if err != nil {
			return nil, fmt.Errorf("destination resource invalid: %w", err)
		}
		if !contains(ValidTapDestinations, destination.Type) {
			return nil, fmt.Errorf("unsupported resource type [%s]", destination.Type)
		}

		match := tapPb.TapByResourceRequest_Match{
			Match: &tapPb.TapByResourceRequest_Match_Destinations{
				Destinations: &metricsPb.ResourceSelection{
					Resource: destination,
				},
			},
		}
		matches = append(matches, &match)
	}

	if params.Scheme != "" {
		match := buildMatchHTTP(&tapPb.TapByResourceRequest_Match_Http{
			Match: &tapPb.TapByResourceRequest_Match_Http_Scheme{Scheme: params.Scheme},
		})
		matches = append(matches, &match)
	}
	if params.Method != "" {
		match := buildMatchHTTP(&tapPb.TapByResourceRequest_Match_Http{
			Match: &tapPb.TapByResourceRequest_Match_Http_Method{Method: params.Method},
		})
		matches = append(matches, &match)
	}
	if params.Authority != "" {
		match := buildMatchHTTP(&tapPb.TapByResourceRequest_Match_Http{
			Match: &tapPb.TapByResourceRequest_Match_Http_Authority{Authority: params.Authority},
		})
		matches = append(matches, &match)
	}
	if params.Path != "" {
		match := buildMatchHTTP(&tapPb.TapByResourceRequest_Match_Http{
			Match: &tapPb.TapByResourceRequest_Match_Http_Path{Path: params.Path},
		})
		matches = append(matches, &match)
	}

	extract := &tapPb.TapByResourceRequest_Extract{}
	if params.Extract {
		extract = buildExtractHTTP(&tapPb.TapByResourceRequest_Extract_Http{
			Extract: &tapPb.TapByResourceRequest_Extract_Http_Headers_{
				Headers: &tapPb.TapByResourceRequest_Extract_Http_Headers{},
			},
		})
	}

	return &tapPb.TapByResourceRequest{
		Target: &metricsPb.ResourceSelection{
			Resource:      target,
			LabelSelector: params.LabelSelector,
		},
		MaxRps: params.MaxRps,
		Match: &tapPb.TapByResourceRequest_Match{
			Match: &tapPb.TapByResourceRequest_Match_All{
				All: &tapPb.TapByResourceRequest_Match_Seq{
					Matches: matches,
				},
			},
		},
		Extract: extract,
	}, nil
}

func buildMatchHTTP(match *tapPb.TapByResourceRequest_Match_Http) tapPb.TapByResourceRequest_Match {
	return tapPb.TapByResourceRequest_Match{
		Match: &tapPb.TapByResourceRequest_Match_Http_{
			Http: match,
		},
	}
}

func buildExtractHTTP(extract *tapPb.TapByResourceRequest_Extract_Http) *tapPb.TapByResourceRequest_Extract {
	return &tapPb.TapByResourceRequest_Extract{
		Extract: &tapPb.TapByResourceRequest_Extract_Http_{
			Http: extract,
		},
	}
}

func contains(list []string, s string) bool {
	for _, elem := range list {
		if s == elem {
			return true
		}
	}
	return false
}
