package service

import (
	"crypto/sha256"
	"encoding/hex"
	"sort"
	"strconv"
	"time"
	"xboard-go/internal/database"
	"xboard-go/internal/model"

	"github.com/google/uuid"
)

const sessionTTL = 7 * 24 * time.Hour

type SessionRecord struct {
	ID         string `json:"id"`
	UserID     int    `json:"user_id"`
	TokenHash  string `json:"token_hash"`
	Name       string `json:"name"`
	UserAgent  string `json:"user_agent"`
	IP         string `json:"ip"`
	CreatedAt  int64  `json:"created_at"`
	LastUsedAt int64  `json:"last_used_at"`
	ExpiresAt  int64  `json:"expires_at"`
}

func TrackSession(userID int, token, ip, userAgent string) SessionRecord {
	now := time.Now().Unix()
	tokenHash := hashToken(token)
	list := activeSessions(userID, now)

	for i := range list {
		if list[i].TokenHash == tokenHash {
			list[i].IP = firstNonEmpty(ip, list[i].IP)
			list[i].UserAgent = firstNonEmpty(userAgent, list[i].UserAgent)
			list[i].LastUsedAt = now
			saveSessions(userID, list)
			syncUserSessionOnlineState(userID, list[i].IP, now)
			return list[i]
		}
	}

	record := SessionRecord{
		ID:         uuid.NewString(),
		UserID:     userID,
		TokenHash:  tokenHash,
		Name:       "Web Session",
		UserAgent:  userAgent,
		IP:         ip,
		CreatedAt:  now,
		LastUsedAt: now,
		ExpiresAt:  now + int64(sessionTTL.Seconds()),
	}
	list = append(list, record)
	saveSessions(userID, list)
	syncUserSessionOnlineState(userID, record.IP, now)
	return record
}

func GetUserSessions(userID int, currentToken string) []SessionRecord {
	now := time.Now().Unix()
	list := activeSessions(userID, now)
	if currentToken != "" {
		currentHash := hashToken(currentToken)
		found := false
		for i := range list {
			if list[i].TokenHash == currentHash {
				found = true
				break
			}
		}
		if !found {
			list = append(list, SessionRecord{
				ID:         uuid.NewString(),
				UserID:     userID,
				TokenHash:  currentHash,
				Name:       "Web Session",
				CreatedAt:  now,
				LastUsedAt: now,
				ExpiresAt:  now + int64(sessionTTL.Seconds()),
			})
			saveSessions(userID, list)
		}
	}
	return activeSessions(userID, now)
}

func RemoveUserSession(userID int, sessionID string) bool {
	now := time.Now().Unix()
	list := activeSessions(userID, now)
	next := make([]SessionRecord, 0, len(list))
	removed := false

	for _, session := range list {
		if session.ID != sessionID {
			next = append(next, session)
			continue
		}

		removed = true
		ttl := time.Until(time.Unix(session.ExpiresAt, 0))
		if ttl < time.Hour {
			ttl = time.Hour
		}
		_ = database.CacheSet(revokedTokenKey(session.TokenHash), true, ttl)
	}

	if removed {
		saveSessions(userID, next)
	}

	return removed
}

func IsSessionTokenRevoked(token string) bool {
	if token == "" {
		return false
	}
	_, ok := database.CacheGetJSON[bool](revokedTokenKey(hashToken(token)))
	return ok
}

func StoreQuickLoginToken(userID int, ttl time.Duration) string {
	verify := uuid.NewString()
	_ = database.CacheSet(quickLoginKey(verify), userID, ttl)
	return verify
}

func ResolveQuickLoginToken(verify string) (int, bool) {
	userID, ok := database.CacheGetJSON[int](quickLoginKey(verify))
	if ok {
		_ = database.CacheDelete(quickLoginKey(verify))
	}
	return userID, ok
}

func StoreEmailVerifyCode(email, code string, ttl time.Duration) {
	_ = database.CacheSet(emailVerifyKey(email), code, ttl)
}

func CheckEmailVerifyCode(email, code string) bool {
	savedCode, ok := database.CacheGetJSON[string](emailVerifyKey(email))
	if !ok || savedCode == "" || savedCode != code {
		return false
	}
	_ = database.CacheDelete(emailVerifyKey(email))
	return true
}

func activeSessions(userID int, now int64) []SessionRecord {
	sessions, ok := database.CacheGetJSON[[]SessionRecord](userSessionsKey(userID))
	if !ok || len(sessions) == 0 {
		return []SessionRecord{}
	}

	filtered := make([]SessionRecord, 0, len(sessions))
	for _, session := range sessions {
		if session.ExpiresAt > 0 && session.ExpiresAt <= now {
			continue
		}
		filtered = append(filtered, session)
	}

	sort.SliceStable(filtered, func(i, j int) bool {
		return filtered[i].LastUsedAt > filtered[j].LastUsedAt
	})

	if len(filtered) != len(sessions) {
		saveSessions(userID, filtered)
	}
	return filtered
}

func saveSessions(userID int, sessions []SessionRecord) {
	sort.SliceStable(sessions, func(i, j int) bool {
		return sessions[i].LastUsedAt > sessions[j].LastUsedAt
	})
	_ = database.CacheSet(userSessionsKey(userID), sessions, sessionTTL)
}

func syncUserSessionOnlineState(userID int, ip string, now int64) {
	updates := map[string]any{
		"last_online_at": time.Unix(now, 0),
		"updated_at":     now,
	}

	if sessions := activeSessions(userID, now); len(sessions) > 0 {
		updates["online_count"] = len(sessions)
	}
	_ = database.DB.Model(&model.User{}).Where("id = ?", userID).Updates(updates).Error

	ip = firstNonEmpty(ip)
	if ip == "" {
		return
	}

	record := model.UserOnlineDevice{
		UserID:      userID,
		NodeID:      0,
		IP:          ip,
		FirstSeenAt: now,
		LastSeenAt:  now,
		ExpiresAt:   now + int64(sessionTTL.Seconds()),
		CreatedAt:   now,
		UpdatedAt:   now,
	}

	var existing model.UserOnlineDevice
	if err := database.DB.Where("user_id = ? AND node_id = ? AND ip = ?", userID, 0, ip).First(&existing).Error; err == nil {
		_ = database.DB.Model(&model.UserOnlineDevice{}).Where("id = ?", existing.ID).Updates(map[string]any{
			"last_seen_at": now,
			"expires_at":   record.ExpiresAt,
			"updated_at":   now,
		}).Error
		return
	}
	_ = database.DB.Create(&record).Error
}

func hashToken(token string) string {
	sum := sha256.Sum256([]byte(token))
	return hex.EncodeToString(sum[:])
}

func userSessionsKey(userID int) string {
	return "session:user:" + strconv.Itoa(userID)
}

func revokedTokenKey(tokenHash string) string {
	return "session:revoked:" + tokenHash
}

func quickLoginKey(verify string) string {
	return "session:quick-login:" + verify
}

func emailVerifyKey(email string) string {
	return "session:email-code:" + email
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if value != "" {
			return value
		}
	}
	return ""
}
