package auth_test

import (
	"path/filepath"
	"testing"

	"archivmail/internal/auth"
	"archivmail/internal/userstore"
)

func newTestAuth(t *testing.T) (*auth.Manager, *userstore.Store) {
	t.Helper()
	store, err := userstore.New(filepath.Join(t.TempDir(), "users.db"))
	if err != nil {
		t.Fatalf("userstore.New: %v", err)
	}
	t.Cleanup(func() { store.Close() })

	// Seed a test user
	store.Create(userstore.CreateUserRequest{
		Username: "testadmin",
		Email:    "admin@example.com",
		Password: "adminpass",
		Role:     userstore.RoleAdmin,
	})
	store.Create(userstore.CreateUserRequest{
		Username: "regularuser",
		Email:    "user@example.com",
		Password: "userpass",
		Role:     userstore.RoleUser,
	})

	mgr := auth.New(store, nil, "test-jwt-secret-32chars-long-enough", "0000000000000000000000000000000000000000000000000000000000000000")
	return mgr, store
}

func TestLoginSuccess(t *testing.T) {
	mgr, _ := newTestAuth(t)

	token, user, totpRequired, err := mgr.Login("testadmin", "adminpass")
	if err != nil {
		t.Fatalf("Login: %v", err)
	}
	if totpRequired {
		t.Error("expected totpRequired=false for user without TOTP")
	}
	if token == "" {
		t.Error("expected non-empty token")
	}
	if user.Username != "testadmin" {
		t.Errorf("Username = %q", user.Username)
	}
	if user.Role != userstore.RoleAdmin {
		t.Errorf("Role = %q", user.Role)
	}
}

func TestLoginWrongPassword(t *testing.T) {
	mgr, _ := newTestAuth(t)

	if _, _, _, err := mgr.Login("testadmin", "wrongpass"); err == nil {
		t.Error("expected error for wrong password")
	}
}

func TestLoginUnknownUser(t *testing.T) {
	mgr, _ := newTestAuth(t)

	if _, _, _, err := mgr.Login("nobody", "pw"); err == nil {
		t.Error("expected error for unknown user")
	}
}

func TestTokenValidation(t *testing.T) {
	mgr, _ := newTestAuth(t)

	token, _, _, _ := mgr.Login("testadmin", "adminpass")
	sess, err := mgr.ValidateToken(token)
	if err != nil {
		t.Fatalf("ValidateToken: %v", err)
	}
	if sess.Username != "testadmin" {
		t.Errorf("Session Username = %q", sess.Username)
	}
	if sess.Role != userstore.RoleAdmin {
		t.Errorf("Session Role = %q", sess.Role)
	}
	if sess.JTI == "" {
		t.Error("Session JTI should not be empty")
	}
}

func TestTokenTampering(t *testing.T) {
	mgr, _ := newTestAuth(t)

	token, _, _, _ := mgr.Login("testadmin", "adminpass")
	tampered := token + "x"

	if _, err := mgr.ValidateToken(tampered); err == nil {
		t.Error("tampered token should fail validation")
	}
}

func TestLogout(t *testing.T) {
	mgr, _ := newTestAuth(t)

	token, _, _, _ := mgr.Login("testadmin", "adminpass")

	// Token valid before logout
	if _, err := mgr.ValidateToken(token); err != nil {
		t.Fatalf("token should be valid before logout: %v", err)
	}

	if err := mgr.Logout(token); err != nil {
		t.Fatalf("Logout: %v", err)
	}

	// Token invalid after logout
	if _, err := mgr.ValidateToken(token); err == nil {
		t.Error("token should be invalid after logout")
	}
}

func TestHasRole(t *testing.T) {
	tests := []struct {
		userRole string
		required string
		want     bool
	}{
		{userstore.RoleAdmin, userstore.RoleAdmin, true},
		{userstore.RoleAdmin, userstore.RoleAuditor, true},
		{userstore.RoleAdmin, userstore.RoleUser, true},
		{userstore.RoleAuditor, userstore.RoleAdmin, false},
		{userstore.RoleAuditor, userstore.RoleAuditor, true},
		{userstore.RoleAuditor, userstore.RoleUser, true},
		{userstore.RoleUser, userstore.RoleAdmin, false},
		{userstore.RoleUser, userstore.RoleAuditor, false},
		{userstore.RoleUser, userstore.RoleUser, true},
	}

	for _, tt := range tests {
		got := auth.HasRole(tt.userRole, tt.required)
		if got != tt.want {
			t.Errorf("HasRole(%q, %q) = %v, want %v", tt.userRole, tt.required, got, tt.want)
		}
	}
}

func TestMultipleSessionsIndependent(t *testing.T) {
	mgr, _ := newTestAuth(t)

	token1, _, _, _ := mgr.Login("testadmin", "adminpass")
	token2, _, _, _ := mgr.Login("testadmin", "adminpass")

	if token1 == token2 {
		t.Error("two logins should produce different tokens (different JTIs)")
	}

	// Logout session 1, session 2 should still work
	mgr.Logout(token1)
	if _, err := mgr.ValidateToken(token2); err != nil {
		t.Errorf("session 2 should still be valid after session 1 logout: %v", err)
	}
}
