package controlplane

import (
	"context"
	"errors"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/pingcap-inc/nexuscrm-controlplane/packs"
)

func TestReadApplyAllSourceRevisionAdvanceCurrentSnapshotCapturesWholeTuple(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	raw := []SourceRevisionAdvanceAuthorization{
		{Name: "advance", Kind: "in_repo", URI: "advance", FromRevision: "advance-old", ToRevision: "advance-new"},
		{Name: "retry", Kind: string(packs.PackSourceKindOfficialRepo), URI: "retry", FromRevision: "retry-old", ToRevision: "retry-new"},
		{Name: "installing", Kind: string(packs.PackSourceKindOfficialRepo), URI: "installing", FromRevision: "installing-old", ToRevision: "installing-new"},
		{Name: "canonicalize", Kind: string(packs.PackSourceKindOfficialRepo), URI: "canonicalize", FromRevision: "canonicalize-old", ToRevision: "canonicalize-new"},
		{Name: "current", Kind: string(packs.PackSourceKindOfficialRepo), URI: "current", FromRevision: "current-old", ToRevision: "current-new"},
	}
	plan, err := normalizeApplyAllSourceRevisionAdvancePlan(raw)
	if err != nil {
		t.Fatalf("normalize plan: %v", err)
	}
	expectApplyAllSourceRevisionAdvanceRow(mock, "advance", installedPackStatusActive, string(packs.PackSourceKindOfficialRepo), "advance-old")
	expectApplyAllSourceRevisionAdvanceRow(mock, "retry", installedPackStatusFailed, string(packs.PackSourceKindOfficialRepo), "retry-new")
	expectApplyAllSourceRevisionAdvanceRow(mock, "installing", installedPackStatusInstalling, string(packs.PackSourceKindOfficialRepo), "installing-new")
	expectApplyAllSourceRevisionAdvanceRow(mock, "canonicalize", installedPackStatusActive, "in_repo", "canonicalize-new")
	expectApplyAllSourceRevisionAdvanceRow(mock, "current", installedPackStatusActive, string(packs.PackSourceKindOfficialRepo), "current-new")

	snapshot, err := readApplyAllSourceRevisionAdvanceCurrentSnapshot(context.Background(), db, plan)
	if err != nil {
		t.Fatalf("read current snapshot: %v", err)
	}
	if len(snapshot.entries) != 5 {
		t.Fatalf("snapshot entries = %d, want 5", len(snapshot.entries))
	}
	revisions := snapshot.currentRevisions()
	wantRevisions := map[string]string{
		"advance": "advance-old", "retry": "retry-new", "installing": "installing-new",
		"canonicalize": "canonicalize-new", "current": "current-new",
	}
	for name, want := range wantRevisions {
		if got := revisions[name]; got != want {
			t.Fatalf("source revision %s = %q, want %q", name, got, want)
		}
		entry, ok := snapshot.entry(name)
		if !ok {
			t.Fatalf("snapshot entry %s is missing", name)
		}
		row := entry.row
		if row.Name != name || !row.AppliedPayloadHash.Valid || row.AppliedPayloadHash.String != "old-hash" {
			t.Fatalf("installed row %s = %#v, want complete queried row", name, row)
		}
	}
	wantPending := []ApplyAllSourceRevisionAdvancePendingKind{
		ApplyAllSourceRevisionAdvancePendingAdvance,
		ApplyAllSourceRevisionAdvancePendingRetry,
		ApplyAllSourceRevisionAdvancePendingRetry,
		ApplyAllSourceRevisionAdvancePendingCanonicalize,
		"",
	}
	for index, entry := range snapshot.entries {
		if entry.advance.Name != raw[index].Name {
			t.Fatalf("entry %d name = %q, want input order %q", index, entry.advance.Name, raw[index].Name)
		}
		if wantPending[index] == "" {
			if entry.pending != nil {
				t.Fatalf("entry %d pending = %#v, want nil", index, entry.pending)
			}
			continue
		}
		if entry.pending == nil || entry.pending.Kind != wantPending[index] {
			t.Fatalf("entry %d pending = %#v, want %q", index, entry.pending, wantPending[index])
		}
	}
	revisions["advance"] = "caller-mutated"
	if got := snapshot.currentRevisions()["advance"]; got != "advance-old" {
		t.Fatalf("fresh current revisions map = %q, want advance-old", got)
	}
	plan[0].Name = "caller-mutated"
	if snapshot.entries[0].advance.Name != "advance" {
		t.Fatal("snapshot plan aliases caller plan")
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestReadApplyAllSourceRevisionAdvanceCurrentSnapshotReturnsNoPartialState(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	plan, err := normalizeApplyAllSourceRevisionAdvancePlan([]SourceRevisionAdvanceAuthorization{
		{Name: "valid", Kind: string(packs.PackSourceKindOfficialRepo), URI: "valid", FromRevision: "old", ToRevision: "new"},
		{Name: "invalid", Kind: string(packs.PackSourceKindOfficialRepo), URI: "invalid", FromRevision: "old", ToRevision: "new"},
	})
	if err != nil {
		t.Fatalf("normalize plan: %v", err)
	}
	expectApplyAllSourceRevisionAdvanceRow(mock, "valid", installedPackStatusActive, string(packs.PackSourceKindOfficialRepo), "old")
	expectApplyAllSourceRevisionAdvanceRow(mock, "invalid", installedPackStatusActive, string(packs.PackSourceKindOfficialRepo), "unexpected")

	snapshot, err := readApplyAllSourceRevisionAdvanceCurrentSnapshot(context.Background(), db, plan)
	var planErr *ApplyAllSourceRevisionAdvancePlanError
	if !errors.As(err, &planErr) || planErr.Stage != ApplyAllSourceRevisionAdvancePlanState || planErr.Index != 1 || planErr.Name != "invalid" {
		t.Fatalf("error = %v, want typed state error for plan entry 1", err)
	}
	if snapshot.entries != nil {
		t.Fatalf("failed snapshot = %#v, want zero value", snapshot)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}
