package utils

import (
	"archive/zip"
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
	"time"

	"gitlab.com/uafrica/go-utils/struct_utils"
)

// GetEnv is a helper function for getting environment variables with a default
func GetEnv(name string, def string) (env string) {
	// If variable not set, provide default
	if env = os.Getenv(name); env == "" {
		env = def
	}
	return
}

// CorsHeaders returns a map to allow Cors
func CorsHeaders() map[string]string {
	return map[string]string{
		"Access-Control-Allow-Origin":      "*",
		"Access-Control-Allow-Headers":     "*",
		"Access-Control-Allow-Methods":     "OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD",
		"Access-Control-Max-Age":           "86400",
		"Access-Control-Allow-Credentials": "true",
	}
}

func CorsHeadersCached() map[string]string {
	return map[string]string{
		"Access-Control-Allow-Origin":      "*",
		"Access-Control-Allow-Headers":     "*",
		"Access-Control-Allow-Methods":     "OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD",
		"Access-Control-Max-Age":           "86400",
		"Access-Control-Allow-Credentials": "true",
	}
}

func ZipData(fileName string, data []byte) ([]byte, error) {
	// Create zip
	buf := new(bytes.Buffer)

	// Create a new zip archive.
	zipWriter := zip.NewWriter(buf)

	header := &zip.FileHeader{
		Name:     fileName,
		Method:   zip.Deflate,
		Modified: time.Now(),
	}

	// Write data
	file, err := zipWriter.CreateHeader(header)
	if err != nil {
		return nil, err
	}
	_, err = file.Write(data)
	if err != nil {
		return nil, err
	}

	// Close zip
	err = zipWriter.Close()
	if err != nil {
		return []byte(""), err
	}

	return buf.Bytes(), nil
}

func UnzipData(data []byte) (map[string][]byte, error) {
	// Create a new zip reader.
	zipReader, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
	if err != nil {
		return nil, err
	}

	// Read all the files from zip archive
	var fileData = make(map[string][]byte)
	for _, zipFile := range zipReader.File {
		unzippedData, err := readZipFile(zipFile)
		if err != nil {
			continue
		}

		fileData[zipFile.FileHeader.Name] = unzippedData
	}

	return fileData, nil
}

func readZipFile(zipFile *zip.File) ([]byte, error) {
	zipFileData, err := zipFile.Open()
	if err != nil {
		return nil, err
	}
	defer zipFileData.Close()

	return ioutil.ReadAll(zipFileData)
}

func DeepCopy(toValue interface{}, fromValue interface{}) (err error) {
	valueBytes, err := json.Marshal(fromValue)
	if err != nil {
		return err
	}
	err = struct_utils.UnmarshalJSON(valueBytes, toValue)
	if err != nil {
		return err
	}
	return nil
}

func UnwrapBool(b *bool) bool {
	if b == nil {
		return false
	}
	return *b
}

// MapStringInterfaceToMapStringString converts a generic value typed map to a map with string values
func MapStringInterfaceToMapStringString(inputMap map[string]interface{}) map[string]string {
	query := make(map[string]string)
	for mapKey, mapVal := range inputMap {
		// Check if mapVal is a slice or a single value
		switch mapValTyped := mapVal.(type) {
		case []interface{}:
			// Slice - convert each element individually
			var mapValString []string

			// Loop through each element in the slice and check the type
			for _, sliceElem := range mapValTyped {
				switch sliceElemTyped := sliceElem.(type) {
				case string:
					// Enclose strings in escaped quotations
					mapValString = append(mapValString, fmt.Sprintf("\"%v\"", sliceElemTyped))
				case float64:
					// Use FormatFloat for least amount of precision.
					mapValString = append(mapValString, strconv.FormatFloat(sliceElemTyped, 'f', -1, 64))
				default:
					// Convert to string
					mapValString = append(mapValString, fmt.Sprintf("%v", sliceElemTyped))
				}
			}
			// Join as a comma seperated array
			query[mapKey] = "[" + strings.Join(mapValString, ",") + "]"
		default:
			// Single value - convert to string
			query[mapKey] = fmt.Sprintf("%v", mapVal)
		}
	}

	return query
}

// MergeMaps If there are similar properties in the maps, the last one will be used as the value
func MergeMaps(maps ...map[string]string) map[string]string {
	ret := map[string]string{}
	for _, mapV := range maps {
		for k, v := range mapV {
			ret[k] = v
		}
	}
	return ret
}