// Copyright 2024 Google Inc. All rights reserved.
//
// 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 android

import (
	"android/soong/android/team_proto"
	"log"
	"testing"

	"google.golang.org/protobuf/proto"
)

func TestAllTeams(t *testing.T) {
	t.Parallel()
	ctx := GroupFixturePreparers(
		prepareForTestWithTeamAndFakes,
		// This adds two variants, one armv7-a-neon, one armv8-a
		PrepareForTestWithArchMutator,
		FixtureRegisterWithContext(func(ctx RegistrationContext) {
			ctx.RegisterParallelSingletonType("all_teams", AllTeamsFactory)
		}),
	).RunTestWithBp(t, `
		fake {
			name: "main_test",
			team: "someteam",
		}
		team {
			name: "someteam",
			trendy_team_id: "cool_team",
		}

		team {
			name: "team2",
			trendy_team_id: "22222",
		}

		fake {
			name: "tool",
			team: "team2",
		}

		fake {
			name: "noteam",
                        test_only: true,
		}
                // write the test-only provider value once
		fake {
                        name: "test-and-team-and-top1",
                        test_only: true,
                        team: "team2",
                        arch: {arm: { skip: false},
                               arm64: { skip: true}},
		}
                // write the test-only provider once, but on the other arch
		fake {
                        name: "test-and-team-and-top2",
                        test_only: true,
                        team: "team2",
                        arch: {arm: { skip: true},
                               arm64: { skip: false}},
		}
                // write the test-only provider value twice
		fake {
                        name: "test-and-team-and-top3",
                        test_only: true,
                        team: "team2",
		}
                // Don't write the test-only provider value
		fake {
                        name: "test-and-team-and-top4",
                        test_only: true,
                        team: "team2",
                        arch: {arm: { skip: true},
                               arm64: { skip: true}},
		}
	`)

	var teams *team_proto.AllTeams
	teams = getTeamProtoOutput(t, ctx)

	// map of module name -> trendy team name.
	actualTeams := make(map[string]string)
	actualTests := []string{}
	actualTopLevelTests := []string{}

	for _, teamProto := range teams.Teams {
		if teamProto.TrendyTeamId != nil {
			actualTeams[teamProto.GetTargetName()] = *teamProto.TrendyTeamId
		} else {
			actualTeams[teamProto.GetTargetName()] = ""
		}
		if teamProto.GetTestOnly() {
			actualTests = append(actualTests, teamProto.GetTargetName())
		}
		if teamProto.GetTopLevelTarget() {
			actualTopLevelTests = append(actualTopLevelTests, teamProto.GetTargetName())
		}
	}
	expectedTeams := map[string]string{
		"main_test":              "cool_team",
		"tool":                   "22222",
		"test-and-team-and-top1": "22222",
		"test-and-team-and-top2": "22222",
		"test-and-team-and-top3": "22222",
		"test-and-team-and-top4": "22222",
		"noteam":                 "",
	}

	expectedTests := []string{
		"noteam",
		"test-and-team-and-top1",
		"test-and-team-and-top2",
		"test-and-team-and-top3",
		// There should be no test-and-team-top4 as we skip writing all variants
		// test-only for all variants
	}
	AssertDeepEquals(t, "compare maps", expectedTeams, actualTeams)
	AssertDeepEquals(t, "test matchup", expectedTests, actualTests)
}

func getTeamProtoOutput(t *testing.T, ctx *TestResult) *team_proto.AllTeams {
	teams := new(team_proto.AllTeams)
	config := ctx.SingletonForTests("all_teams")
	allOutputs := config.AllOutputs()

	protoPath := allOutputs[0]

	out := config.MaybeOutput(protoPath)
	outProto := []byte(ContentFromFileRuleForTests(t, ctx.TestContext, out))
	if err := proto.Unmarshal(outProto, teams); err != nil {
		log.Fatalln("Failed to parse teams proto:", err)
	}
	return teams
}

