// Package repository add by blacknc <blacknc@163.com> at 2023/02/20 19:38:36
package repository

import (
	"context"
	"dataservices/commons/errs"
	"dataservices/commons/utils"
	"dataservices/internal/entity"
	"fmt"
	"time"

	jsoniter "github.com/json-iterator/go"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"gorm.io/gorm/clause"
)

func CreateDataApp(ctx context.Context, dataApp *entity.DataApp) (*entity.DataApp, error) {
	if err := MustFromContext(ctx).Table(entity.TableDataApps).Create(dataApp).Error; err != nil {
		return nil, errs.WrapGORMError(err, "Failed to create dataapp")
	}
	return dataApp, nil
}

func UpdateDataApp(ctx context.Context, dataApp *entity.DataApp) error {
	tx := MustFromContext(ctx).Table(entity.TableDataApps).Where("id=? and is_deleted=0", dataApp.ID).Updates(&dataApp)
	if err := tx.Error; err != nil {
		return errs.WrapGORMError(err, "Failed to update dataapp: %d", dataApp.ID)
	}
	return nil
}

func UpdateDataAppStatus(ctx context.Context, dataappID uint64, status string) error {
	tx := MustFromContext(ctx).Table(entity.TableDataApps).Where("id=? and is_deleted=0", dataappID).Update("status", status)
	if err := tx.Error; err != nil {
		return errs.WrapGORMError(err, "Failed to update dataapp status: %d", dataappID)
	} else if tx.RowsAffected == 0 {
		return errs.NewBadRequest(fmt.Sprintf("Failed to update dataapp status: %d, update nothing", dataappID), nil)
	}
	return nil
}

func PageDataApps(ctx context.Context, appIDs []uint64, offset, limit int) ([]*entity.DataApp, error) {
	var res []*entity.DataApp
	txDB := MustFromContext(ctx).Table(entity.TableDataApps).Where("is_deleted=0").Order("created_at DESC")
	if len(appIDs) > 0 {
		txDB.Clauses(clause.IN{Column: "id", Values: utils.ToGeneralArr(appIDs...)})
	}
	if err := txDB.Offset(offset).Limit(limit).Find(&res).Error; err != nil {
		return nil, errs.WrapGORMError(err, "Failed to list dataapp")
	}
	for _, r := range res {
		if r == nil {
			continue
		}
		if err := r.FillSettings(); err != nil {
			tilog.Error(ctx, "PageDataApps FillSettings failed", zap.Uint64("app", r.ID), zap.Error(err))
		}
	}
	return res, nil
}

func ListAllDataApps(ctx context.Context, appIDs []uint64, appTypes ...string) ([]*entity.DataApp, error) {
	var res []*entity.DataApp
	txDB := MustFromContext(ctx).Table(entity.TableDataApps).Where("is_deleted=0").Order("created_at DESC")
	if len(appIDs) > 0 {
		txDB.Clauses(clause.IN{Column: "id", Values: utils.ToGeneralArr(appIDs...)})
	}
	if len(appTypes) > 0 {
		txDB.Clauses(clause.IN{Column: "app_type", Values: utils.ToGeneralArr(appTypes...)})
	}
	if err := txDB.Find(&res).Error; err != nil {
		return nil, errs.WrapGORMError(err, "Failed to list dataapp")
	}
	for _, r := range res {
		if r == nil {
			continue
		}
		if err := r.FillSettings(); err != nil {
			tilog.Error(ctx, "ListAllDataApps FillSettings failed", zap.Uint64("app", r.ID), zap.Error(err))
		}
	}
	return res, nil
}

