package data

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/base32"
	"time"
	"context"
	"database/sql"

	"party.at/party/internal/validator"
)

const (
	ScopeActivation = "activation"
	ScopeAuthentication = "authentication"
)

type Token struct {
	Plaintext      string    `json:"token"`
	Hash           []byte    `json:"-"`
	UserID         int64     `json:"-"`
	UserIdentityID int64     `json:"-"`
	Expiry         time.Time `json:"expiry"`
	Scope          string    `json:"-"`
}

func generateToken(userID int64, userIdentityID int64, ttl time.Duration, scope string) (*Token, error) {
	token := &Token{
		UserID: userID,
		UserIdentityID: userIdentityID,
		Expiry: time.Now().Add(ttl),
		Scope: scope,
	}

	randomBytes := make([]byte, 16)

	_, err := rand.Read(randomBytes)
	if err != nil {
		return nil, err
	}

	token.Plaintext = base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(randomBytes)

	hash := sha256.Sum256([]byte(token.Plaintext))
	token.Hash = hash[:]

	return token, nil
}

func ValidateTokenPlaintext(v *validator.Validator, tokenPlaintext string) {
	v.Check(tokenPlaintext != "", "token", "must be provided")
	v.Check(len(tokenPlaintext) == 26, "token", "must be 26 bytes long")
}

type TokenModel struct {
	DB *sql.DB
}

func (m TokenModel) New(userID int64, userIdentityID int64, ttl time.Duration, scope string) (*Token, error) {
	token, err := generateToken(userID, userIdentityID, ttl, scope)
	if err != nil {
		return nil, err
	}

	err = m.Insert(token)
	return token, err
}

func (m TokenModel) Insert(token *Token) error {
	query :=`
	INSERT INTO tokens (hash, user_id, user_identity_id, expiry, scope)
	VALUES ($1, $2, $3, $4, $5)`

	args := []interface{}{token.Hash, token.UserID, token.UserIdentityID, token.Expiry, token.Scope}

	ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
	defer cancel()

	_, err := m.DB.ExecContext(ctx, query, args...)
	return err
}

func (m TokenModel) Delete(hash []byte) error {
	query := `DELETE FROM tokens WHERE hash = $1`

	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	_, err := m.DB.ExecContext(ctx, query, hash)
	return err
}

func (m TokenModel) DeleteAllForUser(scope string, userID int64) error {
	query :=`
DELETE FROM tokens
WHERE scope = $1 AND user_id = $2`

	ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
	defer cancel()

	_, err := m.DB.ExecContext(ctx, query, scope, userID)
	return err
}
