package programchecker

import (
	"context"
	"errors"
	"testing"

	sharedcontract "github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	connectorcontract "github.com/pingcap-inc/nexuscrm-contract/pkg/connector"
)

func TestActionSignatureReaderFuncCopiesReturnedSlices(t *testing.T) {
	base := ActionSignature{
		Name: "ProgramAction",
		Inputs: []sharedcontract.StandardActionSchemaField{{
			Name: "inputValue",
			Type: "string",
		}},
		Outputs: []sharedcontract.StandardActionSchemaField{{
			Name: "outputValue",
			Type: "number",
		}},
	}
	reader := ActionSignatureReaderFunc(func(context.Context, string) (ActionSignature, bool, error) {
		return base, true, nil
	})

	got, ok, err := reader.ActionSignature(context.Background(), "ProgramAction")
	if err != nil {
		t.Fatalf("ActionSignature: %v", err)
	}
	if !ok {
		t.Fatal("ActionSignature ok = false, want true")
	}
	got.Inputs[0].Name = "mutated"

	again, ok, err := reader.ActionSignature(context.Background(), "ProgramAction")
	if err != nil || !ok {
		t.Fatalf("ActionSignature second read ok=%v err=%v", ok, err)
	}
	if got, want := again.Inputs[0].Name, "inputValue"; got != want {
		t.Fatalf("Input name after caller mutation = %q, want %q", got, want)
	}
}

func TestConnectorMappingReaderFuncCopiesReturnedRows(t *testing.T) {
	base := []connectorcontract.ConnectorFieldMappingInput{{
		Source:       "pack",
		SourceObject: "ProgramSource",
		TargetObject: "ProgramTarget",
		ActionType:   "upsert",
		SourceField:  "sourceValue",
		TargetField:  "targetValue",
		Direction:    connectorcontract.DirectionOutbound,
	}}
	reader := ConnectorMappingReaderFunc(func(context.Context, connectorcontract.ResolveQuery) ([]connectorcontract.ConnectorFieldMappingInput, error) {
		return base, nil
	})

	got, err := reader.ConnectorMappings(context.Background(), connectorcontract.ResolveQuery{})
	if err != nil {
		t.Fatalf("ConnectorMappings: %v", err)
	}
	got[0].SourceField = "mutated"

	again, err := reader.ConnectorMappings(context.Background(), connectorcontract.ResolveQuery{})
	if err != nil {
		t.Fatalf("ConnectorMappings second read: %v", err)
	}
	if got, want := again[0].SourceField, "sourceValue"; got != want {
		t.Fatalf("SourceField after caller mutation = %q, want %q", got, want)
	}
}

func TestReaderFuncsReturnUnavailableForNil(t *testing.T) {
	_, _, err := ActionSignatureReaderFunc(nil).ActionSignature(context.Background(), "ProgramAction")
	if !errors.Is(err, ErrReaderUnavailable) {
		t.Fatalf("nil ActionSignatureReaderFunc error = %v, want ErrReaderUnavailable", err)
	}
	_, err = ConnectorMappingReaderFunc(nil).ConnectorMappings(context.Background(), connectorcontract.ResolveQuery{})
	if !errors.Is(err, ErrReaderUnavailable) {
		t.Fatalf("nil ConnectorMappingReaderFunc error = %v, want ErrReaderUnavailable", err)
	}
	_, err = CapabilityCatalogReaderFunc(nil).Capability(context.Background(), "RunProgram")
	if !errors.Is(err, ErrReaderUnavailable) {
		t.Fatalf("nil CapabilityCatalogReaderFunc error = %v, want ErrReaderUnavailable", err)
	}
}

func TestCapabilityPolicyAdapterDelegatesAndFailsClosedWhenUnwired(t *testing.T) {
	ctx := context.Background()
	policy := fakeCapabilityPolicy{capabilities: map[sharedcontract.Capability]bool{"RunProgram": true}}
	reader := CapabilityPolicyAdapter{Policy: policy}

	access, err := reader.Access(ctx, &models.UserSession{ID: "u1"}, "ProgramNode", sharedcontract.AccessRequest{})
	if err != nil {
		t.Fatalf("Access: %v", err)
	}
	if access == nil || !access.Object.Read {
		t.Fatalf("Access = %#v, want readable object access", access)
	}
	if !reader.HasCapability(ctx, &models.UserSession{ID: "u1"}, "RunProgram") {
		t.Fatal("HasCapability(RunProgram) = false, want true")
	}
	if (CapabilityPolicyAdapter{}).HasCapability(ctx, nil, "RunProgram") {
		t.Fatal("unwired HasCapability = true, want false")
	}
	if _, err := (CapabilityPolicyAdapter{}).Access(ctx, nil, "ProgramNode", sharedcontract.AccessRequest{}); !errors.Is(err, ErrReaderUnavailable) {
		t.Fatalf("unwired Access error = %v, want ErrReaderUnavailable", err)
	}
}

type fakeCapabilityPolicy struct {
	capabilities map[sharedcontract.Capability]bool
}

func (p fakeCapabilityPolicy) Access(context.Context, *models.UserSession, string, sharedcontract.AccessRequest) (*sharedcontract.EffectiveObjectAccess, error) {
	return &sharedcontract.EffectiveObjectAccess{Object: sharedcontract.ObjectAccess{Read: true}}, nil
}

func (p fakeCapabilityPolicy) HasCapability(_ context.Context, _ *models.UserSession, capability sharedcontract.Capability) bool {
	return p.capabilities[capability]
}