func ListDataAppsWithLock(ctx context.Context, projectId uint64, appTypes ...string) ([]*entity.DataApp, error) {
	var res []*entity.DataApp
	txDB := MustFromContext(ctx).Table(entity.TableDataApps).Set("gorm:query_option", "FOR UPDATE").Where("project_id=? and is_deleted=0", projectId).Order("created_at DESC")
	if len(appTypes) > 0 {
		txDB.Clauses(clause.IN{Column: "app_type", Values: utils.ToGeneralArr(appTypes...)})
	}
	if err := txDB.Find(&res).Error; err != nil {
		return nil, errs.WrapGORMError(err, "Failed to list dataapp")
	}
	for _, r := range res {
		if r == nil {
			continue
		}
		if err := r.FillSettings(); err != nil {
			tilog.Error(ctx, "ListDataAppsWithLock FillSettings failed", zap.Uint64("app", r.ID), zap.Error(err))
		}
	}
	return res, nil
}

func ListDataApps(ctx context.Context, projectId uint64, appTypes ...string) ([]*entity.DataApp, error) {
	var res []*entity.DataApp
	txDB := MustFromContext(ctx).Table(entity.TableDataApps).Where("project_id=? and is_deleted=0", projectId).Order("created_at DESC")
	if len(appTypes) > 0 {
		txDB.Clauses(clause.IN{Column: "app_type", Values: utils.ToGeneralArr(appTypes...)})
	}
	if err := txDB.Find(&res).Error; err != nil {
		return nil, errs.WrapGORMError(err, "Failed to list dataapp")
	}
	for _, r := range res {
		if r == nil {
			continue
		}
		if err := r.FillSettings(); err != nil {
			tilog.Error(ctx, "ListDataApps FillSettings failed", zap.Uint64("app", r.ID), zap.Error(err))
		}
	}

	return res, nil
}

// XXX(bing.wang) This interface will also retrieve deleted data applications and is intended solely for internal analysis purposes.
func GetDataAppRaw(ctx context.Context, appId uint64) (*entity.DataApp, error) {
	res := entity.DataApp{}
	err := MustFromContext(ctx).Table(entity.TableDataApps).Where("id=?", appId).First(&res).Error
	if err != nil {
		return nil, errs.WrapGORMError(err, "Failed to get dataapp: %d", appId)
	}
	if err := res.FillSettings(); err != nil {
		tilog.Error(ctx, "GetDataAppRaw FillSettings failed", zap.Uint64("app", appId), zap.Error(err))
	}
	return &res, nil
}

func GetDataApp(appId uint64) (*entity.DataApp, error) {
	return GetDataAppWithCtx(context.TODO(), appId)
}

func GetDataAppWithCtx(ctx context.Context, appId uint64) (*entity.DataApp, error) {
	res := entity.DataApp{}
	err := MustFromContext(ctx).Table(entity.TableDataApps).Where("id=? and is_deleted=0", appId).First(&res).Error
	if err != nil {
		return nil, errs.WrapGORMError(err, "Failed to get dataapp: %d", appId)
	}
	if err := res.FillSettings(); err != nil {
		tilog.Error(ctx, "GetDataAppWithCtx FillSettings failed", zap.Uint64("app", appId), zap.Error(err))
	}
	return &res, nil
}

func GetDataAppWithLock(ctx context.Context, appId uint64) (*entity.DataApp, error) {
	var res entity.DataApp
	err := MustFromContext(ctx).Table(entity.TableDataApps).Set("gorm:query_option", "FOR UPDATE").Where("id=? and is_deleted=0", appId).First(&res).Error
	if err != nil {
		return nil, errs.WrapGORMError(err, "Failed to get dataapp with lock: %d", appId)
	}
	if err := res.FillSettings(); err != nil {
		tilog.Error(ctx, "GetDataAppWithLock FillSettings failed", zap.Uint64("app", appId), zap.Error(err))
	}
	return &res, nil
}

func UpdateDataAppSettings(ctx context.Context, dataApp *entity.DataApp) error {
	dataApp.SettingsJson, _ = jsoniter.MarshalToString(dataApp.Settings)
	return MustFromContext(ctx).Table(entity.TableDataApps).Where("id=?", dataApp.ID).UpdateColumn("settings", dataApp.SettingsJson).Error
}

