package catalogreconcile

import (
	"bytes"
	"os"
	"path/filepath"
	"reflect"
	"testing"
)

func TestPreparePhaseOnePublicationIsDeterministicAndExplicitlyEmpty(t *testing.T) {
	output := phaseOnePublicationOutput()
	first, err := preparePhaseOnePublication("io.nexuscrm.sfcrm", output)
	if err != nil {
		t.Fatal(err)
	}
	second, err := preparePhaseOnePublication("io.nexuscrm.sfcrm", output)
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(first, second) {
		t.Fatal("same Catalog output did not produce byte-identical phase-one publication")
	}
	if len(first.assignment.Objects) != 0 || len(first.assignment.RecordTypes) != 0 ||
		len(first.assignment.NavigationApps) != 0 || len(first.assignment.ProductApps) != 1 {
		t.Fatalf("assignment view = %#v", first.assignment)
	}
	if first.assignment.ProductApps[0].Key.AppID != "io.nexuscrm.sfcrm" ||
		first.assignment.ProductApps[0].ResolvedTarget.ResolvedAppInstallationID != output.AppInstallationID {
		t.Fatalf("product App identity = %#v", first.assignment.ProductApps[0])
	}
	if len(first.artifacts) != 5 {
		t.Fatalf("artifact count = %d", len(first.artifacts))
	}
	for _, artifact := range first.artifacts {
		if artifact.digest != phaseOneDigest(artifact.raw) {
			t.Fatalf("%s digest does not identify canonical bytes", artifact.kind)
		}
	}
	if first.binding.CatalogRevision != output.CatalogRevision ||
		first.binding.SecurityEpoch != output.SecurityEpoch ||
		first.binding.CatalogSnapshotDigest != output.CatalogSnapshotDigest {
		t.Fatalf("publication binding = %#v", first.binding)
	}
}

func TestPreparePhaseOnePublicationBindsOneImmutableRevision(t *testing.T) {
	first, err := preparePhaseOnePublication("io.nexuscrm.sfcrm", phaseOnePublicationOutput())
	if err != nil {
		t.Fatal(err)
	}
	changedOutput := phaseOnePublicationOutput()
	changedOutput.CatalogRevision++
	changedOutput.CatalogSnapshotDigest = digestOf('8')
	changed, err := preparePhaseOnePublication("io.nexuscrm.sfcrm", changedOutput)
	if err != nil {
		t.Fatal(err)
	}
	if changed.result.PublicationDigest == first.result.PublicationDigest ||
		changed.result.AssignmentViewDigest == first.result.AssignmentViewDigest ||
		changed.result.LayoutValidationViewDigest == first.result.LayoutValidationViewDigest ||
		changed.result.LayoutActivationSetDigest == first.result.LayoutActivationSetDigest {
		t.Fatal("Catalog revision change did not change the complete immutable publication chain")
	}
}

func TestPutPhaseOneArtifactIsIdempotentAndRejectsDigestCollision(t *testing.T) {
	root := t.TempDir()
	raw := []byte("canonical\n")
	artifact := phaseOneArtifact{kind: "test-v1", digest: phaseOneDigest(raw), raw: raw}
	if err := putPhaseOneArtifact(root, artifact); err != nil {
		t.Fatal(err)
	}
	if err := putPhaseOneArtifact(root, artifact); err != nil {
		t.Fatalf("exact replay: %v", err)
	}
	path := filepath.Join(root, artifact.kind, artifact.digest[len("sha256:"):]+".bin")
	stored, err := os.ReadFile(path)
	if err != nil || !bytes.Equal(stored, raw) {
		t.Fatalf("stored artifact = %q, %v", stored, err)
	}
	changed := artifact
	changed.raw = []byte("different\n")
	if err := putPhaseOneArtifact(root, changed); err == nil {
		t.Fatal("digest collision was accepted")
	}
}

func phaseOnePublicationOutput() Output {
	return Output{
		Schema:                OutputSchemaV3,
		OrgID:                 "org-test",
		AppInstallationID:     "01900000-0000-7000-8000-000000000001",
		CatalogRevision:       7,
		SchemaGeneration:      2,
		EngineGeneration:      3,
		SecurityEpoch:         11,
		CatalogSnapshotDigest: digestOf('7'),
	}
}
