package auth

import (
	"errors"
	"testing"
	"time"

	"hyapp-admin-server/internal/config"
	"hyapp-admin-server/internal/model"
	authcore "hyapp-admin-server/internal/service"
)

type fakeAuthStore struct {
	user        model.User
	tokens      map[string]model.RefreshToken
	nextTokenID uint
}

func newFakeAuthStore(t *testing.T) *fakeAuthStore {
	t.Helper()
	passwordHash, err := authcore.HashPassword("secret")
	if err != nil {
		t.Fatalf("hash password: %v", err)
	}
	return &fakeAuthStore{
		user: model.User{
			ID:           7,
			Username:     "admin",
			Name:         "Admin",
			PasswordHash: passwordHash,
			Status:       model.UserStatusActive,
			Roles: []model.Role{{
				Permissions: []model.Permission{{Code: "user:view"}},
			}},
		},
		tokens: make(map[string]model.RefreshToken),
	}
}

func (s *fakeAuthStore) FindUserByUsername(username string) (*model.User, error) {
	if username != s.user.Username {
		return nil, errors.New("not found")
	}
	user := s.user
	return &user, nil
}

func (s *fakeAuthStore) FindUserByID(id uint) (*model.User, error) {
	if id != s.user.ID {
		return nil, errors.New("not found")
	}
	user := s.user
	return &user, nil
}

func (s *fakeAuthStore) CreateRefreshToken(token model.RefreshToken) error {
	if token.ID == 0 {
		s.nextTokenID++
		token.ID = s.nextTokenID
	}
	s.tokens[token.TokenHash] = token
	return nil
}

func (s *fakeAuthStore) RotateRefreshToken(oldTokenID uint, token model.RefreshToken) error {
	if err := s.CreateRefreshToken(token); err != nil {
		return err
	}
	nowMS := time.Now().UTC().UnixMilli()
	for hash, existing := range s.tokens {
		if existing.ID == oldTokenID && existing.RevokedAtMS == nil {
			existing.RevokedAtMS = &nowMS
			s.tokens[hash] = existing
			return nil
		}
	}
	return errors.New("old token not found")
}

func (s *fakeAuthStore) FindRefreshToken(hash string) (*model.RefreshToken, error) {
	token, ok := s.tokens[hash]
	if !ok || token.RevokedAtMS != nil || token.ExpiresAtMS <= time.Now().UTC().UnixMilli() {
		return nil, errors.New("not found")
	}
	return &token, nil
}

func (s *fakeAuthStore) RevokeRefreshToken(hash string) error {
	token, ok := s.tokens[hash]
	if !ok {
		return nil
	}
	nowMS := time.Now().UTC().UnixMilli()
	token.RevokedAtMS = &nowMS
	s.tokens[hash] = token
	return nil
}

func (s *fakeAuthStore) TouchUserLogin(uint) error {
	return nil
}

func (s *fakeAuthStore) CreateLoginLog(model.LoginLog) error {
	return nil
}

func (s *fakeAuthStore) ResetUserPassword(id uint, password string) error {
	if id != s.user.ID {
		return errors.New("not found")
	}
	passwordHash, err := authcore.HashPassword(password)
	if err != nil {
		return err
	}
	s.user.PasswordHash = passwordHash
	return nil
}

func (s *fakeAuthStore) activeRefreshTokenCount() int {
	count := 0
	nowMS := time.Now().UTC().UnixMilli()
	for _, token := range s.tokens {
		if token.RevokedAtMS == nil && token.ExpiresAtMS > nowMS {
			count++
		}
	}
	return count
}

func TestRefreshRotatesOnlyCurrentSessionAndKeepsMultiLogin(t *testing.T) {
	store := newFakeAuthStore(t)
	service := NewService(store, authcore.NewAuthService("test-secret", time.Hour), config.Config{RefreshTokenTTL: 7 * 24 * time.Hour})

	first, err := service.Login(LoginInput{Username: "admin", Password: "secret", IP: "127.0.0.1", UserAgent: "browser-a"})
	if err != nil {
		t.Fatalf("first login: %v", err)
	}
	second, err := service.Login(LoginInput{Username: "admin", Password: "secret", IP: "127.0.0.2", UserAgent: "browser-b"})
	if err != nil {
		t.Fatalf("second login: %v", err)
	}
	if store.activeRefreshTokenCount() != 2 {
		t.Fatalf("expected two active sessions after multi-login, got %d", store.activeRefreshTokenCount())
	}

	refreshedFirst, err := service.Refresh(first.RefreshToken, LoginInput{IP: "127.0.0.1", UserAgent: "browser-a"})
	if err != nil {
		t.Fatalf("refresh first session: %v", err)
	}
	if refreshedFirst.RefreshToken == "" || refreshedFirst.RefreshToken == first.RefreshToken {
		t.Fatalf("refresh must rotate current refresh token")
	}
	if _, err := service.Refresh(first.RefreshToken, LoginInput{IP: "127.0.0.1", UserAgent: "browser-a"}); err == nil {
		t.Fatalf("old refresh token must be revoked after rotation")
	}
	if store.activeRefreshTokenCount() != 2 {
		t.Fatalf("rotation should keep one session per login point, got %d", store.activeRefreshTokenCount())
	}
	if _, err := service.Refresh(second.RefreshToken, LoginInput{IP: "127.0.0.2", UserAgent: "browser-b"}); err != nil {
		t.Fatalf("second session should remain refreshable: %v", err)
	}
}
