package coverart

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

// fakeArtistProvider opts into ArtistArtProvider for capability tests.
type fakeArtistProvider struct {
	fakeProvider
}

func (f *fakeArtistProvider) FetchArtistArt(_ context.Context, _ ArtistRef) (thumb, fanart []byte, err error) {
	return []byte("t"), []byte("f"), nil
}

// fakeTestableAlbumProvider implements TestableProvider for the
// TestProvider test.
type fakeTestableAlbumProvider struct {
	fakeAlbumProvider
	testErr error
}

func (f *fakeTestableAlbumProvider) TestConnection(_ context.Context) error {
	return f.testErr
}

func TestSettingsService_BootInsertsDefaultRows(t *testing.T) {
	pool := newPool(t)
	resetRegistryForTests()
	defer resetRegistryForTests()

	Register(&fakeAlbumProvider{fakeProvider: fakeProvider{id: "fake-album", display: "Fake Album", defaultOn: true}})
	Register(&fakeArtistProvider{fakeProvider: fakeProvider{id: "fake-artist", display: "Fake Artist", defaultOn: false}})

	s, err := NewSettingsService(context.Background(), pool, discardLogger())
	if err != nil {
		t.Fatalf("NewSettingsService: %v", err)
	}

	// Both providers should now have rows; only fake-album is enabled.
	enabled := s.EnabledAlbumProviders()
	if len(enabled) != 1 {
		t.Fatalf("EnabledAlbumProviders = %d, want 1", len(enabled))
	}
	if enabled[0].ID() != "fake-album" {
		t.Errorf("enabled[0].ID = %q, want fake-album", enabled[0].ID())
	}

	// fake-artist defaultOn=false, so it should NOT be in EnabledArtistProviders.
	artists := s.EnabledArtistProviders()
	if len(artists) != 0 {
		t.Errorf("EnabledArtistProviders = %d, want 0", len(artists))
	}

	if s.CurrentVersion() != 1 {
		t.Errorf("CurrentVersion = %d, want 1", s.CurrentVersion())
	}
}

func TestSettingsService_UpdateProvider_FlippingEnabledBumpsVersion(t *testing.T) {
	pool := newPool(t)
	resetRegistryForTests()
	defer resetRegistryForTests()

	Register(&fakeAlbumProvider{fakeProvider: fakeProvider{id: "fake-album", defaultOn: true}})

	s, err := NewSettingsService(context.Background(), pool, discardLogger())
	if err != nil {
		t.Fatal(err)
	}

	startVersion := s.CurrentVersion()

	disabled := false
	bumped, err := s.UpdateProvider(context.Background(), "fake-album", ProviderUpdatePatch{Enabled: &disabled})
	if err != nil {
		t.Fatalf("UpdateProvider: %v", err)
	}
	if !bumped {
		t.Error("versionBumped = false, want true after disabling")
	}
	if s.CurrentVersion() <= startVersion {
		t.Errorf("CurrentVersion = %d, want > %d", s.CurrentVersion(), startVersion)
	}

	enabled := s.EnabledAlbumProviders()
	if len(enabled) != 0 {
		t.Errorf("after disable: EnabledAlbumProviders = %d, want 0", len(enabled))
	}
}

func TestSettingsService_UpdateProvider_KeyOnlyDoesNotBumpVersion(t *testing.T) {
	pool := newPool(t)
	resetRegistryForTests()
	defer resetRegistryForTests()

	Register(&fakeAlbumProvider{fakeProvider: fakeProvider{id: "fake-album", defaultOn: true}})

	s, err := NewSettingsService(context.Background(), pool, discardLogger())
	if err != nil {
		t.Fatal(err)
	}

	startVersion := s.CurrentVersion()

	newKey := "abc123"
	bumped, err := s.UpdateProvider(context.Background(), "fake-album", ProviderUpdatePatch{APIKey: &newKey})
	if err != nil {
		t.Fatalf("UpdateProvider: %v", err)
	}
	if bumped {
		t.Error("versionBumped = true, want false for key-only change")
	}
	if s.CurrentVersion() != startVersion {
		t.Errorf("CurrentVersion changed: %d → %d, want unchanged", startVersion, s.CurrentVersion())
	}

	infos := s.ListProviderInfo()
	var info ProviderInfo
	for _, i := range infos {
		if i.ID == "fake-album" {
			info = i
			break
		}
	}
	if !info.APIKeySet {
		t.Error("APIKeySet = false, want true after setting key")
	}
}

func TestSettingsService_UpdateProvider_ClearKey(t *testing.T) {
	pool := newPool(t)
	resetRegistryForTests()
	defer resetRegistryForTests()

	Register(&fakeAlbumProvider{fakeProvider: fakeProvider{id: "fake-album", defaultOn: true}})

	s, err := NewSettingsService(context.Background(), pool, discardLogger())
	if err != nil {
		t.Fatal(err)
	}

	// Set a key, then clear it.
	setKey := "abc"
	if _, err := s.UpdateProvider(context.Background(), "fake-album", ProviderUpdatePatch{APIKey: &setKey}); err != nil {
		t.Fatal(err)
	}
	emptyKey := ""
	if _, err := s.UpdateProvider(context.Background(), "fake-album", ProviderUpdatePatch{APIKey: &emptyKey}); err != nil {
		t.Fatal(err)
	}

	infos := s.ListProviderInfo()
	for _, i := range infos {
		if i.ID == "fake-album" && i.APIKeySet {
			t.Error("APIKeySet = true after clear, want false")
		}
	}
}

func TestSettingsService_TestProvider_Routing(t *testing.T) {
	pool := newPool(t)
	resetRegistryForTests()
	defer resetRegistryForTests()

	plain := &fakeAlbumProvider{fakeProvider: fakeProvider{id: "plain", defaultOn: true}}
	testable := &fakeTestableAlbumProvider{
		fakeAlbumProvider: fakeAlbumProvider{fakeProvider: fakeProvider{id: "testable", defaultOn: true}},
		testErr:           nil,
	}
	Register(plain)
	Register(testable)

	s, err := NewSettingsService(context.Background(), pool, discardLogger())
	if err != nil {
		t.Fatal(err)
	}

	// Testable returns nil → success.
	if err := s.TestProvider(context.Background(), "testable"); err != nil {
		t.Errorf("TestProvider(testable) = %v, want nil", err)
	}

	// Plain doesn't implement TestableProvider → ErrNotTestable.
	if err := s.TestProvider(context.Background(), "plain"); !errors.Is(err, ErrNotTestable) {
		t.Errorf("TestProvider(plain) = %v, want ErrNotTestable", err)
	}

	// Missing → ErrProviderNotFound.
	if err := s.TestProvider(context.Background(), "missing"); !errors.Is(err, ErrProviderNotFound) {
		t.Errorf("TestProvider(missing) = %v, want ErrProviderNotFound", err)
	}
}

func TestSettingsService_ListProviderInfo_Capabilities(t *testing.T) {
	pool := newPool(t)
	resetRegistryForTests()
	defer resetRegistryForTests()

	plain := &fakeProvider{id: "plain", defaultOn: true}
	album := &fakeAlbumProvider{fakeProvider: fakeProvider{id: "album-only", defaultOn: true}}
	artist := &fakeArtistProvider{fakeProvider: fakeProvider{id: "artist-only", defaultOn: true}}
	Register(plain)
	Register(album)
	Register(artist)

	s, err := NewSettingsService(context.Background(), pool, discardLogger())
	if err != nil {
		t.Fatal(err)
	}

	infos := s.ListProviderInfo()
	if len(infos) != 3 {
		t.Fatalf("ListProviderInfo len = %d, want 3", len(infos))
	}

	byID := map[string]ProviderInfo{}
	for _, i := range infos {
		byID[i.ID] = i
	}

	if got := byID["plain"].Supports; len(got) != 0 {
		t.Errorf("plain.Supports = %v, want empty", got)
	}
	if got := byID["album-only"].Supports; len(got) != 1 || got[0] != "album_cover" {
		t.Errorf("album-only.Supports = %v, want [album_cover]", got)
	}
	if got := byID["artist-only"].Supports; len(got) != 2 || got[0] != "artist_thumb" || got[1] != "artist_fanart" {
		t.Errorf("artist-only.Supports = %v, want [artist_thumb, artist_fanart]", got)
	}
}

