package vo

import (
	"strings"
	"time"

	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/console"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/v1beta1"
	"github.com/tidbcloud/serverless-service/internal/domain/entity"
	"google.golang.org/protobuf/types/known/timestamppb"
)

const SecretData = ""

type AuditLogFile struct {
	Name      string     `json:"name,omitempty"`
	SizeBytes uint32     `json:"size_bytes,omitempty"`
	CreatedAt *time.Time `json:"created_at,omitempty"`
	Url       string     `json:"url,omitempty"`
}

func (a *AuditLogFile) ConvertToConsole() *console.AuditLogFile {
	file := &console.AuditLogFile{
		Name:      a.Name,
		SizeBytes: a.SizeBytes,
		Url:       a.Url,
	}
	if a.CreatedAt != nil {
		file.CreateTime = timestamppb.New(*a.CreatedAt)
	}
	return file
}

func (a *AuditLogFile) ConvertToV1beta1() *v1beta1.AuditLogFile {
	file := &v1beta1.AuditLogFile{
		Name:      a.Name,
		SizeBytes: a.SizeBytes,
		Url:       a.Url,
	}
	if a.CreatedAt != nil {
		file.CreateTime = timestamppb.New(*a.CreatedAt)
	}
	return file
}

type ListFilterRuleOpts struct {
	ClusterID string
	Limit     int32
	Offset    int64
}

func FilterRuleToConsole(filterRule *entity.AuditLogFilterRule) *console.AuditLogFilterRule {
	if filterRule == nil {
		return nil
	}
	consoleFilterRule := &console.AuditLogFilterRule{
		FilterRuleId: filterRule.ID,
		DisplayName:  filterRule.Name,
		ClusterId:    filterRule.ClusterID,
		Users:        filterRule.Users,
		Disabled:     *filterRule.Disabled,
		CreateTime:   timestamppb.New(filterRule.CreatedAt),
	}
	if filterRule.UpdatedAt != nil {
		consoleFilterRule.UpdateTime = timestamppb.New(*filterRule.UpdatedAt)
	}
	if filterRule.Filters != nil {
		consoleFilterRule.Filters = make([]*console.AuditLogFilter, len(filterRule.Filters))
		for i, f := range filterRule.Filters {
			consoleFilterRule.Filters[i] = &console.AuditLogFilter{
				Classes:     f.Classes,
				Tables:      f.Tables,
				StatusCodes: f.StatusCodes,
			}
		}
	}
	return consoleFilterRule
}

func FilterRuleToV1beta1(filterRule *entity.AuditLogFilterRule) *v1beta1.AuditLogFilterRule {
	if filterRule == nil {
		return nil
	}
	v1beta1FilterRule := &v1beta1.AuditLogFilterRule{
		FilterRuleId: filterRule.ID,
		DisplayName:  filterRule.Name,
		ClusterId:    filterRule.ClusterID,
		Users:        filterRule.Users,
		Disabled:     *filterRule.Disabled,
		CreateTime:   timestamppb.New(filterRule.CreatedAt),
	}
	if filterRule.UpdatedAt != nil {
		v1beta1FilterRule.UpdateTime = timestamppb.New(*filterRule.UpdatedAt)
	}
	if filterRule.Filters != nil {
		v1beta1FilterRule.Filters = make([]*v1beta1.AuditLogFilter, len(filterRule.Filters))
		for i, f := range filterRule.Filters {
			v1beta1FilterRule.Filters[i] = &v1beta1.AuditLogFilter{
				Classes:     f.Classes,
				Tables:      f.Tables,
				StatusCodes: f.StatusCodes,
			}
		}
	}
	return v1beta1FilterRule
}

