// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package requirementsdata_test

import (
	"slices"
	"testing"

	"google.golang.org/protobuf/proto"

	"google3/third_party/android/mediapc_requirements/requirements"
	pb "cts/test/mediapc/requirements/requirements_go_proto"
	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"

	_ "embed"
)

// MPC Requirements data
//
//go:embed requirements_data.binarypb
var reqBinary []byte

func TestUniqueRequirementIDs(t *testing.T) {
	reqList := mustUnmarshalRequirementList(t)

	// Requirement ids must be unique
	ids := make(map[string]bool)
	for _, req := range reqList.GetRequirements() {
		id := req.GetId()
		if ids[id] {
			t.Errorf("requirement [%s] is a duplicate", id)
		}
		ids[id] = true
	}
}

func TestUniqueRequirementNames(t *testing.T) {
	reqList := mustUnmarshalRequirementList(t)

	// Requirement names must be unique
	nameToID := make(map[string]string) // name to id
	for _, req := range reqList.GetRequirements() {
		if req.HasName() {
			name := req.GetName()
			if nameToID[name] != "" {
				t.Errorf("the name %q of requirement [%s] is a duplicate of requirement [%s]'s name", req.GetId(), name, nameToID[name])
			}
			nameToID[name] = req.GetId()
		}
	}

}

func TestAllTestConfigsSpecifiedAndUsed(t *testing.T) {
	reqList := mustUnmarshalRequirementList(t)

	for _, req := range reqList.GetRequirements() {
		if !req.HasName() {
			continue // Do not check requirements that are not implemented yet
		}

		t.Run(req.GetName(), func(t *testing.T) {

			specifiedTestConfigs := []string{}
			for id := range req.GetTestConfigs() {
				specifiedTestConfigs = append(specifiedTestConfigs, id)
			}

			usedTestConfigs := []string{}
			for _, spec := range req.GetSpecs() {
				if !slices.Contains(usedTestConfigs, spec.GetTestConfigId()) {
					usedTestConfigs = append(usedTestConfigs, spec.GetTestConfigId())
				}
			}

			if diff := cmp.Diff(specifiedTestConfigs, usedTestConfigs, cmpopts.SortSlices(
				func(a, b string) bool { return a < b })); diff != "" {
				t.Errorf("Specified test configs do not match used test configs (-want +got):\n%s", diff)
			}
		})
	}
}

func TestConfigMeasurementsValid(t *testing.T) {
	reqList := mustUnmarshalRequirementList(t)

	for _, req := range reqList.GetRequirements() {
		if !req.HasName() {
			continue // Do not check requirements that are not implemented yet
		}

		t.Run(req.GetName(), func(t *testing.T) {
			for measurementName, measurement := range req.GetMeasurements() {
				if measurement.GetComparison() != pb.Comparison_COMPARISON_CONFIG {
					continue // Do not check measurements that are not config measurements
				}

				t.Run(measurementName, func(t *testing.T) {
					measurementValues := make(map[string]*pb.RequiredValue)
					for _, spec := range req.GetSpecs() {
						val, ok := measurementValues[spec.GetTestConfigId()]
						if !ok {
							measurementValues[spec.GetTestConfigId()] = spec.GetRequiredValues()[measurementName]
						} else if !proto.Equal(val, spec.GetRequiredValues()[measurementName]) {
							t.Errorf("Test config [%s] has multiple different values for measurement [%s]: [%v] and [%v]", spec.GetTestConfigId(), measurementName, spec.GetRequiredValues()[measurementName], val)
						}
					}
				})
			}

		})
	}
}

func TestConfigVariantsValid(t *testing.T) {
	reqList := mustUnmarshalRequirementList(t)

	for _, req := range reqList.GetRequirements() {
		if !req.HasName() {
			continue // Do not check requirements that are not implemented yet
		}

		t.Run(req.GetName(), func(t *testing.T) {
			for configID := range req.GetTestConfigs() {

				// Check that all test configs have the same variants
				t.Run(configID, func(t *testing.T) {
					specToVariants := make(map[int64][]string)
					for mpc, spec := range req.GetSpecs() {
						if spec.GetTestConfigId() == configID {
							specToVariants[mpc] = []string{}
							for variantID := range spec.GetVariantSpecs() {
								specToVariants[mpc] = append(specToVariants[mpc], variantID)
							}
						}
					}

					prev := []string{}
					for _, variants := range specToVariants {
						if len(prev) > 0 {
							if diff := cmp.Diff(prev, variants, cmpopts.SortSlices(
								func(a, b string) bool { return a < b })); diff != "" {
								t.Errorf("Test config [%s] missing variants (-want +got):\n%s", configID, diff)
							}
						}
						prev = variants
					}
				})
			}
		})
	}
}

func TestProtoFieldNumbersAreUniqueAndValid(t *testing.T) {
	reqList := mustUnmarshalRequirementList(t)

	usedReqNumbers := make(map[int32]bool)
	for _, req := range reqList.GetRequirements() {
		for testConfigID, testConfig := range req.GetTestConfigs() {
			if !testConfig.HasProtoFieldNumber() {
				continue
			}

			if usedReqNumbers[testConfig.GetProtoFieldNumber()] {
				t.Errorf("Test config [%s] has the same proto field number [%d] as another test config", testConfigID, testConfig.GetProtoFieldNumber())
			} else if testConfig.GetProtoFieldNumber() <= 0 {
				t.Errorf("Test config [%s] has an invalid proto field number [%d]", testConfigID, testConfig.GetProtoFieldNumber())
			} else {
				usedReqNumbers[testConfig.GetProtoFieldNumber()] = true
			}
		}

		t.Run(req.GetId(), func(t *testing.T) {
			usedMeasurementNumbers := make(map[int32]bool)

			for _, measurement := range req.GetMeasurements() {
				if !measurement.HasProtoFieldNumber() {
					continue
				}

				if usedMeasurementNumbers[measurement.GetProtoFieldNumber()] {
					t.Errorf("Measurement [%s] has the same proto field number [%d] as another measurement", measurement.GetId(), measurement.GetProtoFieldNumber())
				} else if measurement.GetProtoFieldNumber() <= 2 {
					t.Errorf("Measurement [%s] has an invalid proto field number [%d]", measurement.GetId(), measurement.GetProtoFieldNumber())
				} else {
					usedMeasurementNumbers[measurement.GetProtoFieldNumber()] = true
				}
			}
		})
	}
}

func mustUnmarshalRequirementList(t *testing.T) *pb.RequirementList {
	t.Helper()
	reqList, err := requirements.UnmarshalRequirementList(reqBinary)
	if err != nil {
		t.Fatalf("failed to unmarshal reqBinary: %v", err)
	}
	return reqList
}
