package keyring

import (
	"errors"
	"fmt"
	"testing"
	"time"

	keyring "github.com/zalando/go-keyring"
)

func TestKeyringTokenStore(t *testing.T) {
	// Probe whether the OS keyring is available. If not, skip gracefully
	// (CI Linux headless, Docker containers without Secret Service).
	probeService := fmt.Sprintf("fn_registry.test.probe.%d", time.Now().UnixNano())
	probeErr := keyring.Set(probeService, "probe", "ok")
	if probeErr != nil {
		t.Skipf("keyring not available (headless/CI): %v", probeErr)
	}
	// Clean up the probe entry.
	_ = keyring.Delete(probeService, "probe")

	// Use a timestamped service name so parallel test runs don't collide.
	service := fmt.Sprintf("fn_registry.test.%d", time.Now().UnixNano())
	store := NewKeyringTokenStore(service)

	sampleToken := Token{
		AccessToken:   "mxat_test_access",
		RefreshToken:  "mxrt_test_refresh",
		ExpiresAt:     time.Now().Add(time.Hour).UTC().Truncate(time.Second),
		UserID:        "@testuser:matrix.example.com",
		DeviceID:      "TESTDEV01",
		HomeserverURL: "https://matrix.example.com",
		Issuer:        "https://auth.example.com/",
		ClientID:      "TESTCLIENT123",
	}

	t.Run("Save then Load returns matching token", func(t *testing.T) {
		account := sampleToken.UserID
		t.Cleanup(func() { _ = store.Delete(account) })

		if err := store.Save(account, sampleToken); err != nil {
			t.Fatalf("Save: %v", err)
		}
		got, err := store.Load(account)
		if err != nil {
			t.Fatalf("Load: %v", err)
		}
		if got.AccessToken != sampleToken.AccessToken {
			t.Errorf("AccessToken: got %q, want %q", got.AccessToken, sampleToken.AccessToken)
		}
		if got.RefreshToken != sampleToken.RefreshToken {
			t.Errorf("RefreshToken: got %q, want %q", got.RefreshToken, sampleToken.RefreshToken)
		}
		if !got.ExpiresAt.Equal(sampleToken.ExpiresAt) {
			t.Errorf("ExpiresAt: got %v, want %v", got.ExpiresAt, sampleToken.ExpiresAt)
		}
		if got.UserID != sampleToken.UserID {
			t.Errorf("UserID: got %q, want %q", got.UserID, sampleToken.UserID)
		}
		if got.DeviceID != sampleToken.DeviceID {
			t.Errorf("DeviceID: got %q, want %q", got.DeviceID, sampleToken.DeviceID)
		}
		if got.HomeserverURL != sampleToken.HomeserverURL {
			t.Errorf("HomeserverURL: got %q, want %q", got.HomeserverURL, sampleToken.HomeserverURL)
		}
		if got.Issuer != sampleToken.Issuer {
			t.Errorf("Issuer: got %q, want %q", got.Issuer, sampleToken.Issuer)
		}
		if got.ClientID != sampleToken.ClientID {
			t.Errorf("ClientID: got %q, want %q", got.ClientID, sampleToken.ClientID)
		}
	})

	t.Run("Load nonexistent returns ErrNotFound", func(t *testing.T) {
		_, err := store.Load("@nobody:missing.example.com")
		if !errors.Is(err, ErrNotFound) {
			t.Errorf("expected ErrNotFound, got: %v", err)
		}
	})

	t.Run("Save then Delete then Load returns ErrNotFound", func(t *testing.T) {
		account := "@delete_me:matrix.example.com"
		if err := store.Save(account, sampleToken); err != nil {
			t.Fatalf("Save: %v", err)
		}
		if err := store.Delete(account); err != nil {
			t.Fatalf("Delete: %v", err)
		}
		_, err := store.Load(account)
		if !errors.Is(err, ErrNotFound) {
			t.Errorf("expected ErrNotFound after Delete, got: %v", err)
		}
	})

	t.Run("Delete nonexistent is idempotent", func(t *testing.T) {
		if err := store.Delete("@nonexistent:matrix.example.com"); err != nil {
			t.Errorf("Delete of nonexistent should not error, got: %v", err)
		}
	})

	t.Run("Save twice overwrites with second token", func(t *testing.T) {
		account := "@overwrite_me:matrix.example.com"
		t.Cleanup(func() { _ = store.Delete(account) })

		first := sampleToken
		first.AccessToken = "mxat_first_version"
		if err := store.Save(account, first); err != nil {
			t.Fatalf("Save (first): %v", err)
		}

		second := sampleToken
		second.AccessToken = "mxat_second_version"
		if err := store.Save(account, second); err != nil {
			t.Fatalf("Save (second): %v", err)
		}

		got, err := store.Load(account)
		if err != nil {
			t.Fatalf("Load: %v", err)
		}
		if got.AccessToken != second.AccessToken {
			t.Errorf("overwrite: got AccessToken %q, want %q", got.AccessToken, second.AccessToken)
		}
	})
}
