package service

import (
	"context"
	"errors"
	"strings"
	"testing"

	"github.com/glebarez/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"

	"oci-portal/internal/model"
)

func newTestAuth(t *testing.T) *AuthService {
	t.Helper()
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})
	if err != nil {
		t.Fatalf("open in-memory sqlite: %v", err)
	}
	if err := db.AutoMigrate(&model.User{}); err != nil {
		t.Fatalf("auto migrate: %v", err)
	}
	return NewAuthService(db, "test-jwt-secret")
}

func TestEnsureAdminCreatesUser(t *testing.T) {
	auth := newTestAuth(t)
	if err := auth.EnsureAdmin("admin", "pass123"); err != nil {
		t.Fatalf("EnsureAdmin: %v", err)
	}
	if _, _, err := auth.Login(context.Background(), "admin", "pass123", "127.0.0.1", ""); err != nil {
		t.Errorf("Login after EnsureAdmin: %v", err)
	}
}

func TestEnsureAdminDoesNotResetPassword(t *testing.T) {
	auth := newTestAuth(t)
	if err := auth.EnsureAdmin("admin", "first"); err != nil {
		t.Fatalf("EnsureAdmin: %v", err)
	}
	if err := auth.EnsureAdmin("admin", "second"); err != nil {
		t.Fatalf("EnsureAdmin twice: %v", err)
	}
	if _, _, err := auth.Login(context.Background(), "admin", "first", "127.0.0.1", ""); err != nil {
		t.Errorf("Login with original password: %v", err)
	}
	if _, _, err := auth.Login(context.Background(), "admin", "second", "127.0.0.1", ""); !errors.Is(err, ErrInvalidCredentials) {
		t.Errorf("Login with new password: got %v, want ErrInvalidCredentials", err)
	}
}

func TestEnsureAdminNoPasswordNoUsers(t *testing.T) {
	auth := newTestAuth(t)
	err := auth.EnsureAdmin("admin", "")
	if err == nil {
		t.Fatal("EnsureAdmin with no password and no users: got nil error, want failure")
	}
	if !strings.Contains(err.Error(), "ADMIN_PASSWORD") {
		t.Errorf("error = %q, want mention of ADMIN_PASSWORD", err)
	}
}

func TestEnsureAdminNoPasswordWithExistingUser(t *testing.T) {
	auth := newTestAuth(t)
	if err := auth.EnsureAdmin("admin", "pass"); err != nil {
		t.Fatalf("EnsureAdmin: %v", err)
	}
	if err := auth.EnsureAdmin("admin", ""); err != nil {
		t.Errorf("EnsureAdmin without password but user exists: %v", err)
	}
}

func TestLoginFailures(t *testing.T) {
	auth := newTestAuth(t)
	if err := auth.EnsureAdmin("admin", "pass123"); err != nil {
		t.Fatalf("EnsureAdmin: %v", err)
	}
	tests := []struct {
		name     string
		username string
		password string
	}{
		{name: "密码错误", username: "admin", password: "wrong"},
		{name: "用户不存在", username: "ghost", password: "pass123"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, _, err := auth.Login(context.Background(), tt.username, tt.password, "127.0.0.1", "")
			if !errors.Is(err, ErrInvalidCredentials) {
				t.Errorf("Login(%q, %q) error = %v, want ErrInvalidCredentials", tt.username, tt.password, err)
			}
		})
	}
}

func TestTokenRoundTrip(t *testing.T) {
	auth := newTestAuth(t)
	if err := auth.EnsureAdmin("admin", "pass123"); err != nil {
		t.Fatalf("EnsureAdmin: %v", err)
	}
	token, expires, err := auth.Login(context.Background(), "admin", "pass123", "127.0.0.1", "")
	if err != nil {
		t.Fatalf("Login: %v", err)
	}
	if expires.IsZero() {
		t.Error("expires is zero, want future time")
	}
	username, err := auth.ParseToken(context.Background(), token)
	if err != nil {
		t.Fatalf("ParseToken: %v", err)
	}
	if got, want := username, "admin"; got != want {
		t.Errorf("username = %q, want %q", got, want)
	}
}

