package metadata

import (
	"context"
	"testing"

	"github.com/nexuscrm/kernel/pkg/flowactions"
	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/metadata/programchecker"
	"github.com/nexuscrm/shared/pkg/constants"
	sharedcontract "github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	connectorcontract "github.com/pingcap-inc/nexuscrm-contract/pkg/connector"
)

func TestCheckInstalledPackFlowProgramsAcceptsPackOwnedProgram(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	metaSvc := newProgramCheckMetadataService(
		[]*models.FlowDefinition{{
			Id:              "flowdef-accepted",
			DeveloperName:   "Lead_Connector_Upsert",
			OwnerPack:       "program-pack",
			ActiveVersionId: "flowver-accepted",
		}},
		map[string]*models.FlowVersion{
			"flowver-accepted": {
				Id:           "flowver-accepted",
				DefinitionId: "flowdef-accepted",
				Source:       "standard",
				Status:       models.FlowStatusActive,
				Metadata: models.FlowMetadata{
					Start: &models.FlowStart{
						Object:        "Lead",
						FilterFormula: "IsActive == true",
					},
					ActionCalls: []models.FlowActionCall{{
						Name:       "ApplyMapping",
						ActionName: flowactions.ActionTypeApplyConnectorFieldMappings,
						ActionType: flowactions.ActionTypeApplyConnectorFieldMappings,
						InputParameters: []models.FlowActionParameter{
							{Name: "sourceObject", Value: "RemoteLead"},
							{Name: "targetObject", Value: "Lead"},
							{Name: "actionType", Value: "upsert"},
							{Name: "direction", Value: connectorcontract.DirectionInbound},
						},
					}},
					RecordUpdates: []models.FlowRecordUpdate{{
						Name:   "MarkCompany",
						Object: "Lead",
						InputAssignments: []models.FlowInputAssignment{{
							Field: "Company",
							Value: map[string]interface{}{"stringValue": "Acme"},
						}},
					}},
				},
			},
		},
	)

	diags, err := metaSvc.CheckInstalledPackFlowPrograms(ctx, InstalledPackFlowProgramCheckOptions{
		OwnerPacks: []string{"program-pack"},
		Actions: programchecker.ActionSignatureReaderFunc(func(context.Context, string) (programchecker.ActionSignature, bool, error) {
			return programchecker.ActionSignature{
				Name: flowactions.ActionTypeApplyConnectorFieldMappings,
				Inputs: []sharedcontract.StandardActionSchemaField{
					{Name: "sourceObject", Type: "string", Required: true},
					{Name: "targetObject", Type: "string", Required: true},
					{Name: "actionType", Type: "string", Required: true},
					{Name: "direction", Type: "string", Required: true},
				},
			}, true, nil
		}),
		Connectors: programchecker.ConnectorMappingReaderFunc(func(_ context.Context, q connectorcontract.ResolveQuery) ([]connectorcontract.ConnectorFieldMappingInput, error) {
			if q.Source != "program-pack" || q.SourceObject != "RemoteLead" || q.TargetObject != "Lead" || q.ActionType != "upsert" || q.Direction != connectorcontract.DirectionInbound {
				t.Fatalf("ConnectorMappings query = %#v, want program-pack RemoteLead->Lead upsert inbound", q)
			}
			return []connectorcontract.ConnectorFieldMappingInput{{
				Source:       "program-pack",
				SourceObject: "RemoteLead",
				TargetObject: "Lead",
				ActionType:   "upsert",
				Direction:    connectorcontract.DirectionInbound,
				TargetField:  "Company",
			}}, nil
		}),
	})
	if err != nil {
		t.Fatalf("CheckInstalledPackFlowPrograms() error = %v", err)
	}
	if len(diags) != 0 {
		t.Fatalf("CheckInstalledPackFlowPrograms() diagnostics = %#v, want none", diags)
	}
}

