package worker

import (
	"context"
	"testing"

	reposync "github.com/pingcap/agent-git-service/internal/sync"
	"github.com/pingcap/agent-git-service/internal/sync/canonical"
)

type batchPreflightProvider struct {
	readCalls    int
	recoverCalls int
}

func (*batchPreflightProvider) Endpoint() reposync.Endpoint { return reposync.Endpoint{} }
func (*batchPreflightProvider) Inventory(context.Context) ([]reposync.InventoryObject, string, error) {
	return nil, "", nil
}
func (*batchPreflightProvider) Read(context.Context, string, string) (reposync.InventoryObject, error) {
	panic("per-item Read must not be used when BatchReader is available")
}
func (*batchPreflightProvider) Recover(context.Context, reposync.PlannedItem) (reposync.TargetState, error) {
	panic("per-item Recover must not be used when BatchRecoverer is available")
}
func (*batchPreflightProvider) ApplyBatch(context.Context, reposync.PhysicalBatch, reposync.Provider) ([]reposync.ApplyResult, error) {
	return nil, nil
}
func (*batchPreflightProvider) Verify(context.Context, reposync.PlannedItem, reposync.ApplyResult) (reposync.TargetState, error) {
	return reposync.TargetState{}, nil
}
func (*batchPreflightProvider) Close() error { return nil }

func (p *batchPreflightProvider) ReadBatch(_ context.Context, objectType string, identities []string) (map[string]reposync.InventoryObject, error) {
	p.readCalls++
	result := make(map[string]reposync.InventoryObject, len(identities))
	for _, identity := range identities {
		record := canonical.NewRecord(objectType, identity, "issue:"+identity, map[string]any{"title": identity})
		object, err := record.Inventory("r", "native")
		if err != nil {
			return nil, err
		}
		result[identity] = object
	}
	return result, nil
}

func (p *batchPreflightProvider) RecoverBatch(_ context.Context, items []reposync.PlannedItem) (map[string]reposync.TargetState, error) {
	p.recoverCalls++
	result := make(map[string]reposync.TargetState, len(items))
	for _, item := range items {
		result[reposync.ExecutionKey(item)] = reposync.TargetState{Identity: item.TargetIdentity}
	}
	return result, nil
}

func TestPhysicalBatchPreflightRefreshesEachEndpointOnce(t *testing.T) {
	provider := &batchPreflightProvider{}
	items := []reposync.PlannedItem{
		{ObjectType: "issue", ObjectKey: "issue:1", Action: reposync.ActionUpdate, SourceIdentity: "1", TargetIdentity: "10"},
		{ObjectType: "issue", ObjectKey: "issue:2", Action: reposync.ActionUpdate, SourceIdentity: "2", TargetIdentity: "20"},
	}
	source, err := readSourceBatch(context.Background(), provider, items, "", "")
	if err != nil {
		t.Fatal(err)
	}
	if len(source) != 2 || provider.readCalls != 1 {
		t.Fatalf("source objects=%d read batches=%d, want 2 and 1", len(source), provider.readCalls)
	}
	target, err := recoverTargetBatch(context.Background(), provider, items)
	if err != nil {
		t.Fatal(err)
	}
	if len(target) != 2 || provider.recoverCalls != 1 {
		t.Fatalf("target states=%d recovery batches=%d, want 2 and 1", len(target), provider.recoverCalls)
	}
}

func TestDeletePreflightChecksThatSourceDidNotReappear(t *testing.T) {
	provider := &batchPreflightProvider{}
	items := []reposync.PlannedItem{{
		ObjectType: "issue", ObjectKey: "issue:gone", Action: reposync.ActionDelete,
		SourceIdentity: "source-that-reappeared", TargetIdentity: "target",
	}}
	objects, err := readSourceBatch(context.Background(), provider, items, "", "")
	if err != nil {
		t.Fatal(err)
	}
	if provider.readCalls != 1 || len(objects) != 1 {
		t.Fatalf("delete preflight readCalls=%d objects=%d, want 1/1", provider.readCalls, len(objects))
	}
}
