package catalogreconcile

import (
	"context"
	"fmt"

	"github.com/nexuscrm/metadata/catalogsnapshot"
)

// ArtifactSelection binds the Catalog operation and candidate HandlerBinding
// to one exact Runtime view. OwnershipEpoch is the epoch that will be staged,
// not the preceding compare-and-swap value.
type ArtifactSelection struct {
	Metadata             MetadataSelection
	AppReleaseDigest     catalogsnapshot.Digest
	AppCapabilityID      string
	WorkloadID           string
	ServiceReleaseDigest catalogsnapshot.Digest
	MetadataSourceDigest catalogsnapshot.Digest
	OwnershipEpoch       uint64
	SecurityEpoch        uint64
}

type ArtifactResult struct {
	EffectiveMetadataDigest    catalogsnapshot.Digest
	CatalogSnapshotDigest      catalogsnapshot.Digest
	RuntimeMetadataViewDigest  catalogsnapshot.Digest
	CompatibilityReceiptDigest catalogsnapshot.Digest
}

type preparedArtifacts struct {
	result    ArtifactResult
	artifacts []catalogsnapshot.Artifact
}

// BuildAndStoreArtifacts produces the immutable artifact chain and durably
// stores every serving artifact before the caller may stage a HandlerBinding.
// The store is content-addressed and has no current/latest selector.
func BuildAndStoreArtifacts(
	ctx context.Context,
	store catalogsnapshot.Store,
	effective catalogsnapshot.EffectiveMetadata,
	selection ArtifactSelection,
) (ArtifactResult, error) {
	if store == nil {
		return ArtifactResult{}, fmt.Errorf("Catalog snapshot store is required")
	}
	prepared, err := prepareArtifacts(effective, selection)
	if err != nil {
		return ArtifactResult{}, err
	}
	if err := storePreparedArtifacts(ctx, store, prepared); err != nil {
		return ArtifactResult{}, err
	}
	return prepared.result, nil
}

// prepareArtifacts closes the exact content-addressed chain without making it
// externally durable. Reconcile publishes the CatalogSnapshot first, commits
// that digest as the operation's sole metadata selection, and only then
// publishes the derived view and receipt.
func prepareArtifacts(
	effective catalogsnapshot.EffectiveMetadata,
	selection ArtifactSelection,
) (preparedArtifacts, error) {
	snapshotArtifact, err := catalogsnapshot.CompileEffectiveMetadata(effective)
	if err != nil {
		return preparedArtifacts{}, fmt.Errorf("compile Catalog snapshot: %w", err)
	}
	return prepareArtifactsFromSnapshot(snapshotArtifact, effective.Digest(), selection)
}

func prepareArtifactsFromCommittedSnapshot(
	ctx context.Context,
	store catalogsnapshot.Store,
	digest catalogsnapshot.Digest,
	selection ArtifactSelection,
) (preparedArtifacts, error) {
	if store == nil {
		return preparedArtifacts{}, fmt.Errorf("Catalog snapshot store is required")
	}
	snapshotArtifact, err := store.Get(ctx, catalogsnapshot.ArtifactCatalogSnapshot, digest)
	if err != nil {
		return preparedArtifacts{}, fmt.Errorf("read committed Catalog snapshot: %w", err)
	}
	return prepareArtifactsFromSnapshot(snapshotArtifact, "", selection)
}

