package serverruntime

import (
	"archive/tar"
	"bytes"
	"context"
	"database/sql"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-controlplane/packs"
)

func TestMaterializeConnectorPushTrustedLocalPackSnapshot(t *testing.T) {
	root, source, revision := connectorPushRecoveryTestRepo(t)
	snapshot, cleanup, err := materializeConnectorPushRecoveryTestSnapshot(context.Background(), root, source, revision)
	if err != nil {
		t.Fatalf("materializeConnectorPushTrustedLocalPackSnapshot: %v", err)
	}
	if snapshot == source || !strings.Contains(snapshot, "nexuscrm-recovery-pack-") {
		t.Fatalf("snapshot path = %q, source = %q", snapshot, source)
	}
	payload, err := os.ReadFile(filepath.Join(snapshot, "payload.json"))
	if err != nil {
		t.Fatalf("read snapshot payload: %v", err)
	}
	if string(payload) != "{\"revision\":1}\n" {
		t.Fatalf("snapshot payload = %q", payload)
	}
	cleanup()
	if _, err := os.Stat(snapshot); !os.IsNotExist(err) {
		t.Fatalf("snapshot still exists after cleanup: %v", err)
	}
}

func TestMaterializeConnectorPushTrustedLocalPackSnapshotRelocatesInstalledSource(t *testing.T) {
	installedRoot, installedSource, revision := connectorPushRecoveryTestRepo(t)
	mountParent := t.TempDir()
	mountedRoot := filepath.Join(mountParent, "mounted-release")
	connectorPushRecoveryGit(t, mountParent, "clone", "--no-hardlinks", installedRoot, mountedRoot)
	if got := connectorPushRecoveryGit(t, mountedRoot, "rev-parse", "HEAD"); got != revision {
		t.Fatalf("mounted release HEAD = %q, want %q", got, revision)
	}
	if err := os.RemoveAll(installedRoot); err != nil {
		t.Fatalf("remove install-time checkout: %v", err)
	}
	if _, err := os.Stat(installedSource); !os.IsNotExist(err) {
		t.Fatalf("install-time source still exists: %v", err)
	}

	snapshot, cleanup, err := materializeConnectorPushRecoveryTestSnapshot(
		context.Background(),
		mountedRoot,
		installedSource,
		revision,
	)
	if err != nil {
		t.Fatalf("materialize relocated local_path snapshot: %v", err)
	}
	defer cleanup()
	payload, err := os.ReadFile(filepath.Join(snapshot, "payload.json"))
	if err != nil || string(payload) != "{\"revision\":1}\n" {
		t.Fatalf("relocated snapshot payload = %q, %v", payload, err)
	}
}

func TestMaterializeConnectorPushTrustedLocalPackSnapshotRejectsAmbiguousIdentity(t *testing.T) {
	root, source, _ := connectorPushRecoveryTestRepo(t)
	duplicate := filepath.Join(root, "packs", "duplicate")
	if err := os.MkdirAll(duplicate, 0o700); err != nil {
		t.Fatal(err)
	}
	for _, name := range []string{"pack.json", "payload.json"} {
		payload, err := os.ReadFile(filepath.Join(source, name))
		if err != nil {
			t.Fatal(err)
		}
		if err := os.WriteFile(filepath.Join(duplicate, name), payload, 0o600); err != nil {
			t.Fatal(err)
		}
	}
	connectorPushRecoveryGit(t, root, "add", "packs/duplicate")
	connectorPushRecoveryGit(t, root, "-c", "user.name=NexusCRM Test", "-c", "user.email=test@nexuscrm.invalid", "commit", "-m", "duplicate identity")
	revision := connectorPushRecoveryGit(t, root, "rev-parse", "HEAD")

	_, cleanup, err := materializeConnectorPushRecoveryTestSnapshot(context.Background(), root, source, revision)
	cleanup()
	if err == nil || !strings.Contains(err.Error(), "ambiguous installed pack identity") {
		t.Fatalf("error = %v", err)
	}
}

