package reposync

import (
	"context"
	"testing"
)

type staticInventory struct {
	objects []InventoryObject
	digest  string
}

type emptyCheckedInventory struct {
	staticInventory
	inventoryCalls int
}

func (s *emptyCheckedInventory) IsEmpty(context.Context) (bool, error) { return true, nil }
func (s *emptyCheckedInventory) Inventory(context.Context) ([]InventoryObject, string, error) {
	s.inventoryCalls++
	return nil, "should-not-be-used", nil
}

func (s staticInventory) Inventory(context.Context) ([]InventoryObject, string, error) {
	return s.objects, s.digest, nil
}

func TestExecuteSkipsTargetInventoryAfterAuthoritativeEmptyCheck(t *testing.T) {
	source := staticInventory{digest: "source", objects: []InventoryObject{{ObjectType: "labels", Identity: "1", ObjectKey: "label:1", ContentHash: "hash"}}}
	target := &emptyCheckedInventory{}
	result, err := Execute(context.Background(), source, target, &recordingSink{}, nil, EngineOptions{DryRun: true})
	if err != nil {
		t.Fatal(err)
	}
	if result.PlannerMode != PlannerModeBootstrap || target.inventoryCalls != 0 || result.TargetInventoryDigest != EmptyInventoryDigest() {
		t.Fatalf("result=%#v inventory calls=%d", result, target.inventoryCalls)
	}
}

type recordingSink struct {
	batches  []PhysicalBatch
	verified []string
}

func (s *recordingSink) ApplyBatch(_ context.Context, batch PhysicalBatch) error {
	s.batches = append(s.batches, batch)
	return nil
}

func (s *recordingSink) Verify(_ context.Context, item PlannedItem) error {
	s.verified = append(s.verified, item.ObjectKey)
	return nil
}

func TestExecuteSelectsBulkBootstrapAndVerifiesEveryObject(t *testing.T) {
	source := staticInventory{digest: "source", objects: []InventoryObject{
		{ObjectType: "issues", Identity: "1", ObjectKey: "issue:1", ContentHash: "a", PayloadBytes: 6},
		{ObjectType: "issues", Identity: "2", ObjectKey: "issue:2", ContentHash: "b", PayloadBytes: 6},
	}}
	sink := &recordingSink{}
	result, err := Execute(context.Background(), source, staticInventory{digest: "empty"}, sink, nil, EngineOptions{
		BatchLimits: BatchLimits{MaxItems: 10, MaxBytes: 10},
	})
	if err != nil {
		t.Fatalf("Execute: %v", err)
	}
	if result.PlannerMode != PlannerModeBootstrap || len(result.Batches) != 2 {
		t.Fatalf("result = %#v", result)
	}
	if len(sink.batches) != 2 || len(sink.verified) != 2 {
		t.Fatalf("sink batches=%d verified=%d", len(sink.batches), len(sink.verified))
	}
}

func TestExecuteNonEmptyTargetUsesDiffAndDryRunDoesNotWrite(t *testing.T) {
	source := staticInventory{objects: []InventoryObject{{ObjectType: "labels", Identity: "s", ObjectKey: "label:bug", UniqueKey: "bug", ContentHash: "new"}}}
	target := staticInventory{objects: []InventoryObject{{ObjectType: "labels", Identity: "t", ObjectKey: "label:bug", UniqueKey: "bug", ContentHash: "old"}}}
	sink := &recordingSink{}
	result, err := Execute(context.Background(), source, target, sink, []LinkView{{ID: 7, ObjectType: "labels", SourceIdentity: "s", TargetIdentity: "t"}}, EngineOptions{DryRun: true})
	if err != nil {
		t.Fatalf("Execute: %v", err)
	}
	if result.PlannerMode != PlannerModeCurrentState || len(result.Items) != 1 || result.Items[0].Action != ActionUpdate {
		t.Fatalf("result = %#v", result)
	}
	if len(sink.batches) != 0 || len(sink.verified) != 0 {
		t.Fatal("dry run performed writes")
	}
}
