package auth

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"errors"
	"time"

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

const sessionTTL = 24 * time.Hour
const sessionCookieName = "km_session"
const sessionPrefix = "km:session:"
const statePrefix = "km:state:"

type Session struct {
	UserID     string `json:"user_id"`
	InstanceID string `json:"instance_id"`
	Role       string `json:"role"`
	Email      string `json:"email"`
	Name       string `json:"name"`
}

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) error {
	rdb = redis.NewClient(&redis.Options{
		Addr:     addr,
		Username: username,
		Password: password,
	})
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	return rdb.Ping(ctx).Err()
}

// PingRedis reports whether the session store is reachable. Readiness depends
// on it: a pod that cannot reach Redis can authenticate nobody.
func PingRedis(ctx context.Context) error {
	if rdb == nil {
		return errors.New("redis not initialised")
	}
	return rdb.Ping(ctx).Err()
}

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

func SaveSession(ctx context.Context, sess *Session) (string, error) {
	id, err := randomHex(32)
	if err != nil {
		return "", err
	}
	data, err := json.Marshal(sess)
	if err != nil {
		return "", err
	}
	if err := rdb.Set(ctx, sessionPrefix+id, data, sessionTTL).Err(); err != nil {
		return "", err
	}
	return id, nil
}

func GetSession(ctx context.Context, id string) (*Session, error) {
	data, err := rdb.Get(ctx, sessionPrefix+id).Bytes()
	if err != nil {
		return nil, err
	}
	var sess Session
	if err := json.Unmarshal(data, &sess); err != nil {
		return nil, err
	}
	return &sess, nil
}

func DeleteSession(ctx context.Context, id string) error {
	return rdb.Del(ctx, sessionPrefix+id).Err()
}