func TestMaterializeConnectorPushTrustedLocalPackSnapshotAtRepositoryRoot(t *testing.T) {
	root, revision := connectorPushRecoveryRootPackTestRepo(t)
	snapshot, cleanup, err := materializeConnectorPushRecoveryTestSnapshot(
		context.Background(),
		root,
		root,
		revision,
	)
	if err != nil {
		t.Fatalf("materialize repo-root local_path snapshot: %v", err)
	}
	defer cleanup()
	if snapshot == root || !strings.Contains(snapshot, "nexuscrm-recovery-pack-") {
		t.Fatalf("snapshot path = %q, source = %q", snapshot, root)
	}
	payload, err := os.ReadFile(filepath.Join(snapshot, "payload.json"))
	if err != nil {
		t.Fatalf("read repo-root snapshot payload: %v", err)
	}
	if string(payload) != "{\"revision\":1}\n" {
		t.Fatalf("repo-root snapshot payload = %q", payload)
	}
}

func TestMaterializeConnectorPushTrustedLocalPackSnapshotIgnoresComponentNamedPackJSON(t *testing.T) {
	root, source, _ := connectorPushRecoveryTestRepo(t)
	componentDir := filepath.Join(source, "components")
	if err := os.MkdirAll(componentDir, 0o700); err != nil {
		t.Fatal(err)
	}
	manifest := `{
  "manifestFormatVersion": "1",
  "name": "companion",
  "displayName": "Companion",
  "category": "app-pack",
  "version": "0.1.0",
  "platformCompatibility": {"min": "0.2.0", "maxExclusive": "0.3.0"},
  "dependencies": [],
  "components": [
    {
      "kind": "connector_field_mapping",
      "businessKey": "companion/test-mapping",
      "manageability": "locked",
      "path": "components/pack.json"
    }
  ]
}` + "\n"
	if err := os.WriteFile(filepath.Join(source, "pack.json"), []byte(manifest), 0o600); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(componentDir, "pack.json"), []byte("[]\n"), 0o600); err != nil {
		t.Fatal(err)
	}
	connectorPushRecoveryGit(t, root, "add", "packs/companion")
	connectorPushRecoveryGit(t, root, "-c", "user.name=NexusCRM Test", "-c", "user.email=test@nexuscrm.invalid", "commit", "-m", "nested pack json component")
	revision := connectorPushRecoveryGit(t, root, "rev-parse", "HEAD")

	snapshot, cleanup, err := materializeConnectorPushRecoveryTestSnapshot(context.Background(), root, source, revision)
	if err != nil {
		t.Fatalf("materialize local_path snapshot with pack.json component: %v", err)
	}
	defer cleanup()
	payload, err := os.ReadFile(filepath.Join(snapshot, "components", "pack.json"))
	if err != nil || string(payload) != "[]\n" {
		t.Fatalf("component pack.json payload = %q, %v", payload, err)
	}
}

func TestMaterializeConnectorPushTrustedLocalPackSnapshotAllowsInRootSymlinks(t *testing.T) {
	root := t.TempDir()
	source := filepath.Join(root, "packs", "companion")
	if err := os.MkdirAll(source, 0o700); err != nil {
		t.Fatal(err)
	}
	manifest := `{"manifestFormatVersion":"1","name":"companion","displayName":"Companion","category":"app-pack","version":"0.1.0","platformCompatibility":{"min":"0.2.0","maxExclusive":"0.3.0"},"dependencies":[],"components":[]}` + "\n"
	if err := os.WriteFile(filepath.Join(source, "pack.real.json"), []byte(manifest), 0o600); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(source, "payload.real.json"), []byte("{\"revision\":1}\n"), 0o600); err != nil {
		t.Fatal(err)
	}
	if err := os.Symlink("pack.real.json", filepath.Join(source, "pack.json")); err != nil {
		t.Fatal(err)
	}
	if err := os.Symlink("payload.real.json", filepath.Join(source, "payload.json")); err != nil {
		t.Fatal(err)
	}
	connectorPushRecoveryGit(t, root, "init")
	connectorPushRecoveryGit(t, root, "add", "packs/companion")
	connectorPushRecoveryGit(t, root, "-c", "user.name=NexusCRM Test", "-c", "user.email=test@nexuscrm.invalid", "commit", "-m", "symlink fixture")
	revision := connectorPushRecoveryGit(t, root, "rev-parse", "HEAD")

	snapshot, cleanup, err := materializeConnectorPushRecoveryTestSnapshot(context.Background(), root, source, revision)
	if err != nil {
		t.Fatalf("materialize symlinked local_path snapshot: %v", err)
	}
	defer cleanup()
	if payload, err := os.ReadFile(filepath.Join(snapshot, "payload.json")); err != nil || string(payload) != "{\"revision\":1}\n" {
		t.Fatalf("read symlinked snapshot payload = %q, %v", payload, err)
	}
}

