package auth

import (
	"bytes"
	"context"
	"errors"
	"log/slog"
	"strings"
	"testing"

	"github.com/TaruLab/keluargamart-backend/pkg/logger"
)

// loggedCtx returns a context carrying a logger that writes to the buffer, the
// same slog-over-bytes.Buffer idiom as internal/integration/migrate_test.go.
func loggedCtx(buf *bytes.Buffer) context.Context {
	return logger.ToContext(context.Background(),
		slog.New(slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelWarn})))
}

// TestObserveTokenRevocation covers the DeleteTokensByUser wrapper's
// observability branch. There is no live database in this suite, so this
// exercises the wrapper's decision directly rather than a real DELETE: a
// zero-row revocation must be visible at WARN, and the store's error must come
// back untouched on both the success and the error path.
func TestObserveTokenRevocation(t *testing.T) {
	storeErr := errors.New("token delete failed")

	tests := []struct {
		name       string
		deleted    int64
		err        error
		wantErr    error
		wantLogged bool
	}{
		{"revoked some tokens", 3, nil, nil, false},
		{"revoked exactly one token", 1, nil, nil, false},
		{"zero rows is surfaced, not raised", 0, nil, nil, true},
		{"error passes through unchanged", 0, storeErr, storeErr, false},
		{"error with rows also passes through", 2, storeErr, storeErr, false},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			var buf bytes.Buffer
			gotErr := observeTokenRevocation(loggedCtx(&buf), 42, tc.deleted, tc.err)

			if !errors.Is(gotErr, tc.wantErr) {
				t.Errorf("err = %v, want %v", gotErr, tc.wantErr)
			}
			if tc.wantErr != nil && gotErr != tc.wantErr { //nolint:errorlint // identity, not classification
				t.Errorf("error not returned unchanged: got %v, want the same value %v", gotErr, tc.wantErr)
			}

			out := buf.String()
			if !tc.wantLogged {
				if out != "" {
					t.Errorf("unexpected log output: %q", out)
				}
				return
			}
			if !strings.Contains(out, "token revocation matched zero rows") {
				t.Errorf("zero-row revocation not logged at WARN: %q", out)
			}
			if !strings.Contains(out, "level=WARN") {
				t.Errorf("zero-row revocation must log at WARN: %q", out)
			}
			// The count must reach the log, or the signal is unreadable.
			if !strings.Contains(out, "user_id=42") || !strings.Contains(out, "deleted_tokens=0") {
				t.Errorf("log missing user_id=42 / deleted_tokens=0: %q", out)
			}
			// No credential material may appear in the log.
			for _, secret := range []string{"token", "hash", "password", "secret"} {
				if strings.Contains(strings.ToLower(out), secret+"=") {
					t.Errorf("log leaks %q: %q", secret, out)
				}
			}
		})
	}
}
