package freshness

import (
	"context"
	"testing"
	"time"

	"github.com/jackc/pgx/v5"
	"github.com/stretchr/testify/require"
)

type fakeRow struct {
	scan func(dest ...any) error
}

func (r fakeRow) Scan(dest ...any) error {
	return r.scan(dest...)
}

func TestBuildSnapshotHealthyState(t *testing.T) {
	now := time.Date(2026, 4, 10, 22, 10, 16, 0, time.UTC)
	call := 0
	queryRow := func(_ context.Context, _ string, _ ...any) pgx.Row {
		call++
		switch call {
		case 1:
			return fakeRow{scan: func(dest ...any) error {
				*dest[0].(*int64) = 200
				*dest[1].(*time.Time) = now.Add(-2 * time.Second)
				return nil
			}}
		case 2:
			return fakeRow{scan: func(dest ...any) error {
				*dest[0].(*string) = "0xabc"
				*dest[1].(*int64) = 198
				*dest[2].(*time.Time) = now.Add(-5 * time.Second)
				return nil
			}}
		case 3:
			return fakeRow{scan: func(dest ...any) error {
				*dest[0].(*int64) = 198
				*dest[1].(*time.Time) = now.Add(-5 * time.Second)
				return nil
			}}
		case 4:
			return fakeRow{scan: func(dest ...any) error {
				*dest[0].(*int64) = 198
				*dest[1].(*time.Time) = now.Add(-5 * time.Second)
				return nil
			}}
		case 5:
			return fakeRow{scan: func(dest ...any) error {
				*dest[0].(*int64) = 128
				*dest[1].(*int64) = 12
				*dest[2].(*int64) = 34
				return nil
			}}
		default:
			t.Fatalf("unexpected call %d", call)
			return nil
		}
	}

	probe := func(context.Context) (*Reference, error) {
		ts := now.Add(-1 * time.Second).Format(time.RFC3339)
		age := int64(1)
		block := int64(200)
		return &Reference{
			BlockNumber:  &block,
			Timestamp:    &ts,
			AgeSeconds:   &age,
			Source:       SourceReported,
			Confidence:   ConfidenceHigh,
			Provenance:   ProvenanceRPC,
			Completeness: CompletenessComplete,
		}, nil
	}

	snapshot, completeness, sampling, diagnostics, err := BuildSnapshot(context.Background(), 138, queryRow, probe, now, nil, nil)
	require.NoError(t, err)
	require.Equal(t, int64(200), *snapshot.ChainHead.BlockNumber)
	require.Equal(t, int64(198), *snapshot.LatestIndexedTransaction.BlockNumber)
	require.Equal(t, int64(2), *snapshot.LatestNonEmptyBlock.DistanceFromHead)
	require.Equal(t, CompletenessComplete, completeness.TransactionsFeed)
	require.NotNil(t, sampling.StatsGeneratedAt)
	require.Equal(t, "active", diagnostics.ActivityState)
}

func TestBuildSnapshotFreshHeadStaleTransactionVisibility(t *testing.T) {
	now := time.Date(2026, 4, 11, 0, 10, 16, 0, time.UTC)
	call := 0
	queryRow := func(_ context.Context, _ string, _ ...any) pgx.Row {
		call++
		switch call {
		case 1:
			return fakeRow{scan: func(dest ...any) error {
				*dest[0].(*int64) = 3875999
				*dest[1].(*time.Time) = now.Add(-3 * time.Second)
				return nil
			}}
		case 2:
			return fakeRow{scan: func(dest ...any) error {
				*dest[0].(*string) = "0xstale"
				*dest[1].(*int64) = 3860660
				*dest[2].(*time.Time) = now.Add(-(9*time.Hour + 8*time.Minute))
				return nil
			}}
		case 3:
			return fakeRow{scan: func(dest ...any) error {
				*dest[0].(*int64) = 3860660
				*dest[1].(*time.Time) = now.Add(-(9*time.Hour + 8*time.Minute))
				return nil
			}}
		case 4:
			return fakeRow{scan: func(dest ...any) error {
				*dest[0].(*int64) = 3875998
				*dest[1].(*time.Time) = now.Add(-4 * time.Second)
				return nil
			}}
		case 5:
			return fakeRow{scan: func(dest ...any) error {
				*dest[0].(*int64) = 128
				*dest[1].(*int64) = 3
				*dest[2].(*int64) = 9
				return nil
			}}
		default:
			t.Fatalf("unexpected call %d", call)
			return nil
		}
	}

	probe := func(context.Context) (*Reference, error) {
		ts := now.Add(-1 * time.Second).Format(time.RFC3339)
		age := int64(1)
		block := int64(3876000)
		return &Reference{
			BlockNumber:  &block,
			Timestamp:    &ts,
			AgeSeconds:   &age,
			Source:       SourceReported,
			Confidence:   ConfidenceHigh,
			Provenance:   ProvenanceRPC,
			Completeness: CompletenessComplete,
		}, nil
	}

	snapshot, completeness, _, diagnostics, err := BuildSnapshot(context.Background(), 138, queryRow, probe, now, nil, nil)
	require.NoError(t, err)
	require.Equal(t, int64(15340), *snapshot.LatestNonEmptyBlock.DistanceFromHead)
	require.Equal(t, CompletenessStale, completeness.TransactionsFeed)
	require.Equal(t, CompletenessComplete, completeness.BlocksFeed)
	require.Equal(t, "fresh_head_stale_transaction_visibility", diagnostics.ActivityState)
}

