package store

import (
	"context"
	"errors"
	"testing"
	"time"
)

func int64ptr(v int64) *int64 { return &v }
func boolptr(v bool) *bool    { return &v }

func TestHostRepoStatsRoundTrip(t *testing.T) {
	t.Parallel()
	s := openTestStore(t)
	ctx := context.Background()

	const hostID = "h-stats-test"
	seedHost(t, s, hostID)

	// 1. Initial upsert: set TotalSizeBytes only.
	if err := s.UpsertHostRepoStats(ctx, hostID, HostRepoStats{
		TotalSizeBytes: int64ptr(100),
	}); err != nil {
		t.Fatalf("upsert 1: %v", err)
	}
	got, err := s.GetHostRepoStats(ctx, hostID)
	if err != nil {
		t.Fatalf("get after upsert 1: %v", err)
	}
	if got.TotalSizeBytes == nil || *got.TotalSizeBytes != 100 {
		t.Errorf("TotalSizeBytes: want 100, got %v", got.TotalSizeBytes)
	}
	if got.LastCheckStatus != "" {
		t.Errorf("LastCheckStatus should be empty after first upsert, got %q", got.LastCheckStatus)
	}

	// 2. Upsert with LastCheckStatus; TotalSizeBytes must be preserved.
	if err := s.UpsertHostRepoStats(ctx, hostID, HostRepoStats{
		LastCheckStatus: "ok",
	}); err != nil {
		t.Fatalf("upsert 2: %v", err)
	}
	got, err = s.GetHostRepoStats(ctx, hostID)
	if err != nil {
		t.Fatalf("get after upsert 2: %v", err)
	}
	if got.TotalSizeBytes == nil || *got.TotalSizeBytes != 100 {
		t.Errorf("TotalSizeBytes should still be 100 after second upsert, got %v", got.TotalSizeBytes)
	}
	if got.LastCheckStatus != "ok" {
		t.Errorf("LastCheckStatus: want %q, got %q", "ok", got.LastCheckStatus)
	}

	// 3. Upsert with LockPresent=true; all other fields preserved.
	now := time.Now().UTC().Truncate(time.Second)
	if err := s.UpsertHostRepoStats(ctx, hostID, HostRepoStats{
		LockPresent: boolptr(true),
		LastCheckAt: &now,
	}); err != nil {
		t.Fatalf("upsert 3: %v", err)
	}
	got, err = s.GetHostRepoStats(ctx, hostID)
	if err != nil {
		t.Fatalf("get after upsert 3: %v", err)
	}
	if got.LockPresent == nil || !*got.LockPresent {
		t.Error("LockPresent should be true after upsert 3")
	}
	if got.TotalSizeBytes == nil || *got.TotalSizeBytes != 100 {
		t.Errorf("TotalSizeBytes still 100 expected, got %v", got.TotalSizeBytes)
	}
	if got.LastCheckStatus != "ok" {
		t.Errorf("LastCheckStatus still 'ok' expected, got %q", got.LastCheckStatus)
	}
	if got.LastCheckAt == nil {
		t.Error("LastCheckAt should be set")
	} else if !got.LastCheckAt.UTC().Truncate(time.Second).Equal(now) {
		t.Errorf("LastCheckAt: got %v, want %v", got.LastCheckAt.UTC().Truncate(time.Second), now)
	}

	// 4. Clear lock (set to false).
	if err := s.UpsertHostRepoStats(ctx, hostID, HostRepoStats{
		LockPresent: boolptr(false),
	}); err != nil {
		t.Fatalf("upsert 4: %v", err)
	}
	got, err = s.GetHostRepoStats(ctx, hostID)
	if err != nil {
		t.Fatalf("get after upsert 4: %v", err)
	}
	if got.LockPresent == nil || *got.LockPresent {
		t.Error("LockPresent should be false after upsert 4")
	}
}

func TestHostRepoStatsNotFound(t *testing.T) {
	t.Parallel()
	s := openTestStore(t)
	ctx := context.Background()

	_, err := s.GetHostRepoStats(ctx, "no-such-host")
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("expected ErrNotFound, got %v", err)
	}
}

func TestHostRepoStatsCascadeDelete(t *testing.T) {
	t.Parallel()
	s := openTestStore(t)
	ctx := context.Background()

	const hostID = "h-cascade-test"
	seedHost(t, s, hostID)

	if err := s.UpsertHostRepoStats(ctx, hostID, HostRepoStats{
		TotalSizeBytes: int64ptr(999),
	}); err != nil {
		t.Fatalf("upsert: %v", err)
	}

	// Delete the host; stats row should cascade-delete.
	if _, err := s.DB().ExecContext(ctx,
		`DELETE FROM hosts WHERE id = ?`, hostID); err != nil {
		t.Fatalf("delete host: %v", err)
	}
	_, err := s.GetHostRepoStats(ctx, hostID)
	if !errors.Is(err, ErrNotFound) {
		t.Errorf("after host delete, expected ErrNotFound for stats; got %v", err)
	}
}
