package photosapi

import (
	"errors"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"gorm.io/gorm"
)

// Errors
var (
	ErrSessionNotFound = errors.New("session not found")
	ErrSessionInvalid  = errors.New("session invalid")
	ErrTokenMissing    = errors.New("token missing")
)

// Model
type Session struct {
	ID        uint32   `gorm:"primary_key"`
	Token     string   `gorm:"size:36"`
	Account   *Account `gorm:"constraint:OnDelete:CASCADE,OnUpdate:CASCADE"`
	AccountId uint32   `gorm:"not null"`
	CreatedAt time.Time
	UpdatedAt time.Time
}

func (s *Session) BeforeCreate(tx *gorm.DB) error {
	uuid, err := uuid.NewRandom()
	if err != nil {
		return err
	}
	s.Token = uuid.String()
	return nil
}

func NewSession(tx *gorm.DB, login string, password string) (*Session, error) {
	account := NewAccount(login, password)
	if err := tx.Where(
		"login = ? and encrypted_password = ?",
		account.Login,
		account.EncryptedPassword,
	).First(account).Error; err != nil {
		return nil, err
	}

	session := &Session{Account: account}
	if err := tx.Create(session).Error; err != nil {
		return nil, err
	}
	return session, nil
}

// Service
func (s *Service) RequireAuthToken(c *gin.Context) {
	tokenAuth := c.GetHeader("Authorization")
	tokenCookie, _ := c.Cookie("photoapitoken")

	if tokenAuth != "" {
		if !strings.HasPrefix(tokenAuth, "Private ") {
			c.AbortWithError(http.StatusForbidden, ErrTokenMissing)
		} else {
			c.Set("token", tokenAuth[8:])
		}
	} else if tokenCookie != "" {
		c.Set("token", tokenCookie)
	} else {
		c.AbortWithError(http.StatusForbidden, ErrTokenMissing)
	}
}

func (s *Service) RequireSession(c *gin.Context) {
	s.RequireAuthToken(c)
	if c.IsAborted() {
		return
	}

	sess := &Session{}
	if err := s.DB.Preload("Account").Where("token = ?", c.GetString("token")).First(sess).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			c.AbortWithError(http.StatusForbidden, ErrSessionNotFound)
		} else {
			c.AbortWithError(http.StatusForbidden, err)
		}
		return
	}
	if sess.Account == nil {
		c.AbortWithError(http.StatusInternalServerError, ErrSessionInvalid)
		return
	}
	s.DB.Select("updated_at").Save(sess)
	s.LogOk.Printf("Session", "User: %s", sess.Account.Login)
	c.Set("session", sess)
}

func (s *Service) CurrentSession(c *gin.Context) *Session {
	return c.MustGet("session").(*Session)
}

func (s *Service) SessionCleaner() {
	for range time.Tick(time.Minute) {
		t := time.Now().UTC().Add(-3 * time.Hour).Truncate(time.Minute)
		// s.LogOk.Printf("Session", "Cleaning old session < %s", t)
		if err := s.DB.Where("updated_at < ?", t).Delete(&Session{}).Error; err != nil {
			s.LogErr.Printf("Session", "Cleaning failed: %s", err)
		}
	}
}