package packs

import (
	"encoding/json"
	"reflect"
	"testing"

	"github.com/nexuscrm/metadata"
	connector "github.com/pingcap-inc/nexuscrm-contract/pkg/connector"
)

func TestSourceBackedPackDeclarationAccessorsReturnDetachedValues(t *testing.T) {
	contract := sourceBackedAccessorTestConnectorPushContract()
	component := &sourceBackedManagedComponent{
		expectedProjection: func() (any, error) { return contract, nil },
	}
	pack := &SourceBackedPack{
		connectorInboundHandlerConfigDeclarations: []ConnectorInboundHandlerConfigComponentPayload{{
			Namespace: "connector", CapabilityKind: "signature_verifier", Config: json.RawMessage(`{"secret":"before"}`),
		}},
		connectorPushContractDeclarations: []connector.ConnectorPushDurableMetadataContract{contract},
		relatedListRules:                  []metadata.RelatedListRule{sourceBackedAccessorTestRelatedListRule()},
		connectedAppOwnershipKeys:         []ConnectedAppOwnershipKey{sourceBackedAccessorTestConnectedAppKey()},
		components:                        []PackComponent{component},
	}

	inbound := pack.ConnectorInboundHandlerConfigDeclarations()
	inbound[0].Config[0] = '!'

	push := pack.ConnectorPushContractDeclarations()
	push[0].PreviousActionSlotIDs[0] = "mutated"
	push[0].DiagnosticErrorFieldPaths[0] = "mutated"
	push[0].CreateConflictResolution.StatusCodes[0] = 599
	push[0].CreateConflictResolution.DiagnosticMatch["code"] = "mutated"
	push[0].Associations[0].TargetPreviousActionSlotIDs[0] = "mutated"
	push[0].Associations[0].InlinePhases[0] = "mutated"
	push[0].Associations[0].CalloutPhases[0] = "mutated"
	push[0].Associations[0].PayloadTemplate.(map[string]any)["properties"].([]any)[1].(map[string]any)["path"] = "mutated"
	push[0].Associations[0].CalloutBodyTemplate.([]any)[0].(map[string]any)["type"] = "mutated"
	push[0].Associations[0].Junction.Filters[0].Equals.(map[string]any)["nested"].([]any)[0] = false

	rules := pack.RuntimeRelatedListRules()
	rules[0].Fields[0] = "mutated"
	rules[0].Resolution.Query.ObjectApiName = "mutated"
	rules[0].Resolution.Create.PrefillMappings["ParentId"] = "mutated"

	keys := pack.ConnectedAppOwnershipKeys()
	keys[0].RedirectURIs[0] = "mutated"
	keys[0].Scopes[0] = "mutated"
	keys[0].AllowedGrantTypes[0] = "mutated"

	wantInbound := []ConnectorInboundHandlerConfigComponentPayload{{
		Namespace: "connector", CapabilityKind: "signature_verifier", Config: json.RawMessage(`{"secret":"before"}`),
	}}
	if !reflect.DeepEqual(pack.connectorInboundHandlerConfigDeclarations, wantInbound) {
		t.Fatalf("inbound declaration mutated through accessor: %#v", pack.connectorInboundHandlerConfigDeclarations)
	}
	wantContract := sourceBackedAccessorTestConnectorPushContract()
	if !reflect.DeepEqual(pack.connectorPushContractDeclarations, []connector.ConnectorPushDurableMetadataContract{wantContract}) {
		t.Fatalf("connector push declaration mutated through accessor: %#v", pack.connectorPushContractDeclarations)
	}
	projection, err := component.ExpectedProjection()
	if err != nil {
		t.Fatalf("ExpectedProjection() error = %v", err)
	}
	if !reflect.DeepEqual(projection, wantContract) {
		t.Fatalf("connector push component projection mutated through accessor: %#v", projection)
	}
	if !reflect.DeepEqual(pack.relatedListRules, []metadata.RelatedListRule{sourceBackedAccessorTestRelatedListRule()}) {
		t.Fatalf("related-list runtime declaration mutated through accessor: %#v", pack.relatedListRules)
	}
	if !reflect.DeepEqual(pack.connectedAppOwnershipKeys, []ConnectedAppOwnershipKey{sourceBackedAccessorTestConnectedAppKey()}) {
		t.Fatalf("connected-app ownership key mutated through accessor: %#v", pack.connectedAppOwnershipKeys)
	}
}