func TestMaterializeConnectorPushTrustedLocalPackSnapshotFailsClosed(t *testing.T) {
	t.Run("missing trusted root", func(t *testing.T) {
		_, cleanup, err := materializeConnectorPushRecoveryTestSnapshot(context.Background(), "", "/tmp/pack", strings.Repeat("a", 40))
		cleanup()
		if err == nil || !strings.Contains(err.Error(), connectorPushTrustedLocalPackRootEnv) {
			t.Fatalf("error = %v", err)
		}
	})

	t.Run("installed pack identity missing", func(t *testing.T) {
		root, source, revision := connectorPushRecoveryTestRepo(t)
		_, cleanup, err := materializeConnectorPushTrustedLocalPackSnapshot(
			context.Background(), root, source, revision, "missing", string(packs.AppPack), "1",
		)
		cleanup()
		if err == nil || !strings.Contains(err.Error(), "does not contain installed pack identity") {
			t.Fatalf("error = %v", err)
		}
	})

	t.Run("trusted root is not Git top level", func(t *testing.T) {
		root, source, revision := connectorPushRecoveryTestRepo(t)
		_, cleanup, err := materializeConnectorPushRecoveryTestSnapshot(
			context.Background(),
			filepath.Join(root, "packs"),
			source,
			revision,
		)
		cleanup()
		if err == nil || !strings.Contains(err.Error(), "top level") {
			t.Fatalf("error = %v", err)
		}
	})

	t.Run("tracked worktree drift", func(t *testing.T) {
		root, source, revision := connectorPushRecoveryTestRepo(t)
		if err := os.WriteFile(filepath.Join(source, "payload.json"), []byte("dirty\n"), 0o600); err != nil {
			t.Fatal(err)
		}
		_, cleanup, err := materializeConnectorPushRecoveryTestSnapshot(context.Background(), root, source, revision)
		cleanup()
		if err == nil || !strings.Contains(err.Error(), "tracked changes") {
			t.Fatalf("error = %v", err)
		}
	})

	t.Run("untracked source file", func(t *testing.T) {
		root, source, revision := connectorPushRecoveryTestRepo(t)
		if err := os.WriteFile(filepath.Join(source, "extra.json"), []byte("{}\n"), 0o600); err != nil {
			t.Fatal(err)
		}
		_, cleanup, err := materializeConnectorPushRecoveryTestSnapshot(context.Background(), root, source, revision)
		cleanup()
		if err == nil || !strings.Contains(err.Error(), "untracked files") {
			t.Fatalf("error = %v", err)
		}
	})

	t.Run("historical installed revision remains recoverable after release head advances", func(t *testing.T) {
		root, source, revision := connectorPushRecoveryTestRepo(t)
		if err := os.WriteFile(filepath.Join(root, "release.txt"), []byte("next\n"), 0o600); err != nil {
			t.Fatal(err)
		}
		connectorPushRecoveryGit(t, root, "add", "release.txt")
		connectorPushRecoveryGit(t, root, "-c", "user.name=NexusCRM Test", "-c", "user.email=test@nexuscrm.invalid", "commit", "-m", "next")
		snapshot, cleanup, err := materializeConnectorPushRecoveryTestSnapshot(context.Background(), root, source, revision)
		cleanup()
		if err != nil {
			t.Fatalf("materialize historical revision: %v", err)
		}
		if snapshot == "" {
			t.Fatal("historical revision snapshot is empty")
		}
	})

	t.Run("installed revision missing from release repository", func(t *testing.T) {
		root, source, _ := connectorPushRecoveryTestRepo(t)
		_, cleanup, err := materializeConnectorPushRecoveryTestSnapshot(
			context.Background(),
			root,
			source,
			strings.Repeat("a", 40),
		)
		cleanup()
		if err == nil || !strings.Contains(err.Error(), "does not contain installed source revision") {
			t.Fatalf("error = %v", err)
		}
	})
}

