// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
// Use of this source code is governed by MIT-style
// license that can be found in the LICENSE file.

package utils

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"encoding/json"

	"git.smarteching.com/goffee/core"
	"git.smarteching.com/jacs/goffeetabler/models"
	"gorm.io/gorm"
)

type SessionUser struct {
	mu               sync.RWMutex
	context          *core.Context
	userID           uint
	hashedSessionKey string
	authenticated    bool
	sessionStart     time.Time
	values           map[string]interface{}
}

// start the struct
func (s *SessionUser) Init(c *core.Context) bool {

	// check session cookie
	pass := true
	token := ""
	s.context = c

	payload := make(map[string]interface{})
	// get cookie
	usercookie, err := c.GetCookie()
	if err != nil {

	}

	token = usercookie.Token

	if token == "" {

		pass = false

	} else {

		payload, err = c.GetJWT().DecodeToken(token)

		if err != nil {

			pass = false

		} else {

			userID := uint(c.CastToInt(payload["userID"]))
			userAgent := c.GetUserAgent()

			// get data from redis
			hashedCacheKey := CreateAuthTokenHashedCacheKey(userID, userAgent)
			cachedToken, err := c.GetCache().Get(hashedCacheKey)

			if err != nil {
				pass = false
			} else if cachedToken != token {
				pass = false
			} else {
				var user models.User
				res := c.GetGorm().Where("id = ?", userID).First(&user)
				if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
					pass = false
				}
				// if have session start the struct
				if pass {
					userAgent := c.GetUserAgent()
					sessionKey := fmt.Sprintf("sess_%v", userAgent)
					s.hashedSessionKey = CreateAuthTokenHashedCacheKey(userID, sessionKey)

					s.values = make(map[string]interface{})
					s.authenticated = true
					s.userID = userID
					value, _ := c.GetCache().Get(s.hashedSessionKey)

					if len(value) > 0 {
						_ = json.Unmarshal([]byte(value), &s.values)
					}

					return true

				} else {

					s.hashedSessionKey = ""
					s.authenticated = false
					s.userID = 0
					return false
				}
			}
		}
	}

	return false
}

func (s *SessionUser) Set(key string, value interface{}) error {
	s.mu.Lock()
	s.values[key] = value
	s.mu.Unlock()
	return s.Save()
}

func (s *SessionUser) Get(key string) (interface{}, bool) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	val, ok := s.values[key]
	return val, ok
}

func (s *SessionUser) Delete(key string) interface{} {
	s.mu.RLock()
	v, ok := s.values[key]
	s.mu.RUnlock()
	if ok {
		s.mu.Lock()
		delete(s.values, key)
		s.mu.Unlock()
	}
	s.Save()
	return v
}

func (s *SessionUser) Flush() error {
	s.mu.Lock()
	s.context.GetCache().Delete(s.hashedSessionKey)
	s.mu.Unlock()
	return nil
}

func (s *SessionUser) Save() error {

	var value string

	s.mu.RLock()

	if len(s.values) > 0 {
		buf, err := json.Marshal(&s.values)
		if err != nil {
			s.mu.RUnlock()
			return err
		}
		value = string(buf)
	}

	if len(value) > 0 {
		s.context.GetCache().Set(s.hashedSessionKey, value)
	} else {
		s.context.GetCache().Delete(s.hashedSessionKey)
	}
	s.mu.RUnlock()
	return nil
}

func (s *SessionUser) GetUserID() uint {

	return s.userID
}