func TestSourceBackedPackDeclarationAccessorsPreserveNilAndEmpty(t *testing.T) {
	nilPack := &SourceBackedPack{}
	if nilPack.ConnectorInboundHandlerConfigDeclarations() != nil ||
		nilPack.ConnectorPushContractDeclarations() != nil ||
		nilPack.RuntimeRelatedListRules() != nil ||
		nilPack.ConnectedAppOwnershipKeys() != nil {
		t.Fatal("nil declaration collection became non-nil")
	}

	emptyPack := &SourceBackedPack{
		connectorInboundHandlerConfigDeclarations: []ConnectorInboundHandlerConfigComponentPayload{},
		connectorPushContractDeclarations:         []connector.ConnectorPushDurableMetadataContract{},
		relatedListRules:                          []metadata.RelatedListRule{},
		connectedAppOwnershipKeys:                 []ConnectedAppOwnershipKey{},
	}
	if emptyPack.ConnectorInboundHandlerConfigDeclarations() == nil ||
		emptyPack.ConnectorPushContractDeclarations() == nil ||
		emptyPack.RuntimeRelatedListRules() == nil ||
		emptyPack.ConnectedAppOwnershipKeys() == nil {
		t.Fatal("non-nil empty declaration collection became nil")
	}

	nestedEmptyPack := &SourceBackedPack{
		connectorPushContractDeclarations: []connector.ConnectorPushDurableMetadataContract{{
			CreateConflictResolution: &connector.ConnectorPushCreateConflictResolutionContract{
				StatusCodes:     []int{},
				DiagnosticMatch: map[string]string{},
			},
			Associations: []connector.ConnectorPushAssociationContract{{
				PayloadTemplate: map[string]any{},
				Junction: &connector.ConnectorPushAssociationJunctionContract{
					Filters: []connector.ConnectorPushAssociationJunctionFilter{{Equals: json.Number("1")}},
				},
			}},
		}},
		relatedListRules: []metadata.RelatedListRule{{
			Fields: []string{},
			Resolution: &metadata.RelatedListResolution{Create: metadata.ResolvedCreateTarget{
				PrefillMappings: map[string]string{},
			}},
		}},
	}
	push := nestedEmptyPack.ConnectorPushContractDeclarations()
	if push[0].CreateConflictResolution.StatusCodes == nil ||
		push[0].CreateConflictResolution.DiagnosticMatch == nil ||
		push[0].Associations[0].PayloadTemplate == nil {
		t.Fatal("non-nil empty connector declaration member became nil")
	}
	if _, ok := push[0].Associations[0].Junction.Filters[0].Equals.(json.Number); !ok {
		t.Fatalf("junction filter number type = %T, want json.Number", push[0].Associations[0].Junction.Filters[0].Equals)
	}
	push[0].CreateConflictResolution.DiagnosticMatch["new"] = "value"
	push[0].Associations[0].PayloadTemplate.(map[string]any)["new"] = true
	rules := nestedEmptyPack.RuntimeRelatedListRules()
	if rules[0].Fields == nil || rules[0].Resolution.Create.PrefillMappings == nil {
		t.Fatal("non-nil empty related-list member became nil")
	}
	rules[0].Resolution.Create.PrefillMappings["new"] = "value"
	if len(nestedEmptyPack.connectorPushContractDeclarations[0].CreateConflictResolution.DiagnosticMatch) != 0 ||
		len(nestedEmptyPack.connectorPushContractDeclarations[0].Associations[0].PayloadTemplate.(map[string]any)) != 0 ||
		len(nestedEmptyPack.relatedListRules[0].Resolution.Create.PrefillMappings) != 0 {
		t.Fatal("adding to detached empty map mutated pack-owned state")
	}
}

func TestCloneSourceBackedJSONValueRejectsUnsupportedMutableValue(t *testing.T) {
	defer func() {
		if recovered := recover(); recovered == nil {
			t.Fatal("unsupported mutable JSON value did not panic")
		}
	}()
	cloneSourceBackedJSONValue(map[int]string{1: "unsupported"})
}

func sourceBackedAccessorTestConnectorPushContract() connector.ConnectorPushDurableMetadataContract {
	return connector.ConnectorPushDurableMetadataContract{
		ConnectorOwnerKey:         "connector",
		ActionSlotID:              "slot",
		PreviousActionSlotIDs:     []string{"previous-slot"},
		DiagnosticErrorFieldPaths: []string{"error.message"},
		CreateConflictResolution: &connector.ConnectorPushCreateConflictResolutionContract{
			StatusCodes:     []int{409},
			DiagnosticMatch: map[string]string{"code": "CONFLICT"},
		},
		Associations: []connector.ConnectorPushAssociationContract{{
			TargetPreviousActionSlotIDs: []string{"target-previous-slot"},
			PayloadTemplate: map[string]any{
				"properties": []any{"email", map[string]any{"path": "Email"}},
			},
			InlinePhases: []string{"before"},
			CalloutBodyTemplate: []any{
				map[string]any{"type": "association"},
			},
			CalloutPhases: []string{"after"},
			Junction: &connector.ConnectorPushAssociationJunctionContract{
				Object: "Junction__c",
				Filters: []connector.ConnectorPushAssociationJunctionFilter{{
					Field: "Primary__c",
					Equals: map[string]any{
						"nested": []any{true},
					},
				}},
			},
		}},
	}
}

func sourceBackedAccessorTestRelatedListRule() metadata.RelatedListRule {
	return metadata.RelatedListRule{
		Key:    "children",
		Fields: []string{"Name"},
		Resolution: &metadata.RelatedListResolution{
			Query: metadata.ResolvedQueryTarget{ObjectApiName: "Child__c"},
			Create: metadata.ResolvedCreateTarget{
				Kind:            "standard",
				PrefillMappings: map[string]string{"ParentId": "Id"},
			},
		},
	}
}

func sourceBackedAccessorTestConnectedAppKey() ConnectedAppOwnershipKey {
	return ConnectedAppOwnershipKey{
		ID:                "app",
		RedirectURIs:      []string{"https://example.test/callback"},
		Scopes:            []string{"records.read"},
		AllowedGrantTypes: []string{"authorization_code"},
	}
}
