// Package http adds a http lock implementation
package http

import (
	"errors"
	"fmt"
	"hash/crc32"
	"io/ioutil"
	"net/http"
	"net/url"
	"path/filepath"
	"strings"

	"github.com/micro/go-micro/v2/sync/lock"
)

var (
	DefaultPath    = "/sync/lock"
	DefaultAddress = "localhost:8080"
)

type httpLock struct {
	opts lock.Options
}

func (h *httpLock) url(do, id string) (string, error) {
	sum := crc32.ChecksumIEEE([]byte(id))
	node := h.opts.Nodes[sum%uint32(len(h.opts.Nodes))]

	// parse the host:port or whatever
	uri, err := url.Parse(node)
	if err != nil {
		return "", err
	}

	if len(uri.Scheme) == 0 {
		uri.Scheme = "http"
	}

	// set path
	// build path
	path := filepath.Join(DefaultPath, do, h.opts.Prefix, id)
	uri.Path = path

	// return url
	return uri.String(), nil
}

func (h *httpLock) Acquire(id string, opts ...lock.AcquireOption) error {
	var options lock.AcquireOptions
	for _, o := range opts {
		o(&options)
	}

	uri, err := h.url("acquire", id)
	if err != nil {
		return err
	}

	ttl := fmt.Sprintf("%d", int64(options.TTL.Seconds()))
	wait := fmt.Sprintf("%d", int64(options.Wait.Seconds()))

	rsp, err := http.PostForm(uri, url.Values{
		"id":   {id},
		"ttl":  {ttl},
		"wait": {wait},
	})
	if err != nil {
		return err
	}
	defer rsp.Body.Close()

	b, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return err
	}

	// success
	if rsp.StatusCode == 200 {
		return nil
	}

	// return error
	return errors.New(string(b))
}

func (h *httpLock) Release(id string) error {
	uri, err := h.url("release", id)
	if err != nil {
		return err
	}

	vals := url.Values{
		"id": {id},
	}

	req, err := http.NewRequest("DELETE", uri, strings.NewReader(vals.Encode()))
	if err != nil {
		return err
	}

	rsp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer rsp.Body.Close()

	b, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return err
	}

	// success
	if rsp.StatusCode == 200 {
		return nil
	}

	// return error
	return errors.New(string(b))
}

func NewLock(opts ...lock.Option) lock.Lock {
	var options lock.Options
	for _, o := range opts {
		o(&options)
	}

	if len(options.Nodes) == 0 {
		options.Nodes = []string{DefaultAddress}
	}

	return &httpLock{
		opts: options,
	}
}