package common

import (
	"encoding/json"
	"strings"

	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/go-playground/validator/v10"
	"github.com/pkg/errors"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/bsontype"
	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)

type TRexConfigResponse struct {
	CANBus      CANBus     `json:"canbus"`
	LogLevel    LogLevel   `json:"log_level" validate:"oneof=0 1 2 3 4 5"`
	Log         *LogConfig `json:"log,omitempty" bson:"log,omitempty"`
	DebugMask   string     `json:"debug_mask,omitempty" bson:"debug_mask,omitempty"`
	DLTEnabled  bool       `json:"dlt_enabled,omitempty" bson:"dlt_enabled,omitempty"`
	DLTLevel    int        `json:"dlt_level,omitempty" bson:"dlt_level,omitempty" validate:"oneof=0 1 2 3 4 5 6 255"`
	IDPSEnabled bool       `json:"idps_enabled,omitempty" bson:"idps_enabled,omitempty"`
}

/*
The given values for memory and disk buffer min's and max's
DFT_TMTRC_BUF_SZ    = “16MiB”;
MAX_TMTRC_BUF_SZ    = “1GiB”;
DFT_TMTRC_DISKQ_SZ  = “1GiB”;
MAX_TMTRC_DISKQ_SZ  = “16GiB”;
*/
type CANBus struct {
	Enabled      bool        `json:"enabled" bson:"enabled"`
	DataLogger   bool        `json:"data_logger_enabled" bson:"data_logger"`
	MemBuffSize  int         `json:"max_mem_buffer_size,omitempty" bson:"mem_buff_size" validate:"min=0,max=1073741824"`
	DiskBuffSize int         `json:"max_disk_buffer_size,omitempty" bson:"disk_buff_size" validate:"min=0,max=17179869184"`
	Filters      []CANFilter `json:"filters,omitempty" bson:"filters,omitempty"`
	DTCEnabled   *bool       `json:"dtc_enabled" bson:"dtc_enabled"`
}

type LogLevel int

type LogConfig struct {
	Matches []LogConfigChannel `json:"matches,omitempty" bson:"matches,omitempty"`
}

type LogConfigChannel struct {
	Channel string   `json:"channel,omitempty" bson:"channel,omitempty"`
	Level   LogLevel `json:"level" bson:"level" validate:"oneof=0 1 2 3 4 5"`
}

func CANBusStructLevelValidation(sl validator.StructLevel) {

	canBus := sl.Current().Interface().(CANBus)

	// If we want this canbus to be enabled, then we should enforce the size, otherwise let it be
	if canBus.Enabled {
		if canBus.MemBuffSize < 16777216 {
			sl.ReportError(canBus.MemBuffSize, "max_mem_buffer_size", "MemBuffSize", "canbussizeEnabled", "")
		}
		if canBus.DiskBuffSize < 1073741824 {
			sl.ReportError(canBus.DiskBuffSize, "max_disk_buffer_size", "DiskBuffSize", "canbussizeEnabled", "")
		}
	}
}

const (
	ChannelCMD = "cmd"
)

const (
	Trace LogLevel = iota
	Debug
	Info
	Warn
	Error
	Critical
)

const (
	TraceLabel    = "trace"
	DebugLabel    = "debug"
	InfoLabel     = "info"
	WarnLabel     = "warning"
	ErrorLabel    = "error"
	CriticalLabel = "critical"
)

var logLevels = [...]string{TraceLabel, DebugLabel, InfoLabel, WarnLabel, ErrorLabel, CriticalLabel}

func (l LogLevel) String() string {
	return logLevels[l]
}

// Turn a string into a log level, if we fail, we return trace as default level
func UnmarshalLogLevelString(level string) (l LogLevel) {
	level = strings.ToLower(level)
	switch level {
	case TraceLabel:
		l = Trace
	case DebugLabel:
		l = Debug
	case InfoLabel:
		l = Info
	case WarnLabel:
		l = Warn
	case ErrorLabel:
		l = Error
	case CriticalLabel:
		l = Critical
	default:
		l = Trace
	}
	return l
}

func (l LogLevel) MarshalJSON() ([]byte, error) {
	return json.Marshal(logLevels[l])
}

func (l *LogLevel) UnmarshalJSON(data []byte) error {
	var level string
	err := json.Unmarshal(data, &level)
	if err != nil {
		return errors.WithStack(err)
	}

	switch level {
	case TraceLabel:
		*l = Trace
	case DebugLabel:
		*l = Debug
	case InfoLabel:
		*l = Info
	case WarnLabel:
		*l = Warn
	case ErrorLabel:
		*l = Error
	case CriticalLabel:
		*l = Critical
	default:
		return ErrInvalidLogLevel
	}
	return nil
}

func (l LogLevel) MarshalBSONValue() (bsontype.Type, []byte, error) {
	return bson.MarshalValue(logLevels[l])
}

func (l *LogLevel) UnmarshalBSONValue(t bsontype.Type, value []byte) error {
	if t != bsontype.String {
		return errors.Errorf("invalid bson value type '%s'", t.String())
	}
	level, _, ok := bsoncore.ReadString(value)
	if !ok {
		return errors.New("invalid bson string value")
	}

	switch level {
	case TraceLabel:
		*l = Trace
	case DebugLabel:
		*l = Debug
	case InfoLabel:
		*l = Info
	case ErrorLabel:
		*l = Error
	case WarnLabel:
		*l = Warn
	case CriticalLabel:
		*l = Critical
	default:
		return ErrInvalidLogLevel
	}
	return nil
}

var ErrInvalidLogLevel = errors.New("invalid log level")

// Converts trex log level to the logger level, so its easy to log
// WithLevel() uses a different iota order compared to our LogLevel
func (l LogLevel) ToLoggerLevel() func() *logger.Event {
	switch l {
	case Trace:
		return logger.Trace
	case Debug:
		return logger.Debug
	case Info:
		return logger.Info
	case Warn:
		return logger.Warn
	case Error:
		return logger.Error
	case Critical:
		return logger.Error
	default:
		return logger.Warn
	}
}
