package handler

import (
	"context"
	"errors"
	"strings"
	"time"

	"github.com/micro/micro/v3/service/config"
	"github.com/micro/micro/v3/service/logger"
	"github.com/micro/micro/v3/service/model"
	cache "github.com/patrickmn/go-cache"
	"github.com/teris-io/shortid"

	"github.com/micro/services/pkg/tenant"
	url "github.com/micro/services/url/proto"
)

const hostPrefix = "https://m3o.one/u/"

type Url struct {
	pairs      model.Model
	ownerIndex model.Index
	cache      *cache.Cache
	hostPrefix string
}

func NewUrl() *Url {
	var hp string
	cfg, err := config.Get("micro.url.host_prefix")
	if err != nil {
		hp = cfg.String(hostPrefix)
	}
	if len(strings.TrimSpace(hp)) == 0 {
		hp = hostPrefix
	}

	ownerIndex := model.ByEquality("owner")
	ownerIndex.Order.Type = model.OrderTypeUnordered

	m := model.NewModel(
		model.WithKey("shortURL"),
		model.WithIndexes(ownerIndex),
	)
	m.Register(&url.URLPair{})
	return &Url{
		pairs:      m,
		ownerIndex: ownerIndex,
		hostPrefix: hp,
		cache:      cache.New(cache.NoExpiration, cache.NoExpiration),
	}
}

func (e *Url) Shorten(ctx context.Context, req *url.ShortenRequest, rsp *url.ShortenResponse) error {
	tenantID, ok := tenant.FromContext(ctx)
	if !ok {
		return errors.New("Not authorized")
	}
	sid, err := shortid.New(1, shortid.DefaultABC, 2342)
	if err != nil {
		return err
	}

	id, err := sid.Generate()
	if err != nil {
		return err
	}

	p := &url.URLPair{
		DestinationURL: req.DestinationURL,
		ShortURL:       id,
		Owner:          tenantID,
		Created:        time.Now().Unix(),
	}
	rsp.ShortURL = e.hostPrefix + id

	return e.pairs.Create(p)
}

func (e *Url) List(ctx context.Context, req *url.ListRequest, rsp *url.ListResponse) error {
	tenantID, ok := tenant.FromContext(ctx)
	if !ok {
		return errors.New("Not authorized")
	}

	rsp.UrlPairs = []*url.URLPair{}
	var err error
	if req.ShortURL != "" {
		err = e.pairs.Read(model.QueryEquals("shortURL", req.ShortURL), &rsp.UrlPairs)
	} else {
		err = e.pairs.Read(e.ownerIndex.ToQuery(tenantID), &rsp.UrlPairs)
	}
	if err != nil {
		return err
	}
	for _, v := range rsp.UrlPairs {
		// get the counter and add it to db value to improve
		// accuracy
		// A thing to keep in mind is that in memory cache hits
		// from other nodes wont be added to this. Still, hopefully good enough
		count, ok := e.cache.Get(v.ShortURL)
		if ok {
			v.HitCount += count.(int64)
		}
		v.ShortURL = e.hostPrefix + v.ShortURL
	}
	return nil
}

func (e *Url) Proxy(ctx context.Context, req *url.ProxyRequest, rsp *url.ProxyResponse) error {
	var pair url.URLPair
	id := strings.Replace(req.ShortURL, e.hostPrefix, "", -1)
	err := e.pairs.Read(model.QueryEquals("shortURL", id), &pair)
	if err != nil {
		return err
	}
	v, found := e.cache.Get(id)
	// @todo there is an ABA problem with this solution
	// when it comes to the hit counter
	if !found {
		e.cache.Set(id, int64(1), cache.NoExpiration)
	} else {
		// we null out the counter
		e.cache.Set(id, int64(0), cache.NoExpiration)
		if v.(int64)%10 == 0 {
			go func() {
				// We add instead of set in case the service runs in multiple
				// instances
				pair.HitCount += v.(int64) + int64(1)
				err = e.pairs.Update(pair)
				if err != nil {
					logger.Error(err)
				}
			}()
		}
	}

	rsp.DestinationURL = pair.DestinationURL
	return nil
}
