package binder

// |@@| C

import (
	"context"
	"encoding/json"
	"fmt"
	"gardening/src/lib"
	erro "gardening/src/lib/error"
	"gardening/src/lib/fetcher"
	"gardening/src/lib/in_context"
	"gardening/src/lib/presenter"
	"gardening/src/lib/uploader"
	"github.com/gin-gonic/gin"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"
)

type Binder struct {
	FilesUploader *uploader.FilesUploader
}

func (this *Binder) HasQuery(key string, c *gin.Context) bool {
	_, has := c.GetQuery(key)
	return has
}

func (this *Binder) PostFormStringField(key string, c *gin.Context, inContext *in_context.InContext) string {
	v, has := c.GetPostForm(key)
	inContext.RequestPresence = append(inContext.RequestPresence, &in_context.RequestPresence{
		Section:  "form",
		Key:      key,
		Presence: has,
	})
	return v
}

func (this *Binder) PostFormTimeField(key string, c *gin.Context, inContext *in_context.InContext) time.Time {
	s := c.PostForm(key)
	layout := "2006-01-02T15:04:05"
	if len(s) == len("2006-01-02T15:04") {
		layout = "2006-01-02T15:04"
	}
	v := c.PostForm(key)
	if v == "" {
		v = "2006-01-02T15:04:05"
	}
	t, err := time.Parse(layout, v)
	if err != nil {
		panic(err)
	}
	return t
}

func (this *Binder) PostFormTextField(key string, c *gin.Context, inContext *in_context.InContext) string {
	return c.PostForm(key)
}

func (this *Binder) PostFormJsonField(key string, c *gin.Context, inContext *in_context.InContext) json.RawMessage {
	return []byte(c.PostForm(key))
}

func (this *Binder) PostFormDecimalField(key string, c *gin.Context, inContext *in_context.InContext) float64 {
	v, err := strconv.ParseFloat(c.PostForm(key), 64)
	if err != nil {
		panic(err)
	}
	return v
}

func (this *Binder) PostFormIntegerField(key string, c *gin.Context, inContext *in_context.InContext) int {
	v, _ := strconv.Atoi(c.PostForm(key))
	return v
}

func (this *Binder) PostFormBooleanField(key string, c *gin.Context, inContext *in_context.InContext) bool {
	if c.PostForm(key) == "on" || c.PostForm(key) == "true" {
		return true
	}
	return false
}

func (this *Binder) PostFileField(ctx context.Context, name string, c *gin.Context) (string, error) {
	file, err := c.FormFile("file[" + name + "]")
	if err != nil {
		return "", nil
	}
	if file.Size == 0 {
		return "", nil
	}
	dst := this.FilesUploader.GenerateTmpUploadFilepath(ctx, file.Filename)
	if err := c.SaveUploadedFile(file, dst); err != nil {
		return "", erro.W("Unable to save the file", err)
	}
	return dst, nil
}

func (this *Binder) QueryIntegerField(key string, c *gin.Context, inContext *in_context.InContext) int {
	v, _ := strconv.Atoi(c.Query(key))
	return v
}
func (this *Binder) QueryStringField(key string, c *gin.Context, inContext *in_context.InContext) string {
	return c.Query(key)
}

func (this *Binder) QueryTimeField(key string, c *gin.Context, inContext *in_context.InContext) time.Time {
	s := c.Query(key)
	layout := "2006-01-02T15:04:05"
	if len(s) == len("2006-01-02T15:04") {
		layout = "2006-01-02T15:04"
	}
	v := c.Query(key)
	if v == "" {
		v = "2006-01-02T15:04:05"
	}
	t, err := time.Parse(layout, v)
	if err != nil {
		panic(err)
	}
	return t
}

func (this *Binder) QueryTextField(key string, c *gin.Context, inContext *in_context.InContext) string {
	return c.Query(key)
}

func (this *Binder) QueryJsonField(key string, c *gin.Context, inContext *in_context.InContext) json.RawMessage {
	return []byte(c.Query(key))
}

func (this *Binder) QueryDecimalField(key string, c *gin.Context, inContext *in_context.InContext) float64 {
	s, has := c.GetQuery(key)
	if !has {
		return 0.00
	}
	v, err := strconv.ParseFloat(s, 64)
	if err != nil {
		panic(err)
	}
	return v
}

func (this *Binder) QueryBooleanField(key string, c *gin.Context, inContext *in_context.InContext) bool {
	if c.Query(key) == "on" || c.Query(key) == "true" {
		return true
	}
	return false
}

func (this *Binder) ExtractColumnsFromUrl(c *gin.Context) []presenter.Column {
	var columns []presenter.Column
	query := c.Request.URL.RawQuery
	decoded, err := url.QueryUnescape(query)
	lib.Poe(err)
	params := strings.Split(decoded, "&")
	if len(params) < 1 {
		return columns
	}
	var filtersFieldsParams []string
	for _, param := range params {
		if strings.HasPrefix(param, "columns[visibility]") {
			value := strings.TrimPrefix(param, "columns[visibility]")
			filtersFieldsParams = append(filtersFieldsParams, value)
		}
	}
	for _, param := range filtersFieldsParams {
		ss := strings.Split(param, "=")
		keysString := ss[0]
		rawValue := ss[1]
		keysString = strings.TrimPrefix(keysString, "[")
		keysString = strings.TrimSuffix(keysString, "]")
		keys := strings.Split(keysString, "][")
		value := true
		if rawValue == "true" {
			value = false
		}
		column := presenter.Column{
			Key:    keys[0],
			Hidden: value,
		}
		columns = append(columns, column)
	}
	return columns
}

func (this *Binder) ExtractRawFiltersFromUrl(c *gin.Context) []fetcher.RawFilter {
	var filters []fetcher.RawFilter

	query := c.Request.URL.RawQuery
	decoded, err := url.QueryUnescape(query)
	lib.Poe(err)
	params := strings.Split(decoded, "&")
	if len(params) < 1 {
		return filters
	}
	var filtersFieldsParams []string
	for _, param := range params {
		if strings.HasPrefix(param, "filters[fields]") {
			value := strings.TrimPrefix(param, "filters[fields]")
			filtersFieldsParams = append(filtersFieldsParams, value)
		}
	}
	for _, param := range filtersFieldsParams {
		ss := strings.Split(param, "=")
		keysString := ss[0]
		value := ss[1]
		keysString = strings.TrimPrefix(keysString, "[")
		keysString = strings.TrimSuffix(keysString, "]")
		keys := strings.Split(keysString, "][")
		filter := fetcher.RawFilter{
			Type:      "fields",
			Key:       keys[0],
			Operation: keys[1],
			Value:     value,
		}
		filters = append(filters, filter)
	}
	return filters
}

func (this *Binder) PostFormNestedField(c *gin.Context, name string) []string {
	var indexes []string
	prefix := strings.TrimSuffix(name, "%s]")
	seen := func(i string) bool {
		for _, v := range indexes {
			if v == i {
				return true
			}
		}
		return false
	}
	for key := range c.Request.PostForm {
		if strings.HasPrefix(key, prefix) {
			suffix := key[len(prefix):]
			bracketIndex := strings.Index(suffix, "]")
			if bracketIndex > 0 {
				index := suffix[:bracketIndex]
				if !seen(index) {
					indexes = append(indexes, index)
				}
			}
		}
	}
	sort.Strings(indexes)
	return indexes
}

func (this *Binder) DynamicKey(name string, index string) string {
	return fmt.Sprintf(name, index)
}
