package user

import (
	"context"
	"strings"
	"testing"
	"time"
)

func newTestService(t *testing.T) *Service {
	t.Helper()
	s, err := NewService(NewMemoryStore(), ServiceOptions{BcryptCost: bcryptMinTest})
	if err != nil {
		t.Fatalf("NewService: %v", err)
	}
	return s
}

// bcryptMinTest: testler yavaş olmasın diye düşük maliyet. Üretimde varsayılan 10.
const bcryptMinTest = 4

func TestRegisterAndLogin(t *testing.T) {
	ctx := context.Background()
	s := newTestService(t)

	u, err := s.Register(ctx, "ahmet", "gucluparola123", "Ahmet")
	if err != nil {
		t.Fatalf("Register: %v", err)
	}
	// Kurulum kuralı: hiç hesap yokken açılan İLK hesap yöneticidir.
	if u.Role != RoleAdmin {
		t.Errorf("ilk hesap yönetici olmalı, %q", u.Role)
	}
	// İkinci hesap normal üyedir.
	if second, err := s.Register(ctx, "mehmet", "gucluparola456", ""); err != nil {
		t.Fatalf("ikinci Register: %v", err)
	} else if second.Role != RoleUser {
		t.Errorf("ikinci hesap normal üye olmalı, %q", second.Role)
	}
	// Parola düz metin SAKLANMAMALI.
	if u.PasswordHash == "" {
		t.Fatal("parola özeti boş")
	}
	if strings.Contains(u.PasswordHash, "gucluparola123") {
		t.Error("parola özetinin içinde düz metin görünüyor")
	}

	// Giriş
	got, token, expires, err := s.Login(ctx, "ahmet", "gucluparola123")
	if err != nil {
		t.Fatalf("Login: %v", err)
	}
	if got.ID != u.ID {
		t.Errorf("giriş farklı kullanıcı döndü: %s != %s", got.ID, u.ID)
	}
	if token == "" {
		t.Fatal("token boş")
	}
	if expires.Before(time.Now()) {
		t.Error("bitiş zamanı geçmiş")
	}

	// Token ile doğrulama
	v, err := s.Validate(ctx, token)
	if err != nil {
		t.Fatalf("Validate: %v", err)
	}
	if v.ID != u.ID {
		t.Errorf("Validate farklı kullanıcı: %s", v.ID)
	}

	// Çıkış → token geçersiz
	s.Logout(token)
	if _, err := s.Validate(ctx, token); err == nil {
		t.Error("çıkış sonrası token geçerli kalmamalı")
	}
}

func TestPasswordNeverLeaks(t *testing.T) {
	ctx := context.Background()
	s := newTestService(t)
	u, _ := s.Register(ctx, "ayse", "parola1234", "")

	// API'ye dönecek görünümde parola özeti OLMAMALI.
	// UserView yapısında PasswordHash alanı BİLE yoktur: yeni bir alan eklense bile
	// View() zorunlu kopyasını kullanır, bu yüzden sızıntı imkânsızdır.
	view := u.View()
	if view.Username == "" || view.ID == "" {
		t.Error("View() kimlik ve kullanıcı adı dönmeli")
	}
	// View kendi kopyasını döner: alan değiştirilse bile kayıt etkilenmez.
	view.Username = "saldirgan"
	if u.Username != "ayse" {
		t.Error("View() kayıt nesnesini paylaşıyor")
	}
}

func TestInvalidCredentialsIsGeneric(t *testing.T) {
	ctx := context.Background()
	s := newTestService(t)
	_, _ = s.Register(ctx, "mehmet", "parola1234", "")

	// Var olmayan kullanıcı ve yanlış parola AYNI hatayı dönmeli.
	_, _, _, err1 := s.Login(ctx, "olmayan", "parola1234")
	_, _, _, err2 := s.Login(ctx, "mehmet", "yanlisparola")
	if err1 == nil || err2 == nil {
		t.Fatal("geçersiz giriş kabul edilmemeli")
	}
	if err1.Error() != err2.Error() {
		t.Errorf("hatalar farklı: kullanıcı varlığı sızıyor\n  yok  : %v\n  var  : %v", err1, err2)
	}
}