func prepareArtifactsFromSnapshot(
	snapshotArtifact catalogsnapshot.Artifact,
	effectiveMetadataDigest catalogsnapshot.Digest,
	selection ArtifactSelection,
) (preparedArtifacts, error) {
	if err := validateSelection(selection.Metadata); err != nil {
		return preparedArtifacts{}, err
	}
	if selection.OwnershipEpoch == 0 || selection.SecurityEpoch == 0 {
		return preparedArtifacts{}, fmt.Errorf("ownership and security epochs must be explicit and non-zero")
	}

	snapshot, err := catalogsnapshot.OpenCatalogSnapshot(snapshotArtifact)
	if err != nil {
		return preparedArtifacts{}, fmt.Errorf("open compiled Catalog snapshot: %w", err)
	}
	wantLineage := catalogsnapshot.Lineage{
		CatalogRevision:  catalogsnapshot.CatalogRevision(selection.Metadata.CatalogRevision),
		SchemaGeneration: catalogsnapshot.SchemaGeneration(selection.Metadata.SchemaGeneration),
		EngineGeneration: catalogsnapshot.EngineGeneration(selection.Metadata.EngineGeneration),
	}
	if snapshot.OrgID() != selection.Metadata.OrgID || snapshot.Lineage() != wantLineage {
		return preparedArtifacts{}, fmt.Errorf("effective metadata lineage does not match the Catalog selection")
	}
	viewArtifact, err := catalogsnapshot.ProjectRuntimeMetadataView(snapshot, catalogsnapshot.ProjectionKey{
		AppInstallationID: selection.Metadata.AppInstallationID,
		CapabilityID:      selection.Metadata.RuntimeCapabilityID,
	})
	if err != nil {
		return preparedArtifacts{}, fmt.Errorf("project Runtime metadata view: %w", err)
	}
	view, err := catalogsnapshot.OpenRuntimeMetadataView(viewArtifact)
	if err != nil {
		return preparedArtifacts{}, fmt.Errorf("open projected Runtime metadata view: %w", err)
	}
	if err := view.VerifySource(snapshot); err != nil {
		return preparedArtifacts{}, fmt.Errorf("verify Runtime view source: %w", err)
	}
	// App release and capability identity belong to the serving receipt below.
	// Do not manufacture a Definition component from those values: effective
	// metadata may also be authored by other Apps or by an administrator.
	receiptArtifact, err := catalogsnapshot.BuildServingCompatibilityReceipt(
		catalogsnapshot.ServingCompatibilityReceiptInput{
			OrgID:                     selection.Metadata.OrgID,
			AppInstallationID:         selection.Metadata.AppInstallationID,
			AppReleaseDigest:          selection.AppReleaseDigest,
			AppCapabilityID:           selection.AppCapabilityID,
			RuntimeCapabilityID:       selection.Metadata.RuntimeCapabilityID,
			WorkloadID:                selection.WorkloadID,
			ServiceReleaseDigest:      selection.ServiceReleaseDigest,
			MetadataSourceDigest:      selection.MetadataSourceDigest,
			OwnershipEpoch:            selection.OwnershipEpoch,
			CatalogSnapshotDigest:     snapshotArtifact.Digest(),
			RuntimeMetadataViewDigest: viewArtifact.Digest(),
			Lineage:                   wantLineage,
			SecurityEpoch:             selection.SecurityEpoch,
		},
	)
	if err != nil {
		return preparedArtifacts{}, fmt.Errorf("build serving compatibility receipt: %w", err)
	}
	return preparedArtifacts{
		result: ArtifactResult{
			EffectiveMetadataDigest:    effectiveMetadataDigest,
			CatalogSnapshotDigest:      snapshotArtifact.Digest(),
			RuntimeMetadataViewDigest:  viewArtifact.Digest(),
			CompatibilityReceiptDigest: receiptArtifact.Digest(),
		},
		artifacts: []catalogsnapshot.Artifact{snapshotArtifact, viewArtifact, receiptArtifact},
	}, nil
}

func storePreparedCatalogSnapshot(ctx context.Context, store catalogsnapshot.Store, prepared preparedArtifacts) error {
	if len(prepared.artifacts) != 3 || prepared.artifacts[0].Kind() != catalogsnapshot.ArtifactCatalogSnapshot {
		return fmt.Errorf("prepared artifact chain has no Catalog snapshot root")
	}
	if store == nil {
		return fmt.Errorf("Catalog snapshot store is required")
	}
	if err := store.Put(ctx, prepared.artifacts[0]); err != nil {
		return fmt.Errorf("store %s artifact: %w", prepared.artifacts[0].Kind(), err)
	}
	return nil
}

func storePreparedDerivedArtifacts(ctx context.Context, store catalogsnapshot.Store, prepared preparedArtifacts) error {
	if len(prepared.artifacts) != 3 {
		return fmt.Errorf("prepared artifact chain is incomplete")
	}
	if store == nil {
		return fmt.Errorf("Catalog snapshot store is required")
	}
	for _, artifact := range prepared.artifacts[1:] {
		if err := store.Put(ctx, artifact); err != nil {
			return fmt.Errorf("store %s artifact: %w", artifact.Kind(), err)
		}
	}
	return nil
}

func storePreparedArtifacts(ctx context.Context, store catalogsnapshot.Store, prepared preparedArtifacts) error {
	if store == nil {
		return fmt.Errorf("Catalog snapshot store is required")
	}
	for _, artifact := range prepared.artifacts {
		if err := store.Put(ctx, artifact); err != nil {
			return fmt.Errorf("store %s artifact: %w", artifact.Kind(), err)
		}
	}
	return nil
}
