package packs

import (
	"path/filepath"
	"strings"
	"testing"
)

func TestSourceBackedPackBindsHistoricalReconciliationDeclaration(t *testing.T) {
	payload := historicalReconciliationContractPayload("Email")
	pack, err := newHistoricalReconciliationTestPack(t, payload, connectorPushOutboundMappingPayloadJSON)
	if err != nil {
		t.Fatalf("NewSourceBackedPack() error = %v", err)
	}

	bindings := pack.ConnectorPushHistoricalReconciliationBindings()
	if len(bindings) != 1 {
		t.Fatalf("historical reconciliation bindings = %#v, want one", bindings)
	}
	if bindings[0].Key.SourceField != "Email" || bindings[0].Mapping.TargetField != "email" {
		t.Fatalf("historical reconciliation binding = %#v", bindings[0])
	}
	if len(bindings[0].Fingerprint) != 64 {
		t.Fatalf("historical reconciliation fingerprint = %q, want sha256 hex", bindings[0].Fingerprint)
	}

	bindings[0].Contract.PreviousActionSlotIDs = append(bindings[0].Contract.PreviousActionSlotIDs, "mutated")
	again := pack.ConnectorPushHistoricalReconciliationBindings()
	if len(again[0].Contract.PreviousActionSlotIDs) != 0 {
		t.Fatalf("historical reconciliation binding accessor aliased pack state: %#v", again[0])
	}
}

func TestSourceBackedPackWithoutHistoricalReconciliationDeclarationKeepsLegacyProjection(t *testing.T) {
	pack, err := newHistoricalReconciliationTestPack(t, connectorPushContractPayloadJSON, connectorPushOutboundMappingPayloadJSON)
	if err != nil {
		t.Fatalf("NewSourceBackedPack() error = %v", err)
	}
	if pack.ConnectorPushHistoricalReconciliationBindings() != nil {
		t.Fatalf("historical reconciliation bindings = %#v, want nil", pack.ConnectorPushHistoricalReconciliationBindings())
	}
	declarations := pack.ConnectorPushContractDeclarations()
	if len(declarations) != 1 || declarations[0].SourceObject != "Contact" {
		t.Fatalf("legacy connector push projection changed: %#v", declarations)
	}
}

func TestSourceBackedPackRejectsTransformedHistoricalReconciliationMapping(t *testing.T) {
	payload := historicalReconciliationContractPayload("Email")
	mappingPayload := strings.Replace(
		connectorPushOutboundMappingPayloadJSON,
		`"sourceField": "Email", "targetField": "email"`,
		`"sourceField": "Email", "targetField": "email", "transformExpression": "LOWER(email)"`,
		1,
	)
	_, err := newHistoricalReconciliationTestPack(t, payload, mappingPayload)
	if err == nil {
		t.Fatal("NewSourceBackedPack() error = nil, want transformed historical mapping rejection")
	}
	if !strings.Contains(err.Error(), "historical reconciliation mapping must not declare TransformExpression") {
		t.Fatalf("NewSourceBackedPack() error = %q", err)
	}
}

