// 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 models

import (
	"context"
	"fmt"
	"time"

	"yunion.io/x/jsonutils"
	"yunion.io/x/log"
	"yunion.io/x/pkg/errors"
	"yunion.io/x/pkg/util/timeutils"
	"yunion.io/x/sqlchemy"

	"yunion.io/x/onecloud/pkg/apis/monitor"
	"yunion.io/x/onecloud/pkg/cloudcommon/db"
	"yunion.io/x/onecloud/pkg/httperrors"
	"yunion.io/x/onecloud/pkg/mcclient"
	"yunion.io/x/onecloud/pkg/util/stringutils2"
)

const (
	DEFAULT_SHEILD_TIME = 100 // year
)

var (
	AlertRecordShieldManager *SAlertRecordShieldManager
)

// +onecloud:swagger-gen-model-singular=alertrecordshield
// +onecloud:swagger-gen-model-plural=alertrecordshields
type SAlertRecordShieldManager struct {
	db.SEnabledResourceBaseManager
	db.SStatusStandaloneResourceBaseManager
	SMonitorScopedResourceManager
}

func init() {
	AlertRecordShieldManager = &SAlertRecordShieldManager{
		SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager(
			SAlertRecordShield{},
			"alertrecordshield_tbl",
			"alertrecordshield",
			"alertrecordshields",
		),
	}

	AlertRecordShieldManager.SetVirtualObject(AlertRecordShieldManager)
}

type SAlertRecordShield struct {
	//db.SVirtualResourceBase
	db.SEnabledResourceBase
	db.SStatusStandaloneResourceBase
	SMonitorScopedResource

	AlertId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" json:"alert_id"`
	ResId   string `width:"36" nullable:"false"  create:"optional" list:"user" update:"user" json:"res_id"`
	ResType string `width:"36" nullable:"false"  create:"optional" list:"user" update:"user" json:"res_type"`

	StartTime time.Time `required:"optional" list:"user" update:"user" json:"start_time"`
	EndTime   time.Time `required:"optional" list:"user" update:"user" json:"end_time"`
}

func (manager *SAlertRecordShieldManager) ListItemExportKeys(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, keys stringutils2.SSortedStrings) (*sqlchemy.SQuery, error) {
	q, err := manager.SStatusStandaloneResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
	if err != nil {
		return nil, errors.Wrap(err, "SStatusStandaloneResourceBaseManager.ListItemExportKeys")
	}
	q, err = manager.SScopedResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
	if err != nil {
		return nil, errors.Wrap(err, "SScopedResourceBaseManager.ListItemExportKeys")
	}
	return q, nil
}

func (manager *SAlertRecordShieldManager) ListItemFilter(
	ctx context.Context, q *sqlchemy.SQuery,
	userCred mcclient.TokenCredential,
	query monitor.AlertRecordShieldListInput,
) (*sqlchemy.SQuery, error) {
	var err error

	q, err = manager.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StandaloneResourceListInput)
	if err != nil {
		return nil, errors.Wrap(err, "SStandaloneResourceBaseManager.ListItemFilter")
	}
	q, err = manager.SEnabledResourceBaseManager.ListItemFilter(ctx, q, userCred,
		query.EnabledResourceBaseListInput)
	if err != nil {
		return nil, errors.Wrap(err, "SEnabledResourceBaseManager.ListItemFilter")
	}
	q, err = manager.SScopedResourceBaseManager.ListItemFilter(ctx, q, userCred,
		query.ScopedResourceBaseListInput)
	if err != nil {
		return nil, errors.Wrap(err, "SScopedResourceBaseManager.ListItemFilter")
	}

	q = manager.shieldListByDetailsFeild(q, query)
	return q, nil
}

func (manager *SAlertRecordShieldManager) shieldListByDetailsFeild(query *sqlchemy.SQuery,
	input monitor.AlertRecordShieldListInput) *sqlchemy.SQuery {
	if len(input.ResId) != 0 {
		query.Filter(sqlchemy.Equals(query.Field("res_id"), input.ResId))
	}
	if len(input.ResType) != 0 {
		query.Filter(sqlchemy.Equals(query.Field("res_type"), input.ResType))
	}
	if len(input.AlertId) != 0 {
		query.Filter(sqlchemy.Equals(query.Field("alert_id"), input.AlertId))
	}

	alertQuery := CommonAlertManager.Query().SubQuery()
	if len(input.AlertName) != 0 {
		query.Join(alertQuery, sqlchemy.Equals(query.Field("alert_id"),
			alertQuery.Field("id"))).Filter(sqlchemy.Equals(alertQuery.Field("name"), input.AlertName))
	}
	if len(input.ResName) != 0 {
		resQuery := MonitorResourceManager.Query().SubQuery()
		query.Join(resQuery, sqlchemy.Equals(query.Field("res_id"), resQuery.Field("res_id"))).Filter(sqlchemy.Equals(
			resQuery.Field("name"), input.ResName))
	}
	if input.StartTime != nil {
		query.Filter(sqlchemy.LE(query.Field("start_time"), input.StartTime))
	}
	if input.EndTime != nil {
		query.Filter(sqlchemy.GE(query.Field("end_time"), input.EndTime))
	}
	return query
}

func (man *SAlertRecordShieldManager) OrderByExtraFields(
	ctx context.Context,
	q *sqlchemy.SQuery,
	userCred mcclient.TokenCredential,
	input monitor.AlertRecordListInput,
) (*sqlchemy.SQuery, error) {
	var err error

	q, err = man.SStatusStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, input.StatusStandaloneResourceListInput)
	if err != nil {
		return nil, errors.Wrap(err, "SStandaloneResourceBaseManager.OrderByExtraFields")
	}
	q, err = man.SScopedResourceBaseManager.OrderByExtraFields(ctx, q, userCred, input.ScopedResourceBaseListInput)
	if err != nil {
		return nil, errors.Wrap(err, "SScopedResourceBaseManager.OrderByExtraFields")
	}
	return q, nil
}