func ConsoleCreateToFilterRule(
	consoleFilterRule *console.AuditLogFilterRule,
) (*entity.AuditLogFilterRule, bizerr.BizError) {
	if consoleFilterRule == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty filter rule")
	}
	if len(consoleFilterRule.Users) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty users")
	}
	if consoleFilterRule.DisplayName == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty filter rule name")
	}
	if len(consoleFilterRule.Filters) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty filters")
	}

	filterRule := &entity.AuditLogFilterRule{
		Name:  consoleFilterRule.DisplayName,
		Users: consoleFilterRule.Users,
	}
	filters := make([]*entity.AuditLogFilter, 0)
	for _, f := range consoleFilterRule.Filters {
		filter := &entity.AuditLogFilter{
			Classes:     f.Classes,
			Tables:      f.Tables,
			StatusCodes: f.StatusCodes,
		}
		for i := range filter.Classes {
			filter.Classes[i] = strings.ToUpper(filter.Classes[i])
		}
		filters = append(filters, filter)
	}
	filterRule.Filters = filters

	return filterRule, nil
}

func ConsoleUpdateToFilterRule(
	request *console.UpdateAuditLogFilterRuleRequest,
) (*entity.AuditLogFilterRule, bizerr.BizError) {
	if request.FilterRuleId == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty filter rule id")
	}

	filterRule := &entity.AuditLogFilterRule{
		ID:        request.FilterRuleId,
		ClusterID: request.ClusterId,
		Name:      request.DisplayName,
		Disabled:  request.Disabled,
	}

	if len(request.Users) > 0 {
		filterRule.Users = request.Users
	}
	// import: do not remove the judge of nil
	// when filter is nil, it means not update filter
	// when filter is not nil but is empty, it means filter all audit logs
	if request.Filters != nil {
		filters := make([]*entity.AuditLogFilter, 0)
		for _, f := range request.Filters {
			filter := &entity.AuditLogFilter{
				Classes:     f.Classes,
				Tables:      f.Tables,
				StatusCodes: f.StatusCodes,
			}
			for i := range filter.Classes {
				filter.Classes[i] = strings.ToUpper(filter.Classes[i])
			}
			filters = append(filters, filter)
		}
		filterRule.Filters = filters
	}
	return filterRule, nil
}

func V1beta1CreateToFilterRule(
	v1beta1FilterRule *v1beta1.AuditLogFilterRule,
) (*entity.AuditLogFilterRule, bizerr.BizError) {
	if v1beta1FilterRule == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty filter rule")
	}
	if len(v1beta1FilterRule.Users) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty users")
	}
	if v1beta1FilterRule.DisplayName == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty filter rule name")
	}
	if len(v1beta1FilterRule.Filters) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty filters")
	}

	filterRule := &entity.AuditLogFilterRule{
		Name:  v1beta1FilterRule.DisplayName,
		Users: v1beta1FilterRule.Users,
	}
	filters := make([]*entity.AuditLogFilter, 0)
	for _, f := range v1beta1FilterRule.Filters {
		filter := &entity.AuditLogFilter{
			Classes:     f.Classes,
			Tables:      f.Tables,
			StatusCodes: f.StatusCodes,
		}
		for i := range filter.Classes {
			filter.Classes[i] = strings.ToUpper(filter.Classes[i])
		}
		filters = append(filters, filter)
	}
	filterRule.Filters = filters

	return filterRule, nil
}

func V1beta1UpdateToFilterRule(
	request *v1beta1.UpdateAuditLogFilterRuleRequest,
) (*entity.AuditLogFilterRule, bizerr.BizError) {
	if request.FilterRuleId == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty filter rule id")
	}

	filterRule := &entity.AuditLogFilterRule{
		ID:        request.FilterRuleId,
		ClusterID: request.ClusterId,
		Name:      request.DisplayName,
		Disabled:  request.Disabled,
	}

	if len(request.Users) > 0 {
		filterRule.Users = request.Users
	}
	// import: do not remove the judge of nil
	// when filter is nil, it means not update filter
	// when filter is not nil but is empty, it means filter all audit logs
	if request.Filters != nil {
		filters := make([]*entity.AuditLogFilter, 0)
		for _, f := range request.Filters {
			filter := &entity.AuditLogFilter{
				Classes:     f.Classes,
				Tables:      f.Tables,
				StatusCodes: f.StatusCodes,
			}
			for i := range filter.Classes {
				filter.Classes[i] = strings.ToUpper(filter.Classes[i])
			}
			filters = append(filters, filter)
		}
		filterRule.Filters = filters
	}
	return filterRule, nil
}