func TestCheckInstalledPackFlowProgramsAcceptsPackAuthoredDraftWithoutActivePointer(t *testing.T) {
	t.Parallel()

	metaSvc := newProgramCheckMetadataService(
		[]*models.FlowDefinition{{
			Id:              "flowdef-draft",
			DeveloperName:   "Deferred_Connector_Flow",
			OwnerPack:       "program-pack",
			LatestVersionId: "flowver-draft",
		}},
		map[string]*models.FlowVersion{
			"flowver-draft": {
				Id:            "flowver-draft",
				DefinitionId:  "flowdef-draft",
				Source:        "standard",
				Status:        models.FlowStatusDraft,
				VersionNumber: 1,
				Metadata: models.FlowMetadata{
					ActionCalls: []models.FlowActionCall{{
						Name:       "DeferredAction",
						ActionName: "deferred.action",
						ActionType: "deferred.action",
					}},
				},
			},
		},
	)

	diags, err := metaSvc.CheckInstalledPackFlowPrograms(context.Background(), InstalledPackFlowProgramCheckOptions{
		OwnerPacks: []string{"program-pack"},
	})
	if err != nil {
		t.Fatalf("CheckInstalledPackFlowPrograms() error = %v", err)
	}
	if len(diags) != 0 {
		t.Fatalf("CheckInstalledPackFlowPrograms() diagnostics = %#v, want none for authored Draft", diags)
	}
}

func TestCheckInstalledPackFlowProgramsRejectsActiveVersionWithoutActivePointer(t *testing.T) {
	t.Parallel()

	metaSvc := newProgramCheckMetadataService(
		[]*models.FlowDefinition{{
			Id:              "flowdef-missing-active-pointer",
			DeveloperName:   "Broken_Active_Flow",
			OwnerPack:       "program-pack",
			LatestVersionId: "flowver-active",
		}},
		map[string]*models.FlowVersion{
			"flowver-active": {
				Id:            "flowver-active",
				DefinitionId:  "flowdef-missing-active-pointer",
				Source:        "standard",
				Status:        models.FlowStatusActive,
				VersionNumber: 1,
			},
		},
	)

	diags, err := metaSvc.CheckInstalledPackFlowPrograms(context.Background(), InstalledPackFlowProgramCheckOptions{
		OwnerPacks: []string{"program-pack"},
	})
	if err != nil {
		t.Fatalf("CheckInstalledPackFlowPrograms() error = %v", err)
	}
	assertProgramCheckDiagnostic(t, diags, "metadata_program.missing_active_flow", "program-pack", "standard_flow/Broken_Active_Flow.flow.json")
}

func TestCheckInstalledPackFlowProgramsAppliesFlowMetadataNormalizer(t *testing.T) {
	t.Parallel()

	metaSvc := newProgramCheckMetadataService(
		[]*models.FlowDefinition{{
			Id:              "flowdef-normalized",
			DeveloperName:   "Normalized_Flow",
			OwnerPack:       "program-pack",
			ActiveVersionId: "flowver-normalized",
		}},
		map[string]*models.FlowVersion{
			"flowver-normalized": {
				Id:           "flowver-normalized",
				DefinitionId: "flowdef-normalized",
				Source:       "standard",
				Status:       models.FlowStatusActive,
				MetadataJSON: `{
					"variables": [{"name": "current", "dataType": "SObject", "objectType": "Lead"}],
					"formulas": [{"name": "CompanyText", "dataType": "String", "expression": "current.Company"}]
				}`,
			},
		},
	)

	diags, err := metaSvc.CheckInstalledPackFlowPrograms(context.Background(), InstalledPackFlowProgramCheckOptions{
		OwnerPacks: []string{"program-pack"},
	})
	if err != nil {
		t.Fatalf("CheckInstalledPackFlowPrograms() error = %v", err)
	}
	if !diags.HasErrors() {
		t.Fatalf("CheckInstalledPackFlowPrograms() diagnostics = %#v, want errors before normalization", diags)
	}

	diags, err = metaSvc.CheckInstalledPackFlowPrograms(context.Background(), InstalledPackFlowProgramCheckOptions{
		OwnerPacks: []string{"program-pack"},
		NormalizeFlowMetadata: func(metadata models.FlowMetadata) models.FlowMetadata {
			metadata.Variables = append([]models.FlowVariable(nil), metadata.Variables...)
			metadata.Variables[0].DataType = "Object"
			return metadata
		},
	})
	if err != nil {
		t.Fatalf("CheckInstalledPackFlowPrograms() with normalizer error = %v", err)
	}
	if len(diags) != 0 {
		t.Fatalf("CheckInstalledPackFlowPrograms() with normalizer diagnostics = %#v, want none", diags)
	}
}