func (man *SAlertRecordShieldManager) FetchCustomizeColumns(
	ctx context.Context,
	userCred mcclient.TokenCredential,
	query jsonutils.JSONObject,
	objs []interface{},
	fields stringutils2.SSortedStrings,
	isList bool,
) []monitor.AlertRecordShieldDetails {
	rows := make([]monitor.AlertRecordShieldDetails, len(objs))
	stdRows := man.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
	scopedRows := man.SScopedResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
	for i := range rows {
		rows[i] = monitor.AlertRecordShieldDetails{
			StatusStandaloneResourceDetails: stdRows[i],
			ScopedResourceBaseInfo:          scopedRows[i],
		}
		rows[i], _ = objs[i].(*SAlertRecordShield).GetMoreDetails(ctx, rows[i])
	}
	return rows
}

func (shield *SAlertRecordShield) GetMoreDetails(ctx context.Context, out monitor.AlertRecordShieldDetails) (monitor.
	AlertRecordShieldDetails, error) {
	commonAlert, err := CommonAlertManager.GetAlert(shield.AlertId)
	if err != nil {
		log.Errorf("GetAlert byId:%s err:%v", shield.AlertId, err)
		return out, nil
	}
	// Alert May delete By someone
	out.AlertName = commonAlert.Name
	alertDetails, err := commonAlert.GetMoreDetails(ctx, monitor.CommonAlertDetails{})
	out.CommonAlertDetails = alertDetails

	resources, err := MonitorResourceManager.GetMonitorResources(monitor.MonitorResourceListInput{ResId: []string{shield.ResId}})
	if err != nil {
		return out, errors.Errorf("getMonitorResource:%s err:%v", shield.ResId, err)
	}
	if len(resources) == 0 {
		return out, nil
	}
	if shield.EndTime.Before(time.Now()) {
		out.Expired = true
	}
	out.ResName = resources[0].Name
	return out, nil
}

func (man *SAlertRecordShieldManager) HasName() bool {
	return false
}

func (man *SAlertRecordShieldManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, _ jsonutils.JSONObject,
	data monitor.AlertRecordShieldCreateInput) (monitor.AlertRecordShieldCreateInput, error) {
	if len(data.AlertId) == 0 {
		return data, httperrors.NewInputParameterError("alert_id  is empty")
	}
	alert, err := CommonAlertManager.GetAlert(data.AlertId)
	if err != nil {
		return data, httperrors.NewInputParameterError("get resourceRecord err by:%s,err:%v", data.AlertId, err)
	}
	if len(data.ResName) == 0 {
		return data, httperrors.NewInputParameterError("shield res_name is empty")
	}
	if len(data.ResId) == 0 {
		return data, httperrors.NewInputParameterError("shield res_id is empty")
	} else {
		resources, err := MonitorResourceManager.GetMonitorResources(monitor.MonitorResourceListInput{ResId: []string{data.ResId}})
		if err != nil {
			return data, errors.Errorf("getMonitorResource:%s err:%v", data.ResId, err)
		}
		if len(resources) == 0 {
			return data, httperrors.NewInputParameterError("can not get resource by res_id:%s", data.ResId)
		}
		data.ResType = resources[0].ResType
	}
	if len(data.StartTime) == 0 {
		data.StartTime = time.Now().UTC().Format(timeutils.MysqlTimeFormat)
	}
	if len(data.EndTime) == 0 {
		data.EndTime = time.Now().AddDate(DEFAULT_SHEILD_TIME, 0, 0).UTC().Format(timeutils.MysqlTimeFormat)
	}
	startTime, err := timeutils.ParseTimeStr(data.StartTime)
	if err != nil {
		return data, httperrors.NewInputParameterError("parse start_time: %s err", data.StartTime)
	}
	endTime, err := timeutils.ParseTimeStr(data.EndTime)
	if err != nil {
		return data, httperrors.NewInputParameterError("parse end_time: %s err", data.EndTime)
	}

	if endTime.Before(startTime) {
		return data, httperrors.NewInputParameterError("end_time is before start_time")
	}

	hint := fmt.Sprintf("%s-%s", alert.Name, data.ResName)
	name, err := db.GenerateName(ctx, man, ownerId, hint)
	if err != nil {
		return data, errors.Wrap(err, "get GenerateName err")
	}
	data.Name = name
	return data, nil
}

func (shield *SAlertRecordShield) PostCreate(ctx context.Context,
	userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider,
	query jsonutils.JSONObject, data jsonutils.JSONObject) {
	input := new(monitor.AlertRecordShieldCreateInput)
	if err := data.Unmarshal(input); err != nil {
		log.Errorf("post create unmarshal input: %v", err)
		return
	}
	startTime, _ := timeutils.ParseTimeStr(input.StartTime)
	endTime, _ := timeutils.ParseTimeStr(input.EndTime)
	_, err := db.Update(shield, func() error {
		shield.StartTime = startTime
		shield.EndTime = endTime
		return nil
	})
	if err != nil {
		log.Errorf("PostCreate update startTime and endTime err: %v", err)
		return
	}
}

func (manager *SAlertRecordShieldManager) GetRecordShields(input monitor.AlertRecordShieldListInput) (
	[]SAlertRecordShield, error) {
	shields := make([]SAlertRecordShield, 0)
	shieldQuery := manager.Query()
	shieldQuery = manager.shieldListByDetailsFeild(shieldQuery, input)
	err := db.FetchModelObjects(manager, shieldQuery, &shields)
	if err != nil {
		return nil, err
	}
	return shields, nil
}
