package services

import (
	"log/slog"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

func TestPasswordHashVerification(t *testing.T) {
	hash := HashPassword("correct-horse")
	if !VerifyPasswordHash(hash, "correct-horse") {
		t.Fatalf("VerifyPasswordHash rejected the correct password")
	}
	if VerifyPasswordHash(hash, "wrong-password") {
		t.Fatalf("VerifyPasswordHash accepted the wrong password")
	}
}

func TestBootstrapCreatesAdminAndClosesRegistration(t *testing.T) {
	auth := newTestAuthService(t)
	available, err := auth.BootstrapAvailable()
	if err != nil {
		t.Fatalf("BootstrapAvailable returned error: %v", err)
	}
	if !available {
		t.Fatalf("BootstrapAvailable = false, want true")
	}

	user, err := auth.CreateBootstrapUser("daniel", "daniel@example.test", "password123")
	if err != nil {
		t.Fatalf("CreateBootstrapUser returned error: %v", err)
	}
	if user.Role != UserRoleAdmin {
		t.Fatalf("role = %q, want admin", user.Role)
	}

	if _, err := auth.CreateBootstrapUser("other", "other@example.test", "password123"); err == nil {
		t.Fatalf("second bootstrap unexpectedly succeeded")
	}
}

func TestLoginSessionAndDisabledUser(t *testing.T) {
	auth := newTestAuthService(t)
	user, err := auth.CreateBootstrapUser("daniel", "daniel@example.test", "password123")
	if err != nil {
		t.Fatalf("CreateBootstrapUser returned error: %v", err)
	}
	if _, _, err := auth.Login("daniel@example.test", "wrong"); err == nil {
		t.Fatalf("Login accepted wrong password")
	}

	_, token, err := auth.Login("daniel@example.test", "password123")
	if err != nil {
		t.Fatalf("Login returned error: %v", err)
	}
	sessionUser, _, err := auth.UserForSession(token)
	if err != nil {
		t.Fatalf("UserForSession returned error: %v", err)
	}
	if sessionUser.ID != user.ID {
		t.Fatalf("session user = %q, want %q", sessionUser.ID, user.ID)
	}

	if err := auth.DisableUser(user.ID, true); err != nil {
		t.Fatalf("DisableUser returned error: %v", err)
	}
	if _, _, err := auth.UserForSession(token); err == nil {
		t.Fatalf("disabled user session still resolved")
	}
}

func TestInviteAcceptsOnceAndResetChangesPassword(t *testing.T) {
	auth := newTestAuthService(t)
	admin, err := auth.CreateBootstrapUser("admin", "admin@example.test", "password123")
	if err != nil {
		t.Fatalf("CreateBootstrapUser returned error: %v", err)
	}
	invite, err := auth.CreateInvite("friend@example.test", UserRoleUser, admin.ID, time.Hour)
	if err != nil {
		t.Fatalf("CreateInvite returned error: %v", err)
	}
	user, err := auth.AcceptInvite(invite.Token, "friend", "password123")
	if err != nil {
		t.Fatalf("AcceptInvite returned error: %v", err)
	}
	if user.Email != "friend@example.test" {
		t.Fatalf("email = %q, want friend@example.test", user.Email)
	}
	if _, err := auth.AcceptInvite(invite.Token, "friend", "password123"); err == nil {
		t.Fatalf("AcceptInvite allowed token reuse")
	}

	reset, err := auth.CreatePasswordResetInvite(user.ID, admin.ID)
	if err != nil {
		t.Fatalf("CreatePasswordResetInvite returned error: %v", err)
	}
	if _, err := auth.AcceptInvite(reset.Token, "", "newpassword123"); err != nil {
		t.Fatalf("AcceptInvite reset returned error: %v", err)
	}
	if _, _, err := auth.Login("friend@example.test", "newpassword123"); err != nil {
		t.Fatalf("Login with reset password returned error: %v", err)
	}
}

func TestAPITokenLifecycle(t *testing.T) {
	auth := newTestAuthService(t)
	user, err := auth.CreateBootstrapUser("daniel", "daniel@example.test", "password123")
	if err != nil {
		t.Fatalf("CreateBootstrapUser returned error: %v", err)
	}

	result, err := auth.CreateAPIToken(user.ID, "CLI laptop")
	if err != nil {
		t.Fatalf("CreateAPIToken returned error: %v", err)
	}
	if result.Plaintext == "" || !strings.HasPrefix(result.Plaintext, apiTokenPrefix) {
		t.Fatalf("plaintext = %q, want %q prefix", result.Plaintext, apiTokenPrefix)
	}
	// The secret must never be stored in plaintext — only its hash.
	if strings.Contains(result.Token.TokenHash, result.Plaintext) || result.Token.TokenHash == result.Plaintext {
		t.Fatalf("stored token hash leaks the plaintext secret")
	}

	resolved, err := auth.UserForAPIToken(result.Plaintext)
	if err != nil {
		t.Fatalf("UserForAPIToken returned error: %v", err)
	}
	if resolved.ID != user.ID {
		t.Fatalf("resolved user = %q, want %q", resolved.ID, user.ID)
	}

	tokens, err := auth.ListAPITokens(user.ID)
	if err != nil {
		t.Fatalf("ListAPITokens returned error: %v", err)
	}
	if len(tokens) != 1 {
		t.Fatalf("token count = %d, want 1", len(tokens))
	}
	if tokens[0].Name != "CLI laptop" {
		t.Fatalf("token name = %q, want %q", tokens[0].Name, "CLI laptop")
	}
	if tokens[0].LastUsedAt == nil {
		t.Fatalf("LastUsedAt not recorded after UserForAPIToken")
	}

	if _, err := auth.UserForAPIToken(result.Plaintext + "tampered"); err == nil {
		t.Fatalf("UserForAPIToken accepted a tampered token")
	}
	if _, err := auth.UserForAPIToken("wbx_deadbeef.nope"); err == nil {
		t.Fatalf("UserForAPIToken accepted an unknown token")
	}

	if err := auth.DeleteAPIToken(user.ID, tokens[0].ID); err != nil {
		t.Fatalf("DeleteAPIToken returned error: %v", err)
	}
	if _, err := auth.UserForAPIToken(result.Plaintext); err == nil {
		t.Fatalf("deleted token still resolved")
	}
	remaining, err := auth.ListAPITokens(user.ID)
	if err != nil {
		t.Fatalf("ListAPITokens returned error: %v", err)
	}
	if len(remaining) != 0 {
		t.Fatalf("token count after delete = %d, want 0", len(remaining))
	}
}

func TestAPITokenScopedToOwnerAndDisabledUser(t *testing.T) {
	auth := newTestAuthService(t)
	owner, err := auth.CreateBootstrapUser("owner", "owner@example.test", "password123")
	if err != nil {
		t.Fatalf("CreateBootstrapUser returned error: %v", err)
	}
	invite, err := auth.CreateInvite("other@example.test", UserRoleUser, owner.ID, time.Hour)
	if err != nil {
		t.Fatalf("CreateInvite returned error: %v", err)
	}
	other, err := auth.AcceptInvite(invite.Token, "other", "password123")
	if err != nil {
		t.Fatalf("AcceptInvite returned error: %v", err)
	}

	result, err := auth.CreateAPIToken(owner.ID, "owner token")
	if err != nil {
		t.Fatalf("CreateAPIToken returned error: %v", err)
	}
	tokens, err := auth.ListAPITokens(owner.ID)
	if err != nil {
		t.Fatalf("ListAPITokens returned error: %v", err)
	}

	// Another user cannot delete tokens they do not own.
	if err := auth.DeleteAPIToken(other.ID, tokens[0].ID); err == nil {
		t.Fatalf("DeleteAPIToken allowed deletion across users")
	}

	// A disabled owner cannot authenticate with their token.
	if err := auth.DisableUser(owner.ID, true); err != nil {
		t.Fatalf("DisableUser returned error: %v", err)
	}
	if _, err := auth.UserForAPIToken(result.Plaintext); err == nil {
		t.Fatalf("disabled user token still resolved")
	}
}

func TestUserPolicyAllowsNegativeOneForUnlimitedUploadLimits(t *testing.T) {
	auth := newTestAuthService(t)
	user, err := auth.CreateBootstrapUser("daniel", "daniel@example.test", "password123")
	if err != nil {
		t.Fatalf("CreateBootstrapUser returned error: %v", err)
	}

	unlimited := -1.0
	if err := auth.SetUserPolicy(user.ID, UserPolicy{MaxUploadMB: &unlimited, DailyUploadMB: &unlimited}); err != nil {
		t.Fatalf("SetUserPolicy rejected -1 unlimited upload limits: %v", err)
	}
	updated, err := auth.UserByID(user.ID)
	if err != nil {
		t.Fatalf("UserByID returned error: %v", err)
	}
	if updated.Policy.MaxUploadMB == nil || *updated.Policy.MaxUploadMB != -1 || updated.Policy.DailyUploadMB == nil || *updated.Policy.DailyUploadMB != -1 {
		t.Fatalf("unlimited policy was not persisted: %+v", updated.Policy)
	}
}

func newTestAuthService(t *testing.T) *AuthService {
	t.Helper()
	root := t.TempDir()
	upload, err := NewUploadService(1024*1024, filepath.Join(root, "data"), "http://example.test", slog.Default())
	if err != nil {
		t.Fatalf("NewUploadService returned error: %v", err)
	}
	t.Cleanup(func() {
		if err := upload.Close(); err != nil {
			t.Fatalf("Close returned error: %v", err)
		}
	})
	auth, err := NewAuthService(upload.DB(), "http://example.test")
	if err != nil {
		t.Fatalf("NewAuthService returned error: %v", err)
	}
	return auth
}
