package photosapi

import (
	"crypto"
	"encoding/base64"
	"errors"
	"net/http"
	"time"

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

// Errors
var (
	ErrAccountExists = errors.New("account exists")
	ErrAccountAuth   = errors.New("login or password incorrect")
)

// Model
type Account struct {
	ID                uint32    `gorm:"primary_key" json:"-"`
	Login             string    `gorm:"unique;size:64;not null" json:"login"`
	Password          string    `gorm:"-" json:"-"`
	EncryptedPassword string    `gorm:"size:44;not null" json:"-"`
	CreatedAt         time.Time `json:"created_at"`
	UpdatedAt         time.Time `json:"-"`
}

func (a *Account) BeforeCreate(tx *gorm.DB) error {
	if a.EncryptedPassword == "" {
		a.EncryptPassword()
	}
	return nil
}

func (a *Account) EncryptPassword() {
	sha1 := crypto.SHA256.New()
	sha1.Write([]byte(a.Password))
	a.EncryptedPassword = base64.StdEncoding.EncodeToString(sha1.Sum(nil))
}

func NewAccount(login string, password string) *Account {
	a := &Account{
		Login:    login,
		Password: password,
	}
	a.EncryptPassword()
	return a
}

// Service
type SignupRequest struct {
	Login    string `json:"login" binding:"required,min=3,max=40,alphanum"`
	Password string `json:"password" binding:"required,min=8,max=40"`
}

type LoginRequest struct {
	Login    string `json:"login" binding:"required"`
	Password string `json:"password" binding:"required"`
}

type LoginResponse struct {
	Token string `json:"token"`
}

func (s *Service) Signup(c *gin.Context) {
	var account *SignupRequest

	if c.BindJSON(&account) != nil {
		return
	}

	var accountExists int64
	if err := s.DB.Model(&Account{}).Where("login = ?", account.Login).Count(&accountExists).Error; err != nil {
		c.AbortWithError(http.StatusInternalServerError, err)
		return
	}
	if accountExists > 0 {
		c.AbortWithError(http.StatusConflict, ErrAccountExists)
		return
	}
	if err := s.DB.Create(NewAccount(account.Login, account.Password)).Error; err != nil {
		c.AbortWithError(http.StatusConflict, err)
		return
	}

	c.Status(http.StatusNoContent)
}

func (s *Service) Login(c *gin.Context) {
	var account *LoginRequest

	if c.BindJSON(&account) != nil {
		return
	}

	session, err := NewSession(s.DB, account.Login, account.Password)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			c.AbortWithError(http.StatusNotFound, ErrAccountAuth)
		} else {
			c.AbortWithError(http.StatusInternalServerError, err)
		}
		return
	}

	c.JSON(http.StatusOK, LoginResponse{session.Token})
}

func (s *Service) Logout(c *gin.Context) {
	res := s.DB.Where("token = ?", c.GetString("token")).Delete(&Session{})
	if res.Error != nil {
		c.AbortWithError(http.StatusInternalServerError, res.Error)
		return
	}
	if res.RowsAffected == 0 {
		c.AbortWithError(http.StatusNotFound, ErrSessionNotFound)
		return
	}
	c.Status(http.StatusNoContent)
}

func (s *Service) AccountInit() {
	ac := s.Gin.Group("/account")
	ac.POST("/signup", s.Signup)
	ac.POST("/login", s.Login)
	ac.GET("/logout", s.RequireAuthToken, s.Logout)
}