// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package monitor

import (
	"strconv"
	"strings"
	"time"

	"yunion.io/x/jsonutils"
	"yunion.io/x/pkg/errors"

	api "yunion.io/x/onecloud/pkg/apis/monitor"
	"yunion.io/x/onecloud/pkg/mcclient/modules/monitor"
)

type ResourceMetricsOptions struct {
	RES_TYPE  string   `help:"resource type: host or guest" choices:"host|guest"`
	ResIds    []string `help:"resource IDs" json:"res_ids" nargs:"+"`
	StartTime string   `help:"start time (RFC3339). e.g.: 2023-12-06T21:54:42Z" json:"-"`
	EndTime   string   `help:"end time (RFC3339). e.g.: 2023-12-18T21:54:42Z" json:"-"`
	Interval  string   `help:"query interval. e.g.: 5m, 1h" json:"interval"`
}

func (o *ResourceMetricsOptions) GetInput() (*api.ResourceMetricsQueryInput, error) {
	input := &api.ResourceMetricsQueryInput{
		ResType:  o.RES_TYPE,
		ResIds:   o.ResIds,
		Interval: o.Interval,
	}
	if o.StartTime != "" {
		t, err := time.Parse(time.RFC3339, o.StartTime)
		if err != nil {
			return nil, errors.Wrapf(err, "invalid start_time %q", o.StartTime)
		}
		input.StartTime = t
	}
	if o.EndTime != "" {
		t, err := time.Parse(time.RFC3339, o.EndTime)
		if err != nil {
			return nil, errors.Wrapf(err, "invalid end_time %q", o.EndTime)
		}
		input.EndTime = t
	}
	return input, nil
}

type MeasurementsQueryOptions struct {
	Scope           string `json:"scope"`
	ProjectDomainId string `json:"project_domin_id"`
	ProjectId       string `json:"project_id"`
}

func (o MeasurementsQueryOptions) Params() (jsonutils.JSONObject, error) {
	return jsonutils.Marshal(o), nil
}

func (o MeasurementsQueryOptions) Property() string {
	return "measurements"
}

type DatabasesQueryOptions struct{}

func (o DatabasesQueryOptions) Params() (jsonutils.JSONObject, error) {
	return nil, nil
}

func (o DatabasesQueryOptions) Property() string {
	return "databases"
}

type MetricQueryOptions struct {
	MeasurementsQueryOptions

	MEASUREMENT string `help:"metric measurement. e.g.: cpu, vm_cpu, vm_mem, disk..."`
	FIELD       string `help:"metric field. e.g.: usage_active, free..."`

	Interval        string   `help:"metric interval. e.g.: 5m, 1h"`
	From            string   `help:"start time(RFC3339 format). e.g.: 2023-12-06T21:54:42.123Z"`
	To              string   `help:"end time(RFC3339 format). e.g.: 2023-12-18T21:54:42.123Z"`
	Tags            []string `help:"filter tags. e.g.: vm_name=vm1"`
	GroupBy         []string `help:"group by tag"`
	UseMean         bool     `help:"calcuate mean result for field"`
	SkipCheckSeries bool     `help:"skip checking series: not fetch extra tags from region service"`
	Reducer         string   `help:"series result reducer. e.g.: sum, percentile(95)"`
}

func (o MetricQueryOptions) GetQueryInput() (*api.MetricQueryInput, error) {
	input := monitor.NewMetricQueryInput(o.MEASUREMENT)
	input.Interval(o.Interval)
	if o.SkipCheckSeries {
		input.SkipCheckSeries(true)
	}
	input.Scope(o.Scope)

	// parse time
	if o.From != "" {
		fromTime, err := time.Parse(time.RFC3339, o.From)
		if err != nil {
			return nil, errors.Wrapf(err, "invalid from time: %q", o.From)
		}
		input.From(fromTime)
	}
	if o.To != "" {
		toTime, err := time.Parse(time.RFC3339, o.To)
		if err != nil {
			return nil, errors.Wrapf(err, "invalid to time: %q", o.To)
		}
		input.To(toTime)
	}

	sel := input.Selects().Select(o.FIELD)
	if o.UseMean {
		sel.MEAN()
	}

	where := input.Where()
	for _, tag := range o.Tags {
		if strings.Contains(tag, "=") {
			info := strings.Split(tag, "=")
			if len(info) == 2 {
				where.Equal(info[0], info[1])
			} else {
				return nil, errors.Errorf("invalid tag: %q, len: %d", tag, len(info))
			}
		} else {
			return nil, errors.Errorf("invalid tag: %q", tag)
		}
	}

	groupBy := input.GroupBy()
	for _, tag := range o.GroupBy {
		groupBy.TAG(tag)
	}

	if o.Reducer != "" {
		r, err := o.parseReducer(o.Reducer)
		if err != nil {
			return nil, errors.Wrapf(err, "invalid reducer: %q", o.Reducer)
		}
		input.Reducer(r.Type, r.Params)
	}

	return input.ToQueryData(), nil
}

func (o MetricQueryOptions) parseReducer(reducer string) (*api.Condition, error) {
	if reducer == "" {
		return nil, errors.Errorf("invalid reducer %q", reducer)
	}
	parts := strings.Split(reducer, "(")
	if len(parts) < 1 {
		return nil, errors.Errorf("invalid reducer %q", reducer)
	}
	rType := parts[0]
	cond := &api.Condition{
		Type: rType,
	}
	if len(parts) > 1 {
		params := []float64{}
		paramStr := parts[1]
		paramsStr := strings.Split(strings.TrimSuffix(paramStr, ")"), ",")
		for _, param := range paramsStr {
			f, err := strconv.ParseFloat(strings.ReplaceAll(param, " ", ""), 64)
			if err != nil {
				return nil, errors.Wrapf(err, "invalid reducer param %q", param)
			}
			params = append(params, f)
		}
		cond.Params = params
	}
	return cond, nil
}