func TestParseTokenRejectsForged(t *testing.T) {
	auth := newTestAuth(t)
	other := newTestAuth(t)
	other.jwtSecret = []byte("different-secret")
	if err := other.EnsureAdmin("admin", "pass"); err != nil {
		t.Fatalf("EnsureAdmin: %v", err)
	}
	forged, _, err := other.Login(context.Background(), "admin", "pass", "127.0.0.1", "")
	if err != nil {
		t.Fatalf("Login: %v", err)
	}
	if _, err := auth.ParseToken(context.Background(), forged); err == nil {
		t.Error("ParseToken(forged): got nil error, want failure")
	}
	if _, err := auth.ParseToken(context.Background(), "not.a.token"); err == nil {
		t.Error("ParseToken(garbage): got nil error, want failure")
	}
}

func TestLogoutRevokesToken(t *testing.T) {
	auth := newTestAuth(t)
	// ParseToken 现校验令牌版本,须存在对应账号
	if err := auth.EnsureAdmin("admin", "pass123"); err != nil {
		t.Fatalf("EnsureAdmin: %v", err)
	}
	token, _, err := auth.signToken("admin", 0)
	if err != nil {
		t.Fatalf("signToken: %v", err)
	}
	if _, err := auth.ParseToken(context.Background(), token); err != nil {
		t.Fatalf("ParseToken before logout: %v", err)
	}
	auth.Logout(token)
	if _, err := auth.ParseToken(context.Background(), token); err == nil {
		t.Error("ParseToken after logout: got nil error, want revoked")
	}
	// 幂等:重复登出与无效令牌登出都不应 panic,也不影响其他令牌
	auth.Logout(token)
	auth.Logout("not.a.token")
	fresh, _, err := auth.signToken("admin", 0)
	if err != nil {
		t.Fatalf("signToken fresh: %v", err)
	}
	if _, err := auth.ParseToken(context.Background(), fresh); err != nil {
		t.Errorf("ParseToken(fresh) after revoking old: %v", err)
	}
}

// TestTokenVersionInvalidatesOldToken 凭据变更递增令牌版本,旧 JWT 立即失效,
// 重签的新令牌可用(审计 S-02 动态复现序列的反向断言)。
func TestTokenVersionInvalidatesOldToken(t *testing.T) {
	auth := newTestAuth(t)
	if err := auth.EnsureAdmin("admin", "pass123"); err != nil {
		t.Fatalf("EnsureAdmin: %v", err)
	}
	ctx := context.Background()
	old, _, err := auth.Login(ctx, "admin", "pass123", "127.0.0.1", "")
	if err != nil {
		t.Fatalf("Login: %v", err)
	}
	finalName, err := auth.UpdateCredentials(ctx, "admin", UpdateCredentialsInput{
		NewPassword: "changed-456", CurrentPassword: "pass123",
	})
	if err != nil {
		t.Fatalf("UpdateCredentials: %v", err)
	}
	if _, err := auth.ParseToken(ctx, old); err == nil {
		t.Error("旧 token 在凭据变更后仍有效, want 失效")
	}
	fresh, _, err := auth.IssueToken(ctx, finalName)
	if err != nil {
		t.Fatalf("IssueToken: %v", err)
	}
	if name, err := auth.ParseToken(ctx, fresh); err != nil || name != "admin" {
		t.Errorf("重签 token 应有效: name=%q err=%v", name, err)
	}
}

// TestRevokeSessions 撤销全部会话:旧 token 失效,返回的新 token 有效。
func TestRevokeSessions(t *testing.T) {
	auth := newTestAuth(t)
	if err := auth.EnsureAdmin("admin", "pass123"); err != nil {
		t.Fatalf("EnsureAdmin: %v", err)
	}
	ctx := context.Background()
	old, _, err := auth.Login(ctx, "admin", "pass123", "127.0.0.1", "")
	if err != nil {
		t.Fatalf("Login: %v", err)
	}
	fresh, _, err := auth.RevokeSessions(ctx, "admin")
	if err != nil {
		t.Fatalf("RevokeSessions: %v", err)
	}
	if _, err := auth.ParseToken(ctx, old); err == nil {
		t.Error("撤销后旧 token 仍有效, want 失效")
	}
	if name, err := auth.ParseToken(ctx, fresh); err != nil || name != "admin" {
		t.Errorf("撤销后新 token 应有效: name=%q err=%v", name, err)
	}
}