func TestValidateConnectorInboundInstalledPackStateForHintAllowsOnlyBoundLocalPath(t *testing.T) {
	state := &connectorPushInstalledPackState{
		owner:           "companion",
		packInstallID:   "install-1",
		category:        string(packs.AppPack),
		status:          "active",
		manifestVersion: "1",
		sourceKind:      packs.PackSourceKindLocalPath,
		sourceURI:       "/release/packs/companion",
		sourceRevision:  strings.Repeat("a", 40),
		appliedHash:     "hash",
		desiredHash:     "hash",
	}
	if err := validateConnectorInboundInstalledPackStateForHint(state, "", false); err == nil || !strings.Contains(err.Error(), connectorPushTrustedLocalPackRootEnv) {
		t.Fatalf("unbound local_path error = %v", err)
	}
	if err := validateConnectorInboundInstalledPackStateForHint(state, "/release", false); err != nil {
		t.Fatalf("bound local_path error = %v", err)
	}
	state.sourceKind = "mutable_path"
	if err := validateConnectorInboundInstalledPackStateForHint(state, "/release", false); err == nil || !strings.Contains(err.Error(), "release-scoped") {
		t.Fatalf("unknown source kind error = %v", err)
	}
}

func TestResolveConnectorPushRecoverySourcePackUsesReleaseCommitSnapshot(t *testing.T) {
	root, source, revision := connectorPushRecoveryTestRepo(t)
	original, err := packs.ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevisionWithPackSourceOptionsAndComponentKindRegistrationSet(
		"companion",
		packs.AppPack,
		"1",
		packs.PackSourceKindLocalPath,
		source,
		revision,
		packs.DefaultPackSourceOptions(),
		packs.ComponentKindRegistrationSet{},
	)
	if err != nil {
		t.Fatalf("resolve fixture pack: %v", err)
	}
	payloadHash, err := packs.PackPayloadHash(original)
	if err != nil {
		t.Fatalf("fixture payload hash: %v", err)
	}
	resolver := &connectorPushContractResolver{
		trustedLocalPackRoot:         root,
		componentKindRegistrationSet: packs.ComponentKindRegistrationSet{},
	}
	state := &connectorPushInstalledPackState{
		owner:           "companion",
		packInstallID:   "install-1",
		category:        string(packs.AppPack),
		status:          "active",
		manifestVersion: "1",
		sourceKind:      packs.PackSourceKindLocalPath,
		sourceURI:       source,
		sourceRevision:  revision,
		appliedHash:     payloadHash,
		desiredHash:     payloadHash,
	}
	resolved, cleanup, err := resolver.resolveConnectorPushRecoverySourcePack(context.Background(), state, nil)
	if err != nil {
		t.Fatalf("resolveConnectorPushRecoverySourcePack: %v", err)
	}
	defer cleanup()
	gotHash, err := packs.PackPayloadHash(resolved)
	if err != nil {
		t.Fatalf("resolved payload hash: %v", err)
	}
	if gotHash != payloadHash {
		t.Fatalf("resolved payload hash = %q, want %q", gotHash, payloadHash)
	}

	state.appliedHash = "nexus-pack-sha256-v1:" + strings.Repeat("0", 64)
	_, mismatchCleanup, err := resolver.resolveConnectorPushRecoverySourcePack(
		context.Background(),
		state,
		nil,
	)
	mismatchCleanup()
	if err == nil || !strings.Contains(err.Error(), "locked installed payload hash") {
		t.Fatalf("payload mismatch error = %v", err)
	}
}