func TestCheckInstalledPackFlowProgramsReportsPackContextDiagnostics(t *testing.T) {
	t.Parallel()

	metaSvc := newProgramCheckMetadataService(
		[]*models.FlowDefinition{
			{
				Id:              "flowdef-rejected",
				DeveloperName:   "Broken_Flow",
				OwnerPack:       "program-pack",
				ActiveVersionId: "flowver-rejected",
			},
			{
				Id:              "flowdef-customized",
				DeveloperName:   "Customized_Flow",
				OwnerPack:       "program-pack",
				IsCustomized:    true,
				ActiveVersionId: "flowver-customized",
			},
		},
		map[string]*models.FlowVersion{
			"flowver-rejected": {
				Id:           "flowver-rejected",
				DefinitionId: "flowdef-rejected",
				Source:       "standard",
				Status:       models.FlowStatusActive,
				Metadata: models.FlowMetadata{
					RecordCreates: []models.FlowRecordCreate{{
						Name:   "CreateLead",
						Object: "Lead",
						InputAssignments: []models.FlowInputAssignment{{
							Field: "MissingField",
							Value: map[string]interface{}{"stringValue": "Acme"},
						}},
					}},
				},
			},
			"flowver-customized": {
				Id:           "flowver-customized",
				DefinitionId: "flowdef-customized",
				Source:       "custom",
				Status:       models.FlowStatusActive,
				Metadata: models.FlowMetadata{
					RecordCreates: []models.FlowRecordCreate{{
						Name:   "IgnoredCustom",
						Object: "MissingCustomizedObject",
					}},
				},
			},
		},
	)

	diags, err := metaSvc.CheckInstalledPackFlowPrograms(context.Background(), InstalledPackFlowProgramCheckOptions{
		OwnerPacks: []string{"program-pack"},
	})
	if err != nil {
		t.Fatalf("CheckInstalledPackFlowPrograms() error = %v", err)
	}
	assertProgramCheckDiagnostic(t, diags, "metadata_program.missing_field", "program-pack", "standard_flow/Broken_Flow.flow.json")
	for _, diag := range diags {
		if diag.ComponentPath == "standard_flow/Customized_Flow.flow.json" {
			t.Fatalf("customized FlowDefinition produced diagnostic: %#v", diag)
		}
	}
}

