package auth

import (
	"context"
	"encoding/json"
	"time"
)

// oidcState is what a login flow parks in Redis between the start redirect and
// the callback. It carries the provider as well as the instance: the callback's
// :providerId path segment is attacker-controlled, and this is the half that
// was issued by the start handler.
type oidcState struct {
	InstanceID string `json:"instance_id"`
	ProviderID string `json:"provider_id"`
}

func encodeState(s oidcState) (string, error) {
	b, err := json.Marshal(s)
	if err != nil {
		return "", err
	}
	return string(b), nil
}

func decodeState(raw string) (oidcState, bool) {
	var s oidcState
	if raw == "" {
		return oidcState{}, false
	}
	if err := json.Unmarshal([]byte(raw), &s); err != nil {
		return oidcState{}, false
	}
	if s.InstanceID == "" || s.ProviderID == "" {
		return oidcState{}, false
	}
	return s, true
}

func saveState(ctx context.Context, state string, s oidcState) error {
	raw, err := encodeState(s)
	if err != nil {
		return err
	}
	return rdb.Set(ctx, statePrefix+state, raw, 10*time.Minute).Err()
}

// consumeState is GetDel: a state is single-use, so a replayed callback finds
// nothing and is refused.
func consumeState(ctx context.Context, state string) (oidcState, bool) {
	raw, err := rdb.GetDel(ctx, statePrefix+state).Result()
	if err != nil {
		return oidcState{}, false
	}
	return decodeState(raw)
}
