// Package auth holds admin's three identities: staff, cloud customers and
// self-hosted customers. All three share one session store and one cookie.
package auth

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/redis/go-redis/v9"
)

const (
	CookieName   = "admin_session"
	SessionTTL   = 24 * time.Hour
	KindStaff    = "staff"
	KindCustomer = "customer"
)

type Session struct {
	UserID    string `json:"user_id"`
	Kind      string `json:"kind"`
	Email     string `json:"email"`
	AccountID string `json:"account_id,omitempty"` // customers only
}

var rdb *redis.Client

// InitRedis connects the session store.
//
// Username and password may both be empty for an unauthenticated instance. For
// a legacy `requirepass` Redis, pass the password with an empty username —
// go-redis then sends AUTH with one argument instead of two.
func InitRedis(addr, username, password string) {
	rdb = redis.NewClient(&redis.Options{
		Addr:     addr,
		Username: username,
		Password: password,
	})
}

func Ping(ctx context.Context) error { return rdb.Ping(ctx).Err() }

func newID() (string, error) {
	b := make([]byte, 32)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	return hex.EncodeToString(b), nil
}

func Save(ctx context.Context, s Session) (string, error) {
	id, err := newID()
	if err != nil {
		return "", err
	}
	body, err := json.Marshal(s)
	if err != nil {
		return "", err
	}
	if err := rdb.Set(ctx, "admin_session:"+id, body, SessionTTL).Err(); err != nil {
		return "", err
	}
	return id, nil
}

func Get(ctx context.Context, id string) (*Session, error) {
	body, err := rdb.Get(ctx, "admin_session:"+id).Bytes()
	if err != nil {
		return nil, err
	}
	var s Session
	if err := json.Unmarshal(body, &s); err != nil {
		return nil, err
	}
	return &s, nil
}

func Destroy(ctx context.Context, id string) { rdb.Del(ctx, "admin_session:"+id) }

func SetCookie(c *gin.Context, id string) {
	http.SetCookie(c.Writer, &http.Cookie{
		Name:     CookieName,
		Value:    id,
		Path:     "/",
		HttpOnly: true,
		Secure:   true,
		SameSite: http.SameSiteLaxMode,
		MaxAge:   int(SessionTTL.Seconds()),
	})
}

func ClearCookie(c *gin.Context) {
	http.SetCookie(c.Writer, &http.Cookie{
		Name: CookieName, Value: "", Path: "/", HttpOnly: true, Secure: true, MaxAge: -1,
	})
}
