/********************************************************************************
 *  Copyright 2020 Dell Inc.
 *
 * 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 mqtt

import (
	"fmt"
	"math/rand"
	"net/url"
	"strconv"
	"time"

	"github.com/winc-link/hummingbird/internal/pkg/messaging/mqclient"
	"github.com/winc-link/hummingbird/internal/pkg/messaging/types"
)

// MQTTClientConfig contains all the configurations for the MQTT client.
type MQTTClientConfig struct {
	BrokerURL string
	MQTTClientOptions
}

// ConnectionOptions contains the connection configurations for the MQTT client.
//
// NOTE: The connection properties resides in its own struct in order to avoid the property being loaded in via
//  reflection during the load process.
type ConnectionOptions struct {
	BrokerURL string
}

// MQTTClientOptions contains the client options which are loaded via reflection
type MQTTClientOptions struct {
	// Client Identifiers
	Username string
	Password string
	ClientId string
	// Connection information
	Qos            int
	KeepAlive      int // Seconds
	Retained       bool
	AutoReconnect  bool
	ConnectTimeout int // Seconds
	mqclient.TlsConfigurationOptions
}

// CreateMQTTClientConfiguration constructs a MQTTClientConfig based on the provided MessageBusConfig.
func CreateMQTTClientConfiguration(messageBusConfig types.MessageBusConfig) (MQTTClientConfig, error) {
	var brokerUrl string
	if !messageBusConfig.PublishHost.IsHostInfoEmpty() {
		brokerUrl = messageBusConfig.PublishHost.GetHostURL()
	} else if !messageBusConfig.SubscribeHost.IsHostInfoEmpty() {
		brokerUrl = messageBusConfig.SubscribeHost.GetHostURL()
	} else {
		return MQTTClientConfig{}, fmt.Errorf("Specified empty broker info.")
	}

	_, err := url.Parse(brokerUrl)
	if err != nil {
		return MQTTClientConfig{}, mqclient.NewBrokerURLErr(fmt.Sprintf("Failed to parse broker: %v", err))
	}

	mqttClientOptions := CreateMQTTClientOptionsWithDefaults()
	err = mqclient.Load(messageBusConfig.Optional, &mqttClientOptions)
	if err != nil {
		return MQTTClientConfig{}, err
	}

	tlsConfig := mqclient.TlsConfigurationOptions{}
	err = mqclient.Load(messageBusConfig.Optional, &tlsConfig)
	if err != nil {
		return MQTTClientConfig{}, err
	}

	mqttClientOptions.TlsConfigurationOptions = tlsConfig

	return MQTTClientConfig{
		BrokerURL:         brokerUrl,
		MQTTClientOptions: mqttClientOptions,
	}, nil
}

// CreateMQTTClientOptionsWithDefaults constructs MQTTClientOptions instance with defaults.
func CreateMQTTClientOptionsWithDefaults() MQTTClientOptions {
	randomClientId := strconv.Itoa(rand.New(rand.NewSource(time.Now().UnixNano())).Intn(100000))
	return MQTTClientOptions{
		Username: "",
		Password: "",
		// Client ID is required or else can cause unexpected errors. This was observed with Eclipse's Mosquito MQTT server.
		ClientId:                randomClientId,
		Qos:                     0,
		KeepAlive:               0,
		Retained:                false,
		ConnectTimeout:          5, // 5 seconds
		AutoReconnect:           false,
		TlsConfigurationOptions: mqclient.CreateDefaultTlsConfigurationOptions(),
	}
}