func GetDataAppByExtId(ctx context.Context, appExtId string) (*entity.DataApp, error) {
	res := entity.DataApp{}
	err := MustFromContext(ctx).Table(entity.TableDataApps).Where("external_app_id=? and is_deleted=0", appExtId).First(&res).Error
	if err != nil {
		return nil, errs.WrapGORMError(err, "Failed to get dataapp: %s", appExtId)
	}
	if err := res.FillSettings(); err != nil {
		tilog.Error(ctx, "GetDataAppByExtId FillSettings failed", zap.Uint64("app", res.ID), zap.Error(err))
	}
	return &res, nil
}

func DeleteDataAppWithTX(ctx context.Context, appId uint64) error {
	return Transaction(ctx, func(ctx context.Context) error {
		return DeleteDataApp(ctx, appId)
	})
}

func DeleteDataApp(ctx context.Context, appId uint64) error {
	db := MustFromContext(ctx)
	if err := db.Table(entity.TableDataApps).Where("id=?", appId).Update("is_deleted", 1).Error; err != nil {
		return errs.WrapGORMError(err, "Failed to delete dataapp: %d", appId)
	}
	if err := db.Table(entity.TableEndpoints).Where("dataapp_id=? and is_deleted=0", appId).Update("is_deleted", 1).Error; err != nil {
		return errs.WrapGORMError(err, "Failed to delete dataapp's endpoint: %d", appId)
	}
	if err := db.Table(entity.TableEndpointPublish).Where("dataapp_id=?", appId).Delete(&entity.EndpointPublish{}).Error; err != nil {
		return errs.WrapGORMError(err, "Failed to delete dataapp's deployed endpoint: %d", appId)
	}
	if err := db.Table(entity.TableDataAppClusters).Where("dataapp_id=? and is_deleted=0", appId).Update("is_deleted", 1).Error; err != nil {
		return errs.WrapGORMError(err, "Failed to delete dataapp's cluster: %d", appId)
	}
	return nil
}

func CountCreateDataAppUsers(ctx context.Context, startSecAt int64, endSecAt int64, limit int) ([]entity.AnalysisStrKVResult, error) {
	startTimeObj := time.Unix(startSecAt, 0)
	endTimeObj := time.Unix(endSecAt, 0)

	var results []entity.AnalysisStrKVResult
	err := MustFromContext(ctx).Table(entity.TableDataApps).
		Select("created_by as `key`, COUNT(1) as value").
		Where("created_at between ? and ?", startTimeObj, endTimeObj).
		Group("created_by").
		Limit(limit).
		Scan(&results).Error
	if err != nil {
		return results, errs.WrapGORMError(err, "failed to CountDataAppUsers")
	}
	if results != nil {
		tilog.Info(ctx, "CountDataAppUsers", zap.Any("startTimeObj", startTimeObj),
			zap.Any("endTimeObj", endTimeObj), zap.Any("results", results))
	}
	return results, nil
}

func ListDataAppsByTimeRange(ctx context.Context, startSecAt int64, endSecAt int64) ([]*entity.DataApp, error) {
	startTimeObj := time.Unix(startSecAt, 0)
	endTimeObj := time.Unix(endSecAt, 0)
	var res []*entity.DataApp
	txDB := MustFromContext(ctx).Table(entity.TableDataApps).Where("created_at between ? and ?", startTimeObj, endTimeObj)
	if err := txDB.Find(&res).Error; err != nil {
		return nil, errs.WrapGORMError(err, "Failed to list dataapp")
	}
	for _, r := range res {
		if r == nil {
			continue
		}
		if err := r.FillSettings(); err != nil {
			tilog.Error(ctx, "ListDataAppsByTimeRange FillSettings failed", zap.Uint64("app", r.ID), zap.Error(err))
		}
	}
	return res, nil
}