func TestExtractConnectorPushTarAllowsOnlyInRootSymlinks(t *testing.T) {
	t.Run("in-root symlink", func(t *testing.T) {
		var archive bytes.Buffer
		writer := tar.NewWriter(&archive)
		payload := []byte("ok\n")
		if err := writer.WriteHeader(&tar.Header{Name: "packs/target.json", Typeflag: tar.TypeReg, Size: int64(len(payload))}); err != nil {
			t.Fatal(err)
		}
		if _, err := writer.Write(payload); err != nil {
			t.Fatal(err)
		}
		if err := writer.WriteHeader(&tar.Header{Name: "packs/link.json", Typeflag: tar.TypeSymlink, Linkname: "target.json"}); err != nil {
			t.Fatal(err)
		}
		if err := writer.Close(); err != nil {
			t.Fatal(err)
		}
		destination := t.TempDir()
		if err := extractConnectorPushTar(bytes.NewReader(archive.Bytes()), destination); err != nil {
			t.Fatalf("extract in-root symlink: %v", err)
		}
		if got, err := os.ReadFile(filepath.Join(destination, "packs", "link.json")); err != nil || string(got) != "ok\n" {
			t.Fatalf("read extracted symlink = %q, %v", got, err)
		}
	})

	for _, test := range []struct {
		name   string
		header tar.Header
		want   string
	}{
		{name: "absolute symlink", header: tar.Header{Name: "packs/link", Typeflag: tar.TypeSymlink, Linkname: "/tmp"}, want: "escaping symlink"},
		{name: "relative symlink escape", header: tar.Header{Name: "packs/link", Typeflag: tar.TypeSymlink, Linkname: "../../tmp"}, want: "escaping symlink"},
		{name: "traversal", header: tar.Header{Name: "../escape", Typeflag: tar.TypeReg, Size: 0}, want: "unsafe path"},
	} {
		t.Run(test.name, func(t *testing.T) {
			var archive bytes.Buffer
			writer := tar.NewWriter(&archive)
			if err := writer.WriteHeader(&test.header); err != nil {
				t.Fatal(err)
			}
			if err := writer.Close(); err != nil {
				t.Fatal(err)
			}
			err := extractConnectorPushTar(bytes.NewReader(archive.Bytes()), t.TempDir())
			if err == nil || !strings.Contains(err.Error(), test.want) {
				t.Fatalf("error = %v, want %q", err, test.want)
			}
		})
	}
}

func TestResolveConnectorPushRecoverySourcePackUsesProvisionalDependencySchema(t *testing.T) {
	root, source, revision := connectorPushRecoverySchemaTestRepo(t)
	resolver := &connectorPushContractResolver{
		db:                           &sql.DB{},
		trustedLocalPackRoot:         root,
		componentKindRegistrationSet: packs.ComponentKindRegistrationSet{},
		objectSchemaResolver: func(context.Context, *sql.DB) func(string) *models.ObjectMetadata {
			return connectorPushRecoveryTestSchema
		},
	}
	opts := packs.DefaultPackSourceOptions()
	opts.ObjectSchemaResolver = connectorPushRecoveryTestSchema
	original, err := packs.ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevisionWithPackSourceOptionsAndComponentKindRegistrationSet(
		"companion", packs.AppPack, "1", packs.PackSourceKindLocalPath, source, revision, opts, packs.ComponentKindRegistrationSet{},
	)
	if err != nil {
		t.Fatalf("resolve schema fixture pack: %v", err)
	}
	payloadHash, err := packs.PackPayloadHash(original)
	if err != nil {
		t.Fatalf("schema fixture payload hash: %v", err)
	}
	state := &connectorPushInstalledPackState{
		owner: "companion", packInstallID: "install-1", category: string(packs.AppPack), status: "active",
		manifestVersion: "1", sourceKind: packs.PackSourceKindLocalPath, sourceURI: source,
		sourceRevision: revision, appliedHash: payloadHash, desiredHash: payloadHash,
	}
	resolved, cleanup, err := resolver.resolveConnectorPushRecoverySourcePack(context.Background(), state, nil)
	if err != nil {
		t.Fatalf("resolve recovery source with provisional dependency schema: %v", err)
	}
	cleanup()
	if len(resolved.ConnectorPushContractDeclarations()) != 2 {
		t.Fatalf("connector push declarations = %d, want 2", len(resolved.ConnectorPushContractDeclarations()))
	}
}

func connectorPushRecoveryTestRepo(t *testing.T) (string, string, string) {
	t.Helper()
	root := t.TempDir()
	source := filepath.Join(root, "packs", "companion")
	if err := os.MkdirAll(source, 0o700); err != nil {
		t.Fatal(err)
	}
	manifest := `{
  "manifestFormatVersion": "1",
  "name": "companion",
  "displayName": "Companion",
  "category": "app-pack",
  "version": "0.1.0",
  "platformCompatibility": {"min": "0.2.0", "maxExclusive": "0.3.0"},
  "dependencies": [],
  "components": []
}` + "\n"
	if err := os.WriteFile(filepath.Join(source, "pack.json"), []byte(manifest), 0o600); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(source, "payload.json"), []byte("{\"revision\":1}\n"), 0o600); err != nil {
		t.Fatal(err)
	}
	connectorPushRecoveryGit(t, root, "init")
	connectorPushRecoveryGit(t, root, "add", "packs/companion/pack.json", "packs/companion/payload.json")
	connectorPushRecoveryGit(t, root, "-c", "user.name=NexusCRM Test", "-c", "user.email=test@nexuscrm.invalid", "commit", "-m", "fixture")
	revision := connectorPushRecoveryGit(t, root, "rev-parse", "HEAD")
	return root, source, revision
}

