package routes

import (
	"net/http"
	"net/url"
	"regexp"

	"github.com/gorilla/mux"
	"github.com/timshannon/badgerhold"

	"sour.is/x/toolbox/httpsrv"
	"sour.is/x/toolbox/log"
	"sour.is/x/toolbox/uuid"
)

type logger struct{}

func (logger) Errorf(s string, o ...interface{})   { log.Errorf(s, o...) }
func (logger) Warningf(s string, o ...interface{}) { log.Warningf(s, o...) }
func (logger) Infof(s string, o ...interface{})    { log.Infof(s, o...) }
func (logger) Debugf(s string, o ...interface{})   { log.Debugf(s, o...) }

func init() {
	s := &shortDB{}
	httpsrv.RegisterModule("short", s.config)

	httpsrv.HttpRegister("short", httpsrv.HttpRoutes{
		{Name: "get", Method: "GET", Pattern: "/s/{id}", HandlerFunc: s.get},
		{Name: "put", Method: "PUT", Pattern: "/s/{id}", HandlerFunc: s.put},
	})
}

type shortDB struct {
	options badgerhold.Options
}

func (s *shortDB) config(config map[string]string) {
	s.options = badgerhold.DefaultOptions
	s.options.Options = s.options.WithLogger(logger{})

	var ok bool
	if s.options.Dir, ok = config["index"]; !ok {
		s.options.Dir = "data"
	}

	if s.options.ValueDir, ok = config["value"]; !ok {
		s.options.ValueDir = "data"
	}

	store, err := badgerhold.Open(s.options)
	defer func() {
		if err = store.Close(); err != nil {
			log.Fatal(err)
		}
	}()
	if err != nil {
		log.Fatal(err)
	}
}
func (s *shortDB) get(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	id := vars["id"]

	store, err := badgerhold.Open(s.options)
	defer func() {
		if err = store.Close(); err != nil {
			log.Fatal(err)
		}
	}()
	if err != nil {
		httpsrv.WriteText(w, 500, err.Error())
		return
	}

	var url shortURL
	err = store.Get(id, &url)
	if err != nil && err != badgerhold.ErrNotFound {
		httpsrv.WriteText(w, 500, err.Error())
		return
	}
	if err == badgerhold.ErrNotFound {
		httpsrv.WriteText(w, 404, "not found")
		return
	}

	w.Header().Set("Location", url.URL)
	w.WriteHeader(http.StatusFound)
}
func (s *shortDB) put(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	id := vars["id"]
	secret := r.FormValue("secret")
	u, err := url.Parse(r.FormValue("url"))
	if err != nil {
		httpsrv.WriteText(w, 400, "bad url")
		return
	}

	store, err := badgerhold.Open(s.options)
	defer func() {
		if err = store.Close(); err != nil {
			log.Fatal(err)
		}
	}()
	if err != nil {
		httpsrv.WriteText(w, 500, err.Error())
		return
	}

	url := &shortURL{}
	err = store.Get(id, url)
	if err != nil && err != badgerhold.ErrNotFound {
		httpsrv.WriteText(w, 500, err.Error())
		return
	}

	if err == badgerhold.ErrNotFound {
		url = newShortURL(id, secret, u.String())

		err := store.Insert(url.ID, url)
		if err != nil {
			httpsrv.WriteText(w, 500, err.Error())
			return
		}

		httpsrv.WriteObject(w, 200, url)
		return
	}

	if secret == "" {
		httpsrv.WriteError(w, 401, "no auth")
		return
	}

	if secret != url.Secret {
		httpsrv.WriteError(w, 403, "forbidden")
		return
	}

	err = store.Upsert(url.ID, url)
	if err != nil {
		httpsrv.WriteText(w, 500, err.Error())
		return
	}

	httpsrv.WriteObject(w, 200, url)
}

type shortURL struct {
	ID     string `badgerhold:"unique"`
	URL    string
	Secret string
}

func newShortURL(id, secret, u string) *shortURL {
	m, err := regexp.MatchString("[a-z-]{1,64}", id)
	if id == "" || !m || err != nil {
		id = uuid.V4()
	}
	m, err = regexp.MatchString("[a-z-]{1,64}", secret)
	if secret == "" || !m || err != nil {
		secret = uuid.V4()
	}
	return &shortURL{ID: id, Secret: secret, URL: u}
}