// TestSettingsService_BumpVersionIfProvidersChanged_FirstBoot: stored
// hash is empty (column default ""), so a real provider set produces a bump.
func TestSettingsService_BumpVersionIfProvidersChanged_FirstBoot(t *testing.T) {
	pool := newPool(t)
	resetRegistryForTests()
	defer resetRegistryForTests()

	Register(&fakeAlbumProvider{fakeProvider: fakeProvider{id: "fake-album", defaultOn: true}})

	s, err := NewSettingsService(context.Background(), pool, discardLogger())
	if err != nil {
		t.Fatalf("init: %v", err)
	}

	before := s.CurrentVersion()
	newVer, bumped, err := s.BumpVersionIfProvidersChanged(context.Background())
	if err != nil {
		t.Fatalf("bump: %v", err)
	}
	if !bumped {
		t.Errorf("bumped = false, want true on first boot (stored hash is empty)")
	}
	if newVer <= before {
		t.Errorf("newVer = %d, want > %d", newVer, before)
	}
	if s.CurrentVersion() != newVer {
		t.Errorf("in-memory version = %d, want %d (synchronized with DB)", s.CurrentVersion(), newVer)
	}
}

// TestSettingsService_BumpVersionIfProvidersChanged_NoChange: second
// call back-to-back returns bumped=false because the stored hash now
// matches the registered set.
func TestSettingsService_BumpVersionIfProvidersChanged_NoChange(t *testing.T) {
	pool := newPool(t)
	resetRegistryForTests()
	defer resetRegistryForTests()

	Register(&fakeAlbumProvider{fakeProvider: fakeProvider{id: "fake-album", defaultOn: true}})

	s, err := NewSettingsService(context.Background(), pool, discardLogger())
	if err != nil {
		t.Fatalf("init: %v", err)
	}

	// First call bumps (empty stored hash → mismatch).
	if _, _, err := s.BumpVersionIfProvidersChanged(context.Background()); err != nil {
		t.Fatalf("first bump: %v", err)
	}

	afterFirst := s.CurrentVersion()
	_, bumped, err := s.BumpVersionIfProvidersChanged(context.Background())
	if err != nil {
		t.Fatalf("second bump: %v", err)
	}
	if bumped {
		t.Errorf("bumped = true on second call, want false (no change)")
	}
	if s.CurrentVersion() != afterFirst {
		t.Errorf("version changed without a provider-set change")
	}
}

// TestSettingsService_BumpVersion_Increments verifies the manual bump
// path. Distinct from BumpVersionIfProvidersChanged, this always writes
// regardless of the stored provider-set hash.
func TestSettingsService_BumpVersion_Increments(t *testing.T) {
	pool := newPool(t)
	resetRegistryForTests()
	defer resetRegistryForTests()

	Register(&fakeAlbumProvider{fakeProvider: fakeProvider{id: "fake-album-bump", defaultOn: true}})

	s, err := NewSettingsService(context.Background(), pool, discardLogger())
	if err != nil {
		t.Fatalf("init: %v", err)
	}

	before := s.CurrentVersion()
	newVer, err := s.BumpVersion(context.Background())
	if err != nil {
		t.Fatalf("bump: %v", err)
	}
	if newVer <= before {
		t.Errorf("newVer = %d, want > %d", newVer, before)
	}
	if s.CurrentVersion() != newVer {
		t.Errorf("in-memory version = %d, want %d", s.CurrentVersion(), newVer)
	}

	// Calling again increments by another 1 (always writes).
	afterFirst := newVer
	newVer2, err := s.BumpVersion(context.Background())
	if err != nil {
		t.Fatalf("second bump: %v", err)
	}
	if newVer2 != afterFirst+1 {
		t.Errorf("second newVer = %d, want %d", newVer2, afterFirst+1)
	}
}

// TestRegisteredProvidersHash_StableAcrossOrder: registry order
// shouldn't matter because we sort before hashing.
func TestRegisteredProvidersHash_StableAcrossOrder(t *testing.T) {
	// Hash is a function of the registered set; in this test it just
	// needs to be deterministic and non-empty.
	h1 := registeredProvidersHash()
	h2 := registeredProvidersHash()
	if h1 != h2 {
		t.Errorf("hash is not deterministic: %q vs %q", h1, h2)
	}
	if h1 == "" {
		t.Errorf("hash empty; expected at least one provider registered (or empty set is valid)")
	}
	if len(h1) != 64 {
		t.Errorf("hash length = %d, want 64 (SHA-256 hex)", len(h1))
	}
}