func materializeConnectorPushRecoveryTestSnapshot(
	ctx context.Context,
	root string,
	source string,
	revision string,
) (string, func(), error) {
	return materializeConnectorPushTrustedLocalPackSnapshot(
		ctx,
		root,
		source,
		revision,
		"companion",
		string(packs.AppPack),
		"1",
	)
}

func connectorPushRecoveryRootPackTestRepo(t *testing.T) (string, string) {
	t.Helper()
	root := t.TempDir()
	manifest := `{
  "manifestFormatVersion": "1",
  "name": "companion",
  "displayName": "Companion",
  "category": "app-pack",
  "version": "0.1.0",
  "platformCompatibility": {"min": "0.2.0", "maxExclusive": "0.3.0"},
  "dependencies": [],
  "components": []
}` + "\n"
	if err := os.WriteFile(filepath.Join(root, "pack.json"), []byte(manifest), 0o600); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(root, "payload.json"), []byte("{\"revision\":1}\n"), 0o600); err != nil {
		t.Fatal(err)
	}
	connectorPushRecoveryGit(t, root, "init")
	connectorPushRecoveryGit(t, root, "add", "pack.json", "payload.json")
	connectorPushRecoveryGit(t, root, "-c", "user.name=NexusCRM Test", "-c", "user.email=test@nexuscrm.invalid", "commit", "-m", "fixture")
	return root, connectorPushRecoveryGit(t, root, "rev-parse", "HEAD")
}