func TestBuildSnapshotQuietChainButCurrent(t *testing.T) {
	now := time.Date(2026, 4, 10, 23, 10, 16, 0, time.UTC)
	call := 0
	queryRow := func(_ context.Context, _ string, _ ...any) pgx.Row {
		call++
		switch call {
		case 1:
			return fakeRow{scan: func(dest ...any) error {
				*dest[0].(*int64) = 3875000
				*dest[1].(*time.Time) = now.Add(-1 * time.Second)
				return nil
			}}
		case 2:
			return fakeRow{scan: func(dest ...any) error {
				*dest[0].(*string) = "0xquiet"
				*dest[1].(*int64) = 3874902
				*dest[2].(*time.Time) = now.Add(-512 * time.Second)
				return nil
			}}
		case 3:
			return fakeRow{scan: func(dest ...any) error {
				*dest[0].(*int64) = 3874902
				*dest[1].(*time.Time) = now.Add(-512 * time.Second)
				return nil
			}}
		case 4:
			return fakeRow{scan: func(dest ...any) error {
				*dest[0].(*int64) = 3874902
				*dest[1].(*time.Time) = now.Add(-512 * time.Second)
				return nil
			}}
		case 5:
			return fakeRow{scan: func(dest ...any) error {
				*dest[0].(*int64) = 128
				*dest[1].(*int64) = 0
				*dest[2].(*int64) = 0
				return nil
			}}
		default:
			t.Fatalf("unexpected call %d", call)
			return nil
		}
	}

	probe := func(context.Context) (*Reference, error) {
		ts := now.Add(-1 * time.Second).Format(time.RFC3339)
		age := int64(1)
		block := int64(3875000)
		return &Reference{
			BlockNumber:  &block,
			Timestamp:    &ts,
			AgeSeconds:   &age,
			Source:       SourceReported,
			Confidence:   ConfidenceHigh,
			Provenance:   ProvenanceRPC,
			Completeness: CompletenessComplete,
		}, nil
	}

	snapshot, completeness, _, diagnostics, err := BuildSnapshot(context.Background(), 138, queryRow, probe, now, nil, nil)
	require.NoError(t, err)
	require.Equal(t, int64(98), *snapshot.LatestNonEmptyBlock.DistanceFromHead)
	require.Equal(t, CompletenessComplete, completeness.TransactionsFeed)
	require.Equal(t, "quiet_chain", diagnostics.ActivityState)
}

func TestBuildSnapshotUnknownFieldsRemainNullSafe(t *testing.T) {
	queryRow := func(_ context.Context, _ string, _ ...any) pgx.Row {
		return fakeRow{scan: func(dest ...any) error {
			return pgx.ErrNoRows
		}}
	}

	snapshot, completeness, sampling, diagnostics, err := BuildSnapshot(context.Background(), 138, queryRow, nil, time.Now().UTC(), nil, nil)
	require.NoError(t, err)
	require.Nil(t, snapshot.ChainHead.BlockNumber)
	require.Equal(t, CompletenessUnavailable, completeness.TransactionsFeed)
	require.NotNil(t, sampling.StatsGeneratedAt)
	require.Equal(t, "limited_observability", diagnostics.ActivityState)
}