func TestInstalledSourceBackedPackBindsStoredOfficialSourceRevision(t *testing.T) {
	const packName = "historical-reconciliation-revision"
	officialRoot := t.TempDir()
	packRoot := filepath.Join(officialRoot, packName)
	mappingPayload := strings.Replace(
		connectorPushOutboundMappingPayloadJSON,
		`{"sourceField": "Email", "targetField": "email"}`,
		`{"sourceField": "Email", "targetField": "email"},`+"\n\t\t\t"+`{"sourceField": "Name", "targetField": "name"}`,
		1,
	)
	writeManifest(t, packRoot, historicalReconciliationTestManifest(
		packName,
		historicalReconciliationContractPayload("Email"),
		mappingPayload,
	))
	initGitTestRepo(t, officialRoot)
	installedRevision := gitTestCommit(t, officialRoot, "installed reconciliation key")

	writeManifest(t, packRoot, historicalReconciliationTestManifest(
		packName,
		historicalReconciliationContractPayload("Name"),
		mappingPayload,
	))
	checkoutHead := gitTestCommit(t, officialRoot, "checkout head reconciliation key")
	if checkoutHead == installedRevision {
		t.Fatal("test fixture produced identical git revisions")
	}

	opts := testPackSourceOptions(t)
	opts.OfficialRepoRoot = officialRoot
	pack, err := ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevisionWithPackSourceOptions(
		packName,
		AppPack,
		SupportedManifestFormatVersion,
		PackSourceKindOfficialRepo,
		packName,
		installedRevision,
		opts,
	)
	if err != nil {
		t.Fatalf("resolve installed source-backed pack: %v", err)
	}
	bindings := pack.ConnectorPushHistoricalReconciliationBindings()
	if len(bindings) != 1 || bindings[0].Key.SourceField != "Email" || bindings[0].Mapping.TargetField != "email" {
		t.Fatalf("installed revision binding = %#v, want Email -> email", bindings)
	}
	if got := PackHashMetadataForPack(pack).SourceRevision; got != installedRevision {
		t.Fatalf("resolved source revision = %q, want %q", got, installedRevision)
	}
	headPack, err := ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevisionWithPackSourceOptions(
		packName,
		AppPack,
		SupportedManifestFormatVersion,
		PackSourceKindOfficialRepo,
		packName,
		checkoutHead,
		opts,
	)
	if err != nil {
		t.Fatalf("resolve checkout-head source-backed pack: %v", err)
	}
	headBindings := headPack.ConnectorPushHistoricalReconciliationBindings()
	if len(headBindings) != 1 || headBindings[0].Key.SourceField != "Name" || headBindings[0].Mapping.TargetField != "name" {
		t.Fatalf("checkout-head binding = %#v, want Name -> name", headBindings)
	}
	if headBindings[0].Fingerprint == bindings[0].Fingerprint {
		t.Fatalf("source revision changed binding without changing fingerprint: %q", bindings[0].Fingerprint)
	}
}

func newHistoricalReconciliationTestPack(t *testing.T, contractPayload, mappingPayload string) (*SourceBackedPack, error) {
	t.Helper()
	root := writePackFixture(t, "jira-like-sample", historicalReconciliationTestManifest(
		"jira-like-sample",
		contractPayload,
		mappingPayload,
	))
	opts := testPackSourceOptions(t)
	source, diagnostics := ResolvePackSourceWithOptions("local_path:"+root, opts)
	if len(diagnostics) != 0 {
		t.Fatalf("ResolvePackSourceWithOptions() diagnostics = %#v", diagnostics)
	}
	return NewSourceBackedPack(source, WithConnectorPushValidationSchemaResolverForSource(source, opts))
}

func historicalReconciliationTestManifest(packName, contractPayload, mappingPayload string) string {
	manifest := validPackManifest(`[
		{
			"kind": "object_spec",
			"businessKey": "Contact",
			"manageability": "locked",
			"payload": `+testObjectSpecPayloadWithFields(
		"Contact",
		`{"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true}`,
		`{"apiName": "Email", "label": "Email", "type": "string"}`,
	)+`
		},
		`+connectorPushStandardFlowManifestComponentJSON()+`,
		{
			"kind": "connector_field_mapping",
			"businessKey": "`+packName+`/contact-push-outbound",
			"manageability": "locked",
			"payload": `+jsonRawMessage(mappingPayload)+`
		},
		{
			"kind": "connector_push_contract",
			"businessKey": "`+packName+`/Contact_To_Remote/PushContact/Contact/remote-contact",
			"manageability": "locked",
			"payload": `+jsonRawMessage(contractPayload)+`
		}
	]`, ``)
	return strings.Replace(manifest, `"name": "jira-like-sample"`, `"name": "`+packName+`"`, 1)
}

func historicalReconciliationContractPayload(sourceField string) string {
	return strings.Replace(
		connectorPushContractPayloadJSON,
		`"sourceObject": "Contact",`,
		`"sourceObject": "Contact",`+"\n\t"+`"historicalReconciliationKey": {"sourceField": "`+sourceField+`"},`,
		1,
	)
}