func TestUsernameRules(t *testing.T) {
	ctx := context.Background()
	s := newTestService(t)

	bad := []string{"ab", "", "  ", "ahmet yilmaz", "ahmet@yilmaz", "üçgen", "ahmet!"}
	for _, name := range bad {
		if _, err := s.Register(ctx, name, "parola1234", ""); err == nil {
			t.Errorf("geçersiz kullanıcı adı kabul edildi: %q", name)
		}
	}

	// Büyük/küçük harf AYNI hesaptır.
	if _, err := s.Register(ctx, "Ahmet", "parola1234", ""); err != nil {
		t.Fatalf("Register(Ahmet): %v", err)
	}
	if _, err := s.Register(ctx, "ahmet", "parola1234", ""); err == nil {
		t.Error("aynı kullanıcı adı (farklı yazım) iki kez kaydedilebilmemeli")
	}
}

func TestPasswordRules(t *testing.T) {
	ctx := context.Background()
	s := newTestService(t)
	for _, pw := range []string{"", "kisa", "1234567"} {
		if _, err := s.Register(ctx, "user"+pw, pw, ""); err == nil {
			t.Errorf("zayıf parola kabul edildi: %q", pw)
		}
	}
}

func TestSessionStoreKeepsOnlyHash(t *testing.T) {
	ctx := context.Background()
	s := newTestService(t)
	_, _ = s.Register(ctx, "can", "parola1234", "")
	_, token, _, err := s.Login(ctx, "can", "parola1234")
	if err != nil {
		t.Fatal(err)
	}

	// Bellekte token'ın KENDİSİ bulunmamalı, yalnızca özeti.
	s.mu.RLock()
	for k, v := range s.sessions {
		if v.TokenHash == token {
			t.Error("oturum sözlüğünde tokenın kendisi saklanıyor")
		}
		if k != hashToken(token) {
			t.Error("anahtar token özeti olmalı")
		}
	}
	s.mu.RUnlock()
}

func TestLogoutAll(t *testing.T) {
	ctx := context.Background()
	s := newTestService(t)
	ada, err := s.Register(ctx, "ada", "parola1234", "")
	if err != nil {
		t.Fatal(err)
	}

	_, t1, _, _ := s.Login(ctx, "ada", "parola1234")
	_, t2, _, _ := s.Login(ctx, "ada", "parola1234")
	// Oturumlar KIMLIK (ada.ID) ile etiketlenir, kullanici adi ile degil.
	uid := ada.ID
	if s.ActiveSessions(uid) != 2 {
		t.Fatalf("2 oturum beklenir, %d (id=%s)", s.ActiveSessions(uid), uid)
	}
	s.LogoutAll(uid)
	if s.ActiveSessions(uid) != 0 {
		t.Error("tüm oturumlar kapanmalı")
	}
	if _, err := s.Validate(ctx, t1); err == nil {
		t.Error("t1 geçersiz olmalı")
	}
	if _, err := s.Validate(ctx, t2); err == nil {
		t.Error("t2 geçersiz olmalı")
	}
}

func TestBootstrapAdmin(t *testing.T) {
	ctx := context.Background()
	s := newTestService(t)

	// Parola yoksa hesap OLUŞTURULMAZ (sessizce geçilmez).
	if _, err := s.EnsureBootstrap(ctx, "admin", ""); err == nil {
		t.Error("parola olmadan yönetici oluşturulmamalı")
	}

	admin, err := s.EnsureBootstrap(ctx, "admin", "yonetici1234")
	if err != nil {
		t.Fatalf("EnsureBootstrap: %v", err)
	}
	if admin.Role != RoleAdmin {
		t.Errorf("ilk hesap yönetici olmalı, %q", admin.Role)
	}
	// İkinci çağrı mevcut hesabı döndürmeli (yeni hesap açmamalı).
	again, err := s.EnsureBootstrap(ctx, "admin", "yonetici1234")
	if err != nil {
		t.Fatal(err)
	}
	if again.ID != admin.ID {
		t.Error("EnsureBootstrap mevcut hesabı bulmalı")
	}
	// Yönetici giriş yapabilmeli.
	if _, _, _, err := s.Login(ctx, "admin", "yonetici1234"); err != nil {
		t.Errorf("yönetici girişi başarısız: %v", err)
	}
}