func TestCheckInstalledPackFlowProgramsUsesStandardSiblingWhenActiveVersionIsCustom(t *testing.T) {
	t.Parallel()

	metaSvc := newProgramCheckMetadataService(
		[]*models.FlowDefinition{{
			Id:              "flowdef-custom-active",
			DeveloperName:   "Lead_Custom_Override",
			OwnerPack:       "program-pack",
			ActiveVersionId: "flowver-custom-active",
		}},
		map[string]*models.FlowVersion{
			"flowver-custom-active": {
				Id:            "flowver-custom-active",
				DefinitionId:  "flowdef-custom-active",
				Source:        "custom",
				Status:        models.FlowStatusActive,
				VersionNumber: 3,
				Metadata: models.FlowMetadata{
					RecordCreates: []models.FlowRecordCreate{{
						Name:   "IgnoredCustomPayload",
						Object: "MissingCustomizedObject",
					}},
				},
			},
			"flowver-standard-current": {
				Id:            "flowver-standard-current",
				DefinitionId:  "flowdef-custom-active",
				Source:        "standard",
				Status:        models.FlowStatusDraft,
				VersionNumber: 2,
				Metadata: models.FlowMetadata{
					RecordCreates: []models.FlowRecordCreate{{
						Name:   "CreateLead",
						Object: "Lead",
						InputAssignments: []models.FlowInputAssignment{{
							Field: "Company",
							Value: map[string]interface{}{"stringValue": "Acme"},
						}},
					}},
				},
			},
			"flowver-standard-old": {
				Id:            "flowver-standard-old",
				DefinitionId:  "flowdef-custom-active",
				Source:        "standard",
				Status:        "Obsolete",
				VersionNumber: 1,
				Metadata: models.FlowMetadata{
					RecordCreates: []models.FlowRecordCreate{{
						Name:   "OldBrokenLead",
						Object: "MissingOldObject",
					}},
				},
			},
		},
	)

	diags, err := metaSvc.CheckInstalledPackFlowPrograms(context.Background(), InstalledPackFlowProgramCheckOptions{
		OwnerPacks: []string{"program-pack"},
	})
	if err != nil {
		t.Fatalf("CheckInstalledPackFlowPrograms() error = %v", err)
	}
	if len(diags) != 0 {
		t.Fatalf("CheckInstalledPackFlowPrograms() diagnostics = %#v, want none", diags)
	}
}

func TestCheckInstalledPackFlowProgramsRejectsInvalidActiveVersionPointer(t *testing.T) {
	t.Parallel()

	metaSvc := newProgramCheckMetadataService(
		[]*models.FlowDefinition{{
			Id:              "flowdef-invalid-pointer",
			DeveloperName:   "Invalid_Pointer",
			OwnerPack:       "program-pack",
			ActiveVersionId: "flowver-wrong-definition",
		}},
		map[string]*models.FlowVersion{
			"flowver-wrong-definition": {
				Id:           "flowver-wrong-definition",
				DefinitionId: "flowdef-other",
				Source:       "standard",
				Status:       models.FlowStatusActive,
				Metadata:     models.FlowMetadata{},
			},
		},
	)

	diags, err := metaSvc.CheckInstalledPackFlowPrograms(context.Background(), InstalledPackFlowProgramCheckOptions{
		OwnerPacks: []string{"program-pack"},
	})
	if err != nil {
		t.Fatalf("CheckInstalledPackFlowPrograms() error = %v", err)
	}
	assertProgramCheckDiagnostic(t, diags, "metadata_program.invalid_active_flow_version_definition", "program-pack", "standard_flow/Invalid_Pointer.flow.json")
}

func TestCheckInstalledPackFlowProgramsRejectsInactiveActiveVersionPointer(t *testing.T) {
	t.Parallel()

	metaSvc := newProgramCheckMetadataService(
		[]*models.FlowDefinition{{
			Id:              "flowdef-inactive-pointer",
			DeveloperName:   "Inactive_Pointer",
			OwnerPack:       "program-pack",
			ActiveVersionId: "flowver-draft",
		}},
		map[string]*models.FlowVersion{
			"flowver-draft": {
				Id:           "flowver-draft",
				DefinitionId: "flowdef-inactive-pointer",
				Source:       "standard",
				Status:       models.FlowStatusDraft,
				Metadata:     models.FlowMetadata{},
			},
		},
	)

	diags, err := metaSvc.CheckInstalledPackFlowPrograms(context.Background(), InstalledPackFlowProgramCheckOptions{
		OwnerPacks: []string{"program-pack"},
	})
	if err != nil {
		t.Fatalf("CheckInstalledPackFlowPrograms() error = %v", err)
	}
	assertProgramCheckDiagnostic(t, diags, "metadata_program.invalid_active_flow_version_status", "program-pack", "standard_flow/Inactive_Pointer.flow.json")
}