func connectorPushRecoverySchemaTestRepo(t *testing.T) (string, string, string) {
	t.Helper()
	root := t.TempDir()
	source := filepath.Join(root, "packs", "companion")
	contractDir := filepath.Join(source, "connector_push_contracts")
	if err := os.MkdirAll(contractDir, 0o700); err != nil {
		t.Fatal(err)
	}
	manifest := `{
  "manifestFormatVersion": "1",
  "name": "companion",
  "displayName": "Companion",
  "category": "app-pack",
  "version": "0.1.0",
  "platformCompatibility": {"min": "0.2.0", "maxExclusive": "0.3.0"},
  "dependencies": [{"name": "salesforce-crm-standard"}, {"name": "hubspot-crm-connector"}],
  "components": [
    {
      "kind": "standard_flow",
      "businessKey": "HubSpot_Opportunity_Deal_Outbound_Update",
      "manageability": "defaults_only",
      "payload": {"start": {"object": "Opportunity"}, "actionCalls": [{"name": "PushOpportunityToHubSpotDeal", "actionType": "PushToConnector"}]}
    },
    {
      "kind": "standard_flow",
      "businessKey": "HubSpot_Account_Company_Outbound_Update",
      "manageability": "defaults_only",
      "payload": {"start": {"object": "Account"}, "actionCalls": [{"name": "PushAccountToHubSpotCompany", "actionType": "PushToConnector"}]}
    },
    {
      "kind": "connector_field_mapping",
      "businessKey": "companion/opportunity-outbound",
      "manageability": "locked",
      "payload": [
        {
          "sourceObject": "Opportunity",
          "targetObject": "deals",
          "actionType": "PushToConnector",
          "direction": "outbound",
          "mappings": [{"sourceField": "HubSpot_Id__c", "targetField": "id"}]
        },
        {
          "sourceObject": "Account",
          "targetObject": "companies",
          "actionType": "PushToConnector",
          "direction": "outbound",
          "mappings": [{"sourceField": "HubSpot_Id__c", "targetField": "id"}]
        }
      ]
    },
    {
      "kind": "connector_push_contract",
      "businessKey": "companion/HubSpot_Account_Company_Outbound_Update/PushAccountToHubSpotCompany/Account/hubspot-company",
      "manageability": "locked",
      "path": "connector_push_contracts/account.json"
    },
    {
      "kind": "connector_push_contract",
      "businessKey": "companion/HubSpot_Opportunity_Deal_Outbound_Update/PushOpportunityToHubSpotDeal/Opportunity/hubspot-deal",
      "manageability": "locked",
      "path": "connector_push_contracts/opportunity.json"
    }
  ]
}` + "\n"
	accountContract := `{
  "flowDeveloperName": "HubSpot_Account_Company_Outbound_Update",
  "flowActionName": "PushAccountToHubSpotCompany",
  "slotLineageId": "hubspot-company",
  "sourceObject": "Account",
  "destinationSlotId": "hubspot-company",
  "profileOwnerPack": "hubspot-crm-connector",
  "profileName": "hubspot-write",
  "targetObject": "companies",
  "bodyRoot": "properties",
  "createPathTemplate": "/crm/v3/objects/companies",
  "updatePathTemplate": "/crm/v3/objects/companies/{remoteId}",
  "responseIdPath": "id",
  "remoteIdField": "HubSpot_Id__c"
}` + "\n"
	contract := `{
  "flowDeveloperName": "HubSpot_Opportunity_Deal_Outbound_Update",
  "flowActionName": "PushOpportunityToHubSpotDeal",
  "slotLineageId": "hubspot-deal",
  "sourceObject": "Opportunity",
  "destinationSlotId": "hubspot-deal",
  "profileOwnerPack": "hubspot-crm-connector",
  "profileName": "hubspot-write",
  "targetObject": "deals",
  "bodyRoot": "properties",
  "createPathTemplate": "/crm/v3/objects/deals",
  "updatePathTemplate": "/crm/v3/objects/deals/{remoteId}",
  "responseIdPath": "id",
  "remoteIdField": "HubSpot_Id__c",
  "associations": [{
    "sourceLookupField": "AccountId",
    "sourceObject": "Account",
    "remoteIdField": "HubSpot_Id__c",
    "targetActionSlotId": "hubspot-company",
    "targetDestinationSlotId": "hubspot-company",
    "targetObject": "companies",
    "payloadField": "associations",
    "payloadMergeMode": "append",
    "inlinePhases": ["create"],
    "payloadTemplate": {"to": {"id": "{toId}"}},
    "associationPath": "/crm/v3/objects/deals/{fromId}/associations/companies/{toId}/5",
    "associationMethod": "PUT",
    "calloutPhases": ["update"]
  }]
}` + "\n"
	if err := os.WriteFile(filepath.Join(source, "pack.json"), []byte(manifest), 0o600); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(contractDir, "account.json"), []byte(accountContract), 0o600); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(contractDir, "opportunity.json"), []byte(contract), 0o600); err != nil {
		t.Fatal(err)
	}
	connectorPushRecoveryGit(t, root, "init")
	connectorPushRecoveryGit(t, root, "add", "packs/companion")
	connectorPushRecoveryGit(t, root, "-c", "user.name=NexusCRM Test", "-c", "user.email=test@nexuscrm.invalid", "commit", "-m", "schema fixture")
	return root, source, connectorPushRecoveryGit(t, root, "rev-parse", "HEAD")
}

func connectorPushRecoveryTestSchema(apiName string) *models.ObjectMetadata {
	switch strings.ToLower(strings.TrimSpace(apiName)) {
	case "opportunity":
		return &models.ObjectMetadata{APIName: "Opportunity", Fields: []models.FieldMetadata{
			{APIName: "AccountId", Type: "reference", ReferenceTo: []string{"Account"}},
			{APIName: "HubSpot_Id__c", Type: "text"},
		}}
	case "account":
		return &models.ObjectMetadata{APIName: "Account", Fields: []models.FieldMetadata{
			{APIName: "HubSpot_Id__c", Type: "text"},
		}}
	default:
		return nil
	}
}

func connectorPushRecoveryGit(t *testing.T, root string, args ...string) string {
	t.Helper()
	cmd := exec.Command("git", append([]string{"-C", root}, args...)...)
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("git %s: %v: %s", strings.Join(args, " "), err, out)
	}
	return strings.TrimSpace(string(out))
}
