package database

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/xeipuuv/gojsonschema"
)

// validateSettings validates user-provided JSON-formatted
// Database Service settings against a reference JSON Schema.
func validateSettings(in string, schema interface{}) (map[string]interface{}, error) {
	var userSettings map[string]interface{}

	if err := json.Unmarshal([]byte(in), &userSettings); err != nil {
		return nil, fmt.Errorf("unable to unmarshal JSON: %w", err)
	}

	res, err := gojsonschema.Validate(
		gojsonschema.NewGoLoader(schema),
		gojsonschema.NewStringLoader(in),
	)

	if err != nil {
		// JSON Schema is provided by API and if loading fails there is nothing a user can to to fix the issue.
		// One example is incompatible regex engines for pattern validation that will prevent loading JSON schema.
		// When that happens we should still allow running the command as API would validate request.
		return userSettings, nil
	}

	if !res.Valid() {
		return nil, errors.New(strings.Join(
			func() []string {
				errs := make([]string, len(res.Errors()))
				for i, err := range res.Errors() {
					errs[i] = err.String()
				}
				return errs
			}(),
			"\n",
		))
	}

	return userSettings, nil
}

// parseBackupSchedule parses a Database Service backup
// schedule value expressed in HH:MM format and returns the discrete values
// for hour and minute, or an error if the parsing failed.
func parseBackupSchedule(v string) (int64, int64, error) {
	parts := strings.Split(v, ":")
	if len(parts) != 2 {
		return 0, 0, fmt.Errorf("invalid value %q for backup schedule, expecting HH:MM", v)
	}

	backupHour, err := strconv.Atoi(parts[0])
	if err != nil {
		return 0, 0, fmt.Errorf("invalid value %q for backup schedule hour, must be between 0 and 23", v)
	}

	backupMinute, err := strconv.Atoi(parts[1])
	if err != nil {
		return 0, 0, fmt.Errorf("invalid value %q for backup schedule minute, must be between 0 and 59", v)
	}

	return int64(backupHour), int64(backupMinute), nil
}

// PartialSettingsPatch updates all keys in `data` that exist in `patch`.
// If key from `data` is not present in `patch` then removes the key from `data`.
func PartialSettingsPatch(data, patch map[string]interface{}) {
	for key := range data {
		if v, found := patch[key]; found {
			data[key] = v
		} else {
			delete(data, key)
		}
	}
}