func TestCheckInstalledPackFlowProgramsRejectsMalformedStoredMetadataJSON(t *testing.T) {
	t.Parallel()

	metaSvc := newProgramCheckMetadataService(
		[]*models.FlowDefinition{{
			Id:              "flowdef-malformed-json",
			DeveloperName:   "Malformed_JSON",
			OwnerPack:       "program-pack",
			ActiveVersionId: "flowver-malformed-json",
		}},
		map[string]*models.FlowVersion{
			"flowver-malformed-json": {
				Id:           "flowver-malformed-json",
				DefinitionId: "flowdef-malformed-json",
				Source:       "standard",
				Status:       models.FlowStatusActive,
				MetadataJSON: "{bad",
			},
		},
	)

	diags, err := metaSvc.CheckInstalledPackFlowPrograms(context.Background(), InstalledPackFlowProgramCheckOptions{
		OwnerPacks: []string{"program-pack"},
	})
	if err != nil {
		t.Fatalf("CheckInstalledPackFlowPrograms() error = %v", err)
	}
	assertProgramCheckDiagnostic(t, diags, "metadata_program.invalid_flow_metadata_json", "program-pack", "standard_flow/Malformed_JSON.flow.json")
}

func newProgramCheckMetadataService(defs []*models.FlowDefinition, versions map[string]*models.FlowVersion) *MetadataService {
	lead := &models.ObjectMetadata{
		APIName: "Lead",
		Fields: []models.FieldMetadata{
			{APIName: "Company", Type: constants.FieldTypeString, Updateable: true},
			{APIName: "IsActive", Type: constants.FieldTypeBoolean, Updateable: true},
		},
	}
	snap := &metadataSnapshot{
		schemas:           []*models.ObjectMetadata{lead},
		schemaMap:         map[string]*models.ObjectMetadata{"lead": lead},
		fieldMap:          map[string][]models.FieldMetadata{"lead": lead.Fields},
		relationshipsMap:  map[string][]*models.Relationship{},
		flowDefinitions:   defs,
		flowDefinitionMap: map[string]*models.FlowDefinition{},
	}
	for _, def := range defs {
		snap.flowDefinitionMap[def.Id] = def
	}
	metaSvc := NewMetadataService(&programCheckRepo{versions: versions}, nil, nil)
	metaSvc.cache.Store(snap)
	metaSvc.coreCache.Store(snap)
	return metaSvc
}

type programCheckRepo struct {
	ports.MetadataRepository
	versions map[string]*models.FlowVersion
}

func (r *programCheckRepo) GetFlowVersionsByIDs(_ context.Context, ids []string) (map[string]*models.FlowVersion, error) {
	out := make(map[string]*models.FlowVersion, len(ids))
	for _, id := range ids {
		if version := r.versions[id]; version != nil {
			out[id] = version
		}
	}
	return out, nil
}

func (r *programCheckRepo) GetFlowVersions(_ context.Context, definitionId string) ([]*models.FlowVersion, error) {
	out := make([]*models.FlowVersion, 0)
	for _, version := range r.versions {
		if version != nil && version.DefinitionId == definitionId {
			out = append(out, version)
		}
	}
	return out, nil
}

func assertProgramCheckDiagnostic(t *testing.T, diags programchecker.Diagnostics, code string, ownerPack string, componentPath string) {
	t.Helper()
	for _, diag := range diags {
		if diag.Code == code && diag.OwnerPack == ownerPack && diag.ComponentPath == componentPath {
			return
		}
	}
	t.Fatalf("diagnostic code=%q ownerPack=%q componentPath=%q not found in %#v", code, ownerPack, componentPath, diags)
}