// Android.bp
//
//	team: team_top
//
// # dir1 has no modules with teams,
// # but has a dir with no Android.bp
// dir1/Android.bp
//
//	module_dir1
//
// # dirs without and Android.bp should be fine.
// dir1/dir2/dir3/Android.bp
//
//	package {}
//	module_dir123
//
// teams_dir/Android.bp
//
//	module_with_team1: team1
//	team1: 111
//
// # team comes from upper package default
// teams_dir/deeper/Android.bp
//
//	module2_with_team1: team1
//
// package_defaults/Android.bp
// package_defaults/pd2/Android.bp
//
//	package{ default_team: team_top}
//	module_pd2   ## should get team_top
//
// package_defaults/pd2/pd3/Android.bp
//
//	module_pd3  ## should get team_top
func TestPackageLookup(t *testing.T) {
	t.Parallel()
	rootBp := `
		team {
			name: "team_top",
			trendy_team_id: "trendy://team_top",
		} `

	dir1Bp := `
		fake {
			name: "module_dir1",
		} `
	dir3Bp := `
                package {}
		fake {
			name: "module_dir123",
		} `
	teamsDirBp := `
		fake {
			name: "module_with_team1",
                        team: "team1"

		}
		team {
			name: "team1",
			trendy_team_id: "111",
		} `
	teamsDirDeeper := `
		fake {
			name: "module2_with_team1",
                        team: "team1"
		} `
	// create an empty one.
	packageDefaultsBp := ""
	packageDefaultspd2 := `
                package { default_team: "team_top"}
		fake {
			name: "modulepd2",
		} `

	packageDefaultspd3 := `
		fake {
			name: "modulepd3",
		}
		fake {
			name: "modulepd3b",
			team: "team1"
		} `

	ctx := GroupFixturePreparers(
		prepareForTestWithTeamAndFakes,
		PrepareForTestWithPackageModule,
		FixtureRegisterWithContext(func(ctx RegistrationContext) {
			ctx.RegisterParallelSingletonType("all_teams", AllTeamsFactory)
		}),
		FixtureAddTextFile("Android.bp", rootBp),
		FixtureAddTextFile("dir1/Android.bp", dir1Bp),
		FixtureAddTextFile("dir1/dir2/dir3/Android.bp", dir3Bp),
		FixtureAddTextFile("teams_dir/Android.bp", teamsDirBp),
		FixtureAddTextFile("teams_dir/deeper/Android.bp", teamsDirDeeper),
		FixtureAddTextFile("package_defaults/Android.bp", packageDefaultsBp),
		FixtureAddTextFile("package_defaults/pd2/Android.bp", packageDefaultspd2),
		FixtureAddTextFile("package_defaults/pd2/pd3/Android.bp", packageDefaultspd3),
	).RunTest(t)

	var teams *team_proto.AllTeams
	teams = getTeamProtoOutput(t, ctx)

	// map of module name -> trendy team name.
	actualTeams := make(map[string]*string)
	for _, teamProto := range teams.Teams {
		actualTeams[teamProto.GetTargetName()] = teamProto.TrendyTeamId
	}
	expectedTeams := map[string]*string{
		"module_with_team1":  proto.String("111"),
		"module2_with_team1": proto.String("111"),
		"modulepd2":          proto.String("trendy://team_top"),
		"modulepd3":          proto.String("trendy://team_top"),
		"modulepd3b":         proto.String("111"),
		"module_dir1":        nil,
		"module_dir123":      nil,
	}
	AssertDeepEquals(t, "compare maps", expectedTeams, actualTeams)
}

func TestPackageLookupForIncludedBlueprintFiles(t *testing.T) {
	t.Parallel()
	rootBp := `
                package { default_team: "team_top"}
		team {
			name: "team_top",
			trendy_team_id: "trendy://team_top",
		}
                build = ["include.bp"]
 `
	includeBp := `
		fake {
			name: "IncludedModule",
		} `

	ctx := GroupFixturePreparers(
		prepareForTestWithTeamAndFakes,
		PrepareForTestWithPackageModule,
		FixtureRegisterWithContext(func(ctx RegistrationContext) {
			ctx.RegisterParallelSingletonType("all_teams", AllTeamsFactory)
		}),
		FixtureAddTextFile("Android.bp", rootBp),
		FixtureAddTextFile("include.bp", includeBp),
	).RunTest(t)

	var teams *team_proto.AllTeams
	teams = getTeamProtoOutput(t, ctx)

	// map of module name -> trendy team name.
	actualTeams := make(map[string]*string)
	for _, teamProto := range teams.Teams {
		actualTeams[teamProto.GetTargetName()] = teamProto.TrendyTeamId
	}
	expectedTeams := map[string]*string{
		"IncludedModule": proto.String("trendy://team_top"),
	}
	AssertDeepEquals(t, "compare maps", expectedTeams, actualTeams)
}

func TestPackageLookupForIncludedBlueprintFilesWithPackageInChildBlueprint(t *testing.T) {
	t.Parallel()
	rootBp := `
		team {
			name: "team_top",
			trendy_team_id: "trendy://team_top",
		}
                build = ["include.bp"]
 `
	includeBp := `
                package { default_team: "team_top"}
		fake {
			name: "IncludedModule",
		} `

	ctx := GroupFixturePreparers(
		prepareForTestWithTeamAndFakes,
		PrepareForTestWithPackageModule,
		FixtureRegisterWithContext(func(ctx RegistrationContext) {
			ctx.RegisterParallelSingletonType("all_teams", AllTeamsFactory)
		}),
		FixtureAddTextFile("Android.bp", rootBp),
		FixtureAddTextFile("include.bp", includeBp),
	).RunTest(t)

	var teams *team_proto.AllTeams
	teams = getTeamProtoOutput(t, ctx)

	// map of module name -> trendy team name.
	actualTeams := make(map[string]*string)
	for _, teamProto := range teams.Teams {
		actualTeams[teamProto.GetTargetName()] = teamProto.TrendyTeamId
	}
	expectedTeams := map[string]*string{
		"IncludedModule": proto.String("trendy://team_top"),
	}
	AssertDeepEquals(t, "compare maps", expectedTeams, actualTeams)
}

type fakeForTests struct {
	ModuleBase

	sourceProperties SourceProperties
	props            struct {
		// If true, don't write test-only value in provider
		Skip bool `android:"arch_variant"`
	}
}

func fakeFactory() Module {
	module := &fakeForTests{}
	module.AddProperties(&module.sourceProperties, &module.props)
	InitAndroidArchModule(module, HostAndDeviceSupported, MultilibBoth)

	return module
}

var prepareForTestWithTeamAndFakes = GroupFixturePreparers(
	FixtureRegisterWithContext(RegisterTeamBuildComponents),
	FixtureRegisterWithContext(func(ctx RegistrationContext) {
		ctx.RegisterModuleType("fake", fakeFactory)
	}),
)

func (f *fakeForTests) GenerateAndroidBuildActions(ctx ModuleContext) {
	if Bool(f.sourceProperties.Test_only) {
		SetProvider(ctx, TestOnlyProviderKey, TestModuleInformation{
			TestOnly:       Bool(f.sourceProperties.Test_only) && !f.props.Skip,
			TopLevelTarget: false,
		})
	}
}
