package pipeline

import (
	"context"
	"fmt"
	"reflect"
	"strings"
	"testing"

	"gitea.maximumdirect.net/eric/notarius/internal/core/source"
	"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
)

type testEvidenceOutput struct {
	policy EvidenceContextPolicy
}

func (output testEvidenceOutput) Key() string { return "output" }
func (output testEvidenceOutput) Encode(context.Context, contracts.OutputRequest) (contracts.OutputResult, error) {
	return contracts.OutputResult{}, nil
}
func (output testEvidenceOutput) EvidenceContextPolicy() EvidenceContextPolicy {
	return cloneEvidenceContextPolicy(output.policy)
}

func TestEvidencePolicyResolutionAndPreparationSurviveLaneFiltering(t *testing.T) {
	registries, _ := constructionRegistries(t, nil, nil)
	registries.ArtifactEvidence = NewArtifactEvidenceRegistry()
	policy := EvidenceContextPolicy{Enabled: true, WindowUnits: 2, LaneIDs: []string{"artifact", "inactive"}}
	profileValidated := false
	registerTestEvidenceOutputWithProfileValidation(t, &registries, policy, func(context OutputProfileOptionContext, _ map[string]any) error {
		profileValidated = true
		if want := []string{"artifact", "inactive"}; !reflect.DeepEqual(context.LaneIDs, want) {
			return fmt.Errorf("configured lane ids = %#v, want %#v", context.LaneIDs, want)
		}
		return nil
	})
	if err := RegisterArtifactEvidence(registries.ArtifactEvidence, "test/notes", func(codecNotes) []source.SourceRef { return nil }); err != nil {
		t.Fatal(err)
	}
	profile := constructionProfile()
	profile.Artifacts["inactive"] = profile.Artifacts["artifact"]
	profile.Output.Options = map[string]any{"known": true}
	resolved, err := ResolvePipeline(profile, ResolveOptions{Only: []string{"artifact"}}, registries.catalog())
	if err != nil {
		t.Fatal(err)
	}
	if !profileValidated {
		t.Fatal("output profile options were not validated")
	}
	if lanes := resolved.AllArtifactLanes(); len(lanes) != 1 || lanes[0].ID != "artifact" {
		t.Fatalf("resolved lanes = %#v, want only the invocation-selected lane", lanes)
	}
	prepared, err := Prepare(resolved, registries, ModuleDependencies{})
	if err != nil {
		t.Fatalf("Prepare() error = %v, want nil", err)
	}
	if prepared.evidencePlan == nil || !reflect.DeepEqual(prepared.evidencePlan.policy.LaneIDs, []string{"artifact", "inactive"}) {
		t.Fatalf("evidence plan = %#v, want complete configured policy", prepared.evidencePlan)
	}
	if got := prepared.evidencePlan.lanes; len(got) != 1 || got[0].laneID != "artifact" {
		t.Fatalf("active evidence lanes = %#v, want artifact only", got)
	}
	prepared.evidencePlan.policy.LaneIDs[0] = "mutated"
	if policy := prepared.output.(EvidenceContextPolicyProvider).EvidenceContextPolicy(); policy.LaneIDs[0] != "artifact" {
		t.Fatalf("prepared plan mutated provider policy: %#v", policy)
	}
}

func TestPrepareEvidencePlanRejectsMissingAndMismatchedCapabilities(t *testing.T) {
	for _, test := range []struct {
		name      string
		configure func(*Registries)
		want      string
	}{
		{name: "missing registry", configure: func(registries *Registries) { registries.ArtifactEvidence = nil }, want: "artifact evidence registry"},
		{name: "unsupported kind", configure: func(registries *Registries) {}, want: "artifact evidence \"test/notes\" is not registered"},
		{name: "mismatched type", configure: func(registries *Registries) {
			if err := RegisterArtifactEvidence(registries.ArtifactEvidence, "test/notes", func(codecScore) []source.SourceRef { return nil }); err != nil {
				t.Fatal(err)
			}
		}, want: "requires Go type"},
	} {
		t.Run(test.name, func(t *testing.T) {
			registries, _ := constructionRegistries(t, nil, nil)
			registries.ArtifactEvidence = NewArtifactEvidenceRegistry()
			registerTestEvidenceOutput(t, &registries, EvidenceContextPolicy{Enabled: true, LaneIDs: []string{"artifact"}})
			test.configure(&registries)
			profile := constructionProfile()
			profile.Output.Options = map[string]any{"known": true}
			resolved, err := ResolvePipeline(profile, ResolveOptions{}, registries.catalog())
			if err != nil {
				t.Fatal(err)
			}
			_, err = Prepare(resolved, registries, ModuleDependencies{})
			if err == nil || !strings.Contains(err.Error(), test.want) {
				t.Fatalf("Prepare() error = %v, want %q", err, test.want)
			}
		})
	}
}

func registerTestEvidenceOutput(t *testing.T, registries *Registries, policy EvidenceContextPolicy) {
	t.Helper()
	registerTestEvidenceOutputWithProfileValidation(t, registries, policy, nil)
}

func registerTestEvidenceOutputWithProfileValidation(t *testing.T, registries *Registries, policy EvidenceContextPolicy, validateProfile OutputProfileOptionValidator) {
	t.Helper()
	registry := NewOutputEncoderRegistry()
	if err := registry.RegisterBuilderWithProfileValidation(testModuleSpec("output", StageOutput), func(options map[string]any) error {
		return RejectUnknownOptions(options, "known")
	}, validateProfile, func(BuildRequest) (contracts.OutputEncoder, error) {
		return testEvidenceOutput{policy: cloneEvidenceContextPolicy(policy)}, nil
	}); err != nil {
		t.Fatal(err)
	}
	registries.Outputs = registry
}
