package mockid

import (
	"errors"
	"net/http"
	"sync"
	"time"

	"git.rootprojects.org/root/hashcash"
)

var hashcashes = &hashcashDB{
	db: sync.Map{},
}

func NewHashcash(sub string, exp time.Time) *hashcash.Hashcash {

	h := hashcash.New(hashcash.Hashcash{
		Subject:   sub,
		ExpiresAt: exp,
	})

	// ignoring the error because this implementation is backed by an in-memory map
	_ = hashcashes.Store(h.Nonce, h)

	return h
}

var ErrNotFound = errors.New("not found")

func UseHashcash(hc, sub string) error {
	phony, err := hashcash.Parse(hc)
	if nil != err {
		return err
	}

	hi, ok, _ := hashcashes.Load(phony.Nonce)
	if !ok {
		return ErrNotFound
	}
	mccoy := hi.(*hashcash.Hashcash)
	mccopy := *mccoy

	mccopy.Solution = phony.Solution
	if err := mccopy.Verify(sub); nil != err {
		return err
	}

	_ = hashcashes.Delete(mccoy.Nonce)
	return nil
}

func issueHashcash(w http.ResponseWriter, r *http.Request) *hashcash.Hashcash {
	h := NewHashcash(r.Host, time.Now().Add(5*time.Minute))
	w.Header().Set("Hashcash-Challenge", h.String())
	return h
}

func requireHashcash(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		hc := r.Header.Get("Hashcash")
		_ = issueHashcash(w, r)
		if err := UseHashcash(hc, r.Host); nil != err {
			http.Error(w, "Bad Request", http.StatusBadRequest)
			return
		}

		next(w, r)
	}
}

type hashcashDB struct {
	db sync.Map
}

func (h *hashcashDB) Load(key interface{}) (value interface{}, ok bool, err error) {
	v, ok := h.db.Load(key)
	return v, ok, nil
}

func (h *hashcashDB) Store(key interface{}, value interface{}) (err error) {
	h.db.Store(key, value)
	return nil
}

func (h *hashcashDB) Delete(key interface{}) (err error) {
	h.db.Delete(key)
	return nil
}

func (h *hashcashDB) vacuum() (err error) {
	now := time.Now().UTC()
	h.db.Range(func(key interface{}, val interface{}) bool {
		v := val.(*hashcash.Hashcash)
		if v.ExpiresAt.Sub(now) < 0 {
			h.db.Delete(key)
		}
		return true
	})
	return nil
}