package metadata

import (
	"context"
	"errors"
	"strings"
	"testing"

	"github.com/nexuscrm/shared/pkg/models"
)

// A Salesforce-compatible UI API serves picklist values per record type, and a dialect had no neutral
// way to ask for them: the resolver lived here, but nothing on the neutral plane exposed it. So a
// dialect either served the field's whole value set under a record-type-keyed route — wrong the moment
// a record type restricts values — or could not serve the route at all.
//
// These tests own the two properties that make the new read safe to depend on: a source that can
// answer is reached, and a source that cannot degrades through *this* assertion rather than taking
// anything else with it.

type scopedPicklistSource struct {
	legacyLayoutSource

	calls   []string
	answer  *models.ScopedPicklistValueSet
	failErr error
}

func (s *scopedPicklistSource) ResolvePicklistValuesForRecordType(
	_ context.Context,
	objectAPIName string,
	recordTypeID string,
	fieldAPIName string,
) (*models.ScopedPicklistValueSet, error) {
	s.calls = append(s.calls, strings.Join([]string{objectAPIName, recordTypeID, fieldAPIName}, "|"))
	if s.failErr != nil {
		return nil, s.failErr
	}
	return s.answer, nil
}

// TestContractServiceForwardsTheScopedPicklistReadUnchanged checks that the adapter forwards rather
// than deciding. The scoping rule — including the master fallback for an object with no concrete record
// types — belongs to the resolver, so every consumer must see one answer.
func TestContractServiceForwardsTheScopedPicklistReadUnchanged(t *testing.T) {
	want := &models.ScopedPicklistValueSet{
		ObjectAPIName: "Opportunity",
		FieldAPIName:  "StageName",
		RecordTypeID:  "012-business",
		Values: []models.ScopedPicklistValue{
			{Value: "Prospecting", Label: "Prospecting"},
			{Value: "Closed Won", Label: "Closed Won"},
		},
	}
	source := &scopedPicklistSource{answer: want}
	svc := NewContractService(source, nil)

	got, err := svc.ResolvePicklistValuesForRecordType(context.Background(), "Opportunity", "012-business", "StageName")
	if err != nil {
		t.Fatalf("ResolvePicklistValuesForRecordType error = %v", err)
	}
	if got != want {
		t.Fatalf("got %#v, want the resolver's own value set %#v", got, want)
	}
	if len(source.calls) != 1 || source.calls[0] != "Opportunity|012-business|StageName" {
		t.Fatalf("resolver calls = %v, want one call carrying the tuple unchanged", source.calls)
	}
}

// TestContractServiceSurfacesTheResolversRefusal keeps the adapter from turning a real refusal into a
// missing capability. A caller has to be able to tell "this platform cannot answer" from "this field is
// not a picklist", because the first is a reason to fail closed and the second is a 404.
func TestContractServiceSurfacesTheResolversRefusal(t *testing.T) {
	source := &scopedPicklistSource{failErr: errFieldIsNotAPicklist}
	svc := NewContractService(source, nil)

	_, err := svc.ResolvePicklistValuesForRecordType(context.Background(), "Account", "012-business", "Name")
	if err != errFieldIsNotAPicklist {
		t.Fatalf("error = %v, want the resolver's own error", err)
	}
	if strings.Contains(err.Error(), "unavailable") {
		t.Fatalf("error = %v, which a caller would read as a missing capability", err)
	}
}

// TestALegacySourceDegradesOnlyTheScopedPicklistRead is the reason this capability is its own
// interface. Folding it into an existing source interface would make a platform that implements only
// the older reads fail their assertions too, reporting an outage for reads that work perfectly.
func TestALegacySourceDegradesOnlyTheScopedPicklistRead(t *testing.T) {
	source := &legacyLayoutSource{presented: &models.PageLayout{ID: "L1", LayoutName: "presented"}}
	svc := NewContractService(source, nil)
	ctx := context.Background()

	// t.Errorf, not t.Fatalf: one run should enumerate everything that broke.
	if got, err := svc.ListLayouts(ctx, "Account"); err != nil || len(got) != 1 {
		t.Errorf("ListLayouts = (%v, %v), want one layout and no error", got, err)
	}
	if got, err := svc.ListApprovalLayouts(ctx, "Account"); err != nil || len(got) != 1 {
		t.Errorf("ListApprovalLayouts = (%v, %v), want one layout and no error", got, err)
	}

	values, err := svc.ResolvePicklistValuesForRecordType(ctx, "Account", "012-business", "Industry")
	if err == nil || !strings.Contains(err.Error(), "picklist scope source unavailable") {
		t.Errorf("ResolvePicklistValuesForRecordType error = %v, want the picklist-scope degrade", err)
	}
	if values != nil {
		t.Errorf("ResolvePicklistValuesForRecordType returned %#v alongside an error, want nil", values)
	}
}

// TestTheRealMetadataServiceSatisfiesTheScopedPicklistSource is what makes the assertion more than a
// hopeful cast: the shipped service must actually be reachable through it, or every deployment fails
// closed and the capability is exposed in name only.
func TestTheRealMetadataServiceSatisfiesTheScopedPicklistSource(t *testing.T) {
	var _ contractPicklistScopeSource = (*MetadataService)(nil)
}

// errFieldIsNotAPicklist stands in for a refusal the resolver itself produces, so the test above
// distinguishes it from the adapter's missing-capability error without depending on the resolver's
// concrete error type.
var errFieldIsNotAPicklist = errors.New("field is not a picklist or multipicklist")