func AuditLogConfigToConsole(config *entity.AuditLogConfig) *console.AuditLogConfig {
	if config == nil {
		return &console.AuditLogConfig{}
	}
	rotationIntervalSeconds := config.RotationIntervalSeconds
	if rotationIntervalSeconds == 0 {
		rotationIntervalSeconds = entity.DefaultAuditlogRotationIntervalSeconds
	}
	rotationSizeBytes := config.RotationSizeBytes
	if rotationSizeBytes == 0 {
		rotationSizeBytes = entity.DefaultAuditlogRotationSizeBytes
	}
	return &console.AuditLogConfig{
		ClusterId:               config.ClusterID,
		Enabled:                 config.Enabled,
		Unredacted:              config.Unredacted,
		CloudStorage:            CloudStorageToCommon(config.CloudStorageType, config.CloudStorage),
		RotationIntervalMinutes: int32(rotationIntervalSeconds / 60),
		RotationSizeMib:         int32(rotationSizeBytes / (1024 * 1024)),
	}
}

func ConsoleUpdateToAuditLogConfig(
	clusterId string,
	config *console.AuditLogConfig,
) (*entity.AuditLogConfig, bizerr.BizError) {
	if config == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty audit log config")
	}
	entityConfig := &entity.AuditLogConfig{
		ClusterID:               clusterId,
		Enabled:                 config.Enabled,
		Unredacted:              config.Unredacted,
		RotationIntervalSeconds: int64(config.RotationIntervalMinutes) * 60,
		RotationSizeBytes:       int64(config.RotationSizeMib) * 1024 * 1024,
	}
	cloudStorage, cloudStorageType, err := CommonToCloudStorage(config.CloudStorage, clusterId)
	if err != nil {
		return nil, err
	}
	entityConfig.CloudStorage = cloudStorage
	entityConfig.CloudStorageType = cloudStorageType
	return entityConfig, nil
}

func AuditLogConfigToV1beta1(config *entity.AuditLogConfig) *v1beta1.AuditLogConfig {
	if config == nil {
		return nil
	}
	rotationIntervalSeconds := config.RotationIntervalSeconds
	if rotationIntervalSeconds == 0 {
		rotationIntervalSeconds = entity.DefaultAuditlogRotationIntervalSeconds
	}
	rotationSizeBytes := config.RotationSizeBytes
	if rotationSizeBytes == 0 {
		rotationSizeBytes = entity.DefaultAuditlogRotationSizeBytes
	}
	return &v1beta1.AuditLogConfig{
		ClusterId:               config.ClusterID,
		Enabled:                 config.Enabled,
		Unredacted:              config.Unredacted,
		CloudStorage:            CloudStorageToCommon(config.CloudStorageType, config.CloudStorage),
		RotationIntervalMinutes: int32(rotationIntervalSeconds / 60),
		RotationSizeMib:         int32(rotationSizeBytes / (1024 * 1024)),
	}
}

func V1beta1UpdateToAuditLogConfig(
	clusterId string,
	config *v1beta1.AuditLogConfig,
) (*entity.AuditLogConfig, bizerr.BizError) {
	if config == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty audit log config")
	}
	entityConfig := &entity.AuditLogConfig{
		ClusterID:               clusterId,
		Enabled:                 config.Enabled,
		Unredacted:              config.Unredacted,
		RotationIntervalSeconds: int64(config.RotationIntervalMinutes) * 60,
		RotationSizeBytes:       int64(config.RotationSizeMib) * 1024 * 1024,
	}
	cloudStorage, cloudStorageType, err := CommonToCloudStorage(config.CloudStorage, clusterId)
	if err != nil {
		return nil, err
	}
	entityConfig.CloudStorage = cloudStorage
	entityConfig.CloudStorageType = cloudStorageType
	return entityConfig, nil
}
