package mmHa

import (
	"GoSungrow/iSolarCloud/AppService/getDeviceList"
	"GoSungrow/iSolarCloud/api"
	"GoSungrow/iSolarCloud/api/GoStruct/valueTypes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/MickMake/GoUnify/Only"
	"github.com/MickMake/GoUnify/cmdLog"
	mqtt "github.com/eclipse/paho.mqtt.golang"
	"net/url"
	"strings"
	"time"
)


type Mqtt struct {
	ClientId      string `json:"client_id"`
	Username      string `json:"username"`
	Password      string `json:"password"`
	Host          string `json:"host"`
	Port          string `json:"port"`
	Timeout       time.Duration `json:"timeout"`
	EntityPrefix  string `json:"entity_prefix"`

	url           *url.URL
	client        mqtt.Client
	pubClient     mqtt.Client
	clientOptions *mqtt.ClientOptions

	DeviceName     string
	LastRefresh    time.Time             `json:"-"`
	SungrowDevices getDeviceList.Devices `json:"-"`
	SungrowPsIds   map[valueTypes.PsId]bool
	MqttDevices    map[string]Device
	Prefix         string
	UserOptions    Options

	token    mqtt.Token
	firstRun bool
	err      error
	debug    bool
}


func New(req Mqtt) *Mqtt {
	var ret Mqtt

	for range Only.Once {
		ret.err = ret.setUrl(req)
		if ret.err != nil {
			break
		}
		ret.firstRun = true
		ret.EntityPrefix = req.EntityPrefix
		ret.Prefix = "homeassistant"

		// ret.selectPrefix = fmt.Sprintf("homeassistant/%s/%s", LabelSelect, req.ClientId)
		// ret.servicePrefix = fmt.Sprintf("homeassistant/%s/%s", LabelSensor, req.ClientId)
		// ret.sensorPrefix = fmt.Sprintf("homeassistant/%s/%s", LabelSensor, req.ClientId)
		// ret.lightPrefix = fmt.Sprintf("homeassistant/%s/%s", LabelLight, req.ClientId)
		// ret.switchPrefix = fmt.Sprintf("homeassistant/%s/%s", LabelSwitch, req.ClientId)
		// ret.binarySensorPrefix = fmt.Sprintf("homeassistant/%s/%s", LabelBinarySensor, req.ClientId)

		ret.MqttDevices = make(map[string]Device)
		ret.SungrowPsIds = make(map[valueTypes.PsId]bool)
		ret.Timeout = time.Second * 5
		ret.UserOptions.Map = make(map[string]Option, 0)
	}

	return &ret
}

func (m *Mqtt) IsDebug() bool {
	return m.debug
}

func (m *Mqtt) IsFirstRun() bool {
	return m.firstRun
}

func (m *Mqtt) IsNotFirstRun() bool {
	return !m.firstRun
}

func (m *Mqtt) UnsetFirstRun() {
	m.firstRun = false
}

func (m *Mqtt) GetError() error {
	return m.err
}

func (m *Mqtt) IsError() bool {
	if m.err != nil {
		return true
	}
	return false
}

func (m *Mqtt) IsNewDay() bool {
	var yes bool
	for range Only.Once {
		last := m.LastRefresh.Format("20060102")
		now := time.Now().Format("20060102")

		if last != now {
			yes = true
			break
		}
	}
	return yes
}

func (m *Mqtt) setUrl(req Mqtt) error {

	for range Only.Once {
		// if req.Username == "" {
		// 	m.err = errors.New("username empty")
		// 	break
		// }
		m.Username = req.Username

		// if req.Password == "" {
		// 	m.err = errors.New("password empty")
		// 	break
		// }
		m.Password = req.Password

		if req.Host == "" {
			m.err = errors.New("HASSIO mqtt host not defined")
			break
		}
		m.Host = req.Host

		if req.Port == "" {
			req.Port = "1883"
		}
		m.Port = req.Port

		u := fmt.Sprintf("tcp://%s:%s@%s:%s",
			m.Username,
			m.Password,
			m.Host,
			m.Port,
			)
		m.url, m.err = url.Parse(u)
	}

	return m.err
}

func (m *Mqtt) SetAuth(username string, password string) error {

	for range Only.Once {
		if username == "" {
			m.err = errors.New("username empty")
			break
		}
		m.Username = username

		if password == "" {
			m.err = errors.New("password empty")
			break
		}
		m.Password = password
	}

	return m.err
}

func (m *Mqtt) Connect() error {
	for range Only.Once {
		m.err = m.createClientOptions()
		if m.err != nil {
			break
		}

		m.client = mqtt.NewClient(m.clientOptions)
		token := m.client.Connect()
		for !token.WaitTimeout(3 * time.Second) {
		}
		if m.err = token.Error(); m.err != nil {
			break
		}
		if m.ClientId == "" {
			m.ClientId = "GoSungrow"
		}

		device := Config {
			Entry:      JoinStringsForTopic(m.Prefix, LabelSensor, m.ClientId),	// m.servicePrefix
			Name:       m.ClientId,
			UniqueId:   m.ClientId, 	// + "_Service",
			StateTopic:   "~/state",
			DeviceConfig: DeviceConfig {
				Identifiers:  []string{"GoSungrow"},
				SwVersion:    "GoSungrow https://github.com/MickMake/GoSungrow",
				Name:         m.ClientId + " Service",
				Manufacturer: "MickMake",
				Model:        "SunGrow",
			},
		}

		m.err = m.Publish(JoinStringsForTopic(m.Prefix, LabelSensor, m.ClientId, "config"), 0, true, device.Json())
		if m.err != nil {
			break
		}
		m.err = m.Publish(JoinStringsForTopic(m.Prefix, LabelSensor, m.ClientId, "state"), 0, true, "ON")
		if m.err != nil {
			break
		}

		_, m.err = m.SetDeviceConfig(
			m.DeviceName, m.DeviceName,
			"options", "Options", "", m.DeviceName,
			m.DeviceName,
		)
		if m.err != nil {
			break
		}

		m.err = m.SetOption("mqtt_debug", "Mqtt Debug", m.funcMqttDebug)
		if m.err != nil {
			break
		}

		v := "Disabled"
		if m.debug {
			v = "Enabled"
		}
		m.err = m.SetOptionValue("mqtt_debug", v)
		if m.err != nil {
			break
		}
	}

	return m.err
}

func (m *Mqtt) funcMqttDebug(_ mqtt.Client, msg mqtt.Message) {
	for range Only.Once {
		request := strings.ToLower(string(msg.Payload()))
		cmdLog.LogPrintDate("Option[%s] set to '%s'\n", msg.Topic(), request)
		if request == strings.ToLower(OptionEnabled) {
			m.debug = true
			break
		}
		m.debug = false
	}
}

func (m *Mqtt) Disconnect() error {
	for range Only.Once {
		m.client.Disconnect(250)
		time.Sleep(1 * time.Second)
	}
	return m.err
}

func (m *Mqtt) createClientOptions() error {
	for range Only.Once {
		m.clientOptions = mqtt.NewClientOptions()
		m.clientOptions.AddBroker(fmt.Sprintf("tcp://%s", m.url.Host))
		m.clientOptions.SetUsername(m.url.User.Username())
		password, _ := m.url.User.Password()
		m.clientOptions.SetPassword(password)
		m.clientOptions.SetClientID(m.ClientId)

		m.clientOptions.WillTopic = JoinStringsForTopic(m.Prefix, LabelSensor, m.ClientId, "state")
		m.clientOptions.WillPayload = []byte("OFF")
		m.clientOptions.WillQos = 0
		m.clientOptions.WillRetained = true
		m.clientOptions.WillEnabled = true
	}
	return m.err
}

// type SubscribeFunc func(client mqtt.Client, msg mqtt.Message)
func (m *Mqtt) subscribeDefault(client mqtt.Client, msg mqtt.Message) {
	fmt.Printf("*%t> [%s] %s\n", client.IsConnected(), msg.Topic(), string(msg.Payload()))
}

func (m *Mqtt) Subscribe(topic string, fn mqtt.MessageHandler) error {
	for range Only.Once {
		if fn == nil {
			fn = m.subscribeDefault
		}
		t := m.client.Subscribe(topic, 0, fn)
		if !t.WaitTimeout(m.Timeout) {
			m.err = t.Error()

		}
	}
	return m.err
}

func (m *Mqtt) Publish(topic string, qos byte, retained bool, payload interface{}) error {
	for range Only.Once {
		t := m.client.Publish(topic, qos, retained, payload)
		if !t.WaitTimeout(m.Timeout) {
			m.err = t.Error()
		}
	}
	return m.err
}

// func (m *Mqtt) PublishState(Type string, subtopic string, payload interface{}) error {
// 	for range Only.Once {
// 		// topic = JoinStringsForId(m.EntityPrefix, m.Device.Name, topic)
// 		// topic = JoinStringsForTopic(m.sensorPrefix, topic, "state")
// 		// st := JoinStringsForTopic(m.sensorPrefix, JoinStringsForId(m.EntityPrefix, m.Device.FullName, strings.ReplaceAll(subName, "/", ".")), "state")
// 		topic := ""
// 		switch Type {
// 			case LabelSensor:
// 				topic = JoinStringsForTopic(m.Prefix, LabelSensor, m.ClientId, subtopic, "state")
// 			case LabelBinarySensor:
// 				topic = JoinStringsForTopic(m.Prefix, LabelBinarySensor, m.ClientId, subtopic, "state")
// 			case LabelLight:
// 				topic = JoinStringsForTopic(m.Prefix, LabelLight, m.ClientId, subtopic, "state")
// 			case LabelSwitch:
// 				topic = JoinStringsForTopic(m.Prefix, LabelSwitch, m.ClientId, subtopic, "state")
// 			default:
// 				topic = JoinStringsForTopic(m.Prefix, LabelSensor, m.ClientId, subtopic, "state")
// 		}
//
// 		t := m.client.Publish(topic, 0, true, payload)
// 		if !t.WaitTimeout(m.Timeout) {
// 			m.err = t.Error()
// 		}
// 	}
// 	return m.err
// }

func (m *Mqtt) PublishValue(Type string, subtopic string, value string) error {
	for range Only.Once {
		topic := ""
		switch Type {
			case LabelSensor:
				topic = JoinStringsForTopic(m.Prefix, LabelSensor, m.ClientId, subtopic, "state")
				// state := MqttState {
				// 	LastReset: "", // m.GetLastReset(point.PointId),
				// 	Value:     value,
				// }
				// value = state.Json()

			case "binary_sensor":
				topic = JoinStringsForTopic(m.Prefix, LabelBinarySensor, m.ClientId, subtopic, "state")
				// state := MqttState {
				// 	LastReset: "", // m.GetLastReset(point.PointId),
				// 	Value:     value,
				// }
				// value = state.Json()

			case "lights":
				topic = JoinStringsForTopic(m.Prefix, LabelLight, m.ClientId, subtopic, "state")
				// state := MqttState {
				// 	LastReset: "", // m.GetLastReset(point.PointId),
				// 	Value:     value,
				// }
				// value = state.Json()

			case LabelSwitch:
				topic = JoinStringsForTopic(m.Prefix, LabelSwitch, m.ClientId, subtopic, "state")
				// state := MqttState {
				// 	LastReset: "", // m.GetLastReset(point.PointId),
				// 	Value:     value,
				// }
				// value = state.Json()

			default:
				topic = JoinStringsForTopic(m.Prefix, LabelSensor, m.ClientId, subtopic, "state")
		}

		// t = JoinStringsForId(m.EntityPrefix, m.Device.Name, t)
		// st := JoinStringsForTopic(m.sensorPrefix, JoinStringsForId(m.EntityPrefix, m.Device.FullName, strings.ReplaceAll(subName, "/", ".")), "state")
		// payload := MqttState {
		// 	LastReset: "", // m.GetLastReset(point.PointId),
		// 	Value:     value,
		// }
		// m.client.Publish(JoinStringsForTopic(m.sensorPrefix, t, "state"), 0, true, payload.Json())
		t := m.client.Publish(topic, 0, true, value)
		if !t.WaitTimeout(m.Timeout) {
			m.err = t.Error()
		}
	}

	return m.err
}

func (m *Mqtt) SetDeviceConfig(swname string, parentId string, id string, name string, model string, vendor string, area string) (Device, error) {
	var ret Device

	for range Only.Once {
		// id = JoinStringsForId(m.EntityPrefix, id)

		c := [][]string{
			{swname, JoinStringsForId(m.EntityPrefix, parentId)},
			{JoinStringsForId(m.EntityPrefix, parentId), JoinStringsForId(m.EntityPrefix, id)},
		}
		if swname == parentId {
			c = [][]string{
				{parentId, JoinStringsForId(m.EntityPrefix, id)},
			}
		}

		ret = Device {
			Connections:  c,
			Identifiers:  []string{JoinStringsForId(m.EntityPrefix, id)},
			Manufacturer: vendor,
			Model:        model,
			Name:         name,
			SwVersion:    swname + " https://github.com/MickMake/" + swname,
			ViaDevice:    swname,
			SuggestedArea: area,
		}
		m.MqttDevices[id] = ret
	}

	return ret, m.err
}

// func (m *Mqtt) GetLastReset(pointType string) string {
// 	var ret string
//
// 	for range Only.Once {
// 		pt := api.GetDevicePoint(pointType)
// 		if !pt.Valid {
// 			break
// 		}
// 		if pt.UpdateFreq == "" {
// 			break
// 		}
// 		ret = pt.WhenReset()
// 	}
//
// 	return ret
// }


type MqttState struct {
	LastReset string `json:"last_reset,omitempty"`
	Value string `json:"value"`
}

func (mq *MqttState) Json() string {
	var ret string
	for range Only.Once {
		j, err := json.Marshal(*mq)
		if err != nil {
			ret = fmt.Sprintf("{ \"error\": \"%s\"", err)
			break
		}
		ret = string(j)
	}
	return ret
}

type SensorState string

type EntityConfig struct {
	// Type          string
	Name          string
	SubName       string

	ParentId      string
	ParentName    string

	UniqueId      string
	FullId        string
	Units         string
	ValueName     string
	DeviceClass   string
	StateClass    string
	Icon          string

	Value         *valueTypes.UnitValue
	Point         *api.Point
	ValueTemplate string

	UpdateFreq    string
	LastReset     string
	LastResetValueTemplate string

	IgnoreUpdate  bool

	haType        string
	Options       []string
}

func (config *EntityConfig) FixConfig() {

	for range Only.Once {
		// mdi:power-socket-au
		// mdi:solar-power
		// mdi:home-lightning-bolt-outline
		// mdi:transmission-tower
		// mdi:transmission-tower-export
		// mdi:transmission-tower-import
		// mdi:transmission-tower-off
		// mdi:home-battery-outline
		// mdi:lightning-bolt
		// mdi:check-circle-outline | mdi:arrow-right-bold

		switch config.Units {
			case "Bool":
				fallthrough
			case LabelBinarySensor:
				config.DeviceClass = SetDefault(config.DeviceClass, "power")
				config.Icon = SetDefault(config.Icon, "mdi:check-circle-outline")
				config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value }}")
				// if !config.Value.Valid {
				// 	config.Value = "false"
				// }

			case "MW":
				fallthrough
			case "kW":
				fallthrough
			case "W":
				config.DeviceClass = SetDefault(config.DeviceClass, "power")
				config.Icon = SetDefault(config.Icon, "mdi:lightning-bolt")
				if config.ValueName == "" {
					config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value | float }}")
				} else {
					config.ValueTemplate = SetDefault(config.ValueTemplate, fmt.Sprintf("{{ value_json.%s | float }}", config.ValueName))
				}
				if !config.Value.Valid {
					config.IgnoreUpdate = true
				}

			case "MWh":
				fallthrough
			case "kWh":
				fallthrough
			case "Wh":
				config.DeviceClass = SetDefault(config.DeviceClass, "energy")
				config.Icon = SetDefault(config.Icon, "mdi:lightning-bolt")
				if config.ValueName == "" {
					config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value | float }}")
				} else {
					config.ValueTemplate = SetDefault(config.ValueTemplate, fmt.Sprintf("{{ value_json.%s | float }}", config.ValueName))
				}
				if !config.Value.Valid {
					config.IgnoreUpdate = true
				}

			case "kvar":
				config.DeviceClass = SetDefault(config.DeviceClass, "reactive_power")
				config.Icon = SetDefault(config.Icon, "mdi:lightning-bolt")
				if config.ValueName == "" {
					config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value | float }}")
				} else {
					config.ValueTemplate = SetDefault(config.ValueTemplate, fmt.Sprintf("{{ value_json.%s | float }}", config.ValueName))
				}
				if !config.Value.Valid {
					config.IgnoreUpdate = true
				}

			case "Hz":
				config.DeviceClass = SetDefault(config.DeviceClass, "frequency")
				config.Icon = SetDefault(config.Icon, "mdi:sine-wave")
				if config.ValueName == "" {
					config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value | float }}")
				} else {
					config.ValueTemplate = SetDefault(config.ValueTemplate, fmt.Sprintf("{{ value_json.%s | float }}", config.ValueName))
				}
				if !config.Value.Valid {
					config.IgnoreUpdate = true
				}

			case "V":
				config.DeviceClass = SetDefault(config.DeviceClass, "voltage")
				config.Icon = SetDefault(config.Icon, "mdi:current-dc")
				if config.ValueName == "" {
					config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value | float }}")
				} else {
					config.ValueTemplate = SetDefault(config.ValueTemplate, fmt.Sprintf("{{ value_json.%s | float }}", config.ValueName))
				}
				if !config.Value.Valid {
					config.IgnoreUpdate = true
				}

			case "A":
				config.DeviceClass = SetDefault(config.DeviceClass, "current")
				config.Icon = SetDefault(config.Icon, "mdi:current-ac")
				if config.ValueName == "" {
					config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value | float }}")
				} else {
					config.ValueTemplate = SetDefault(config.ValueTemplate, fmt.Sprintf("{{ value_json.%s | float }}", config.ValueName))
				}
				if !config.Value.Valid {
					config.IgnoreUpdate = true
				}

			case "°F":
				fallthrough
			case "F":
				fallthrough
			case "℉":
				fallthrough
			case "°C":
				fallthrough
			case "C":
				fallthrough
			case "℃":
				config.DeviceClass = SetDefault(config.DeviceClass, "temperature")
				config.Units = "°C"
				config.Icon = SetDefault(config.Icon, "mdi:thermometer")
				if config.ValueName == "" {
					config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value | float }}")
				} else {
					config.ValueTemplate = SetDefault(config.ValueTemplate, fmt.Sprintf("{{ value_json.%s | float }}", config.ValueName))
				}
				if !config.Value.Valid {
					config.IgnoreUpdate = true
				}

			case "%":
				config.DeviceClass = SetDefault(config.DeviceClass, "battery")
				config.Icon = SetDefault(config.Icon, "mdi:home-battery-outline")
				if config.ValueName == "" {
					config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value | float }}")
				} else {
					config.ValueTemplate = SetDefault(config.ValueTemplate, fmt.Sprintf("{{ value_json.%s | float }}", config.ValueName))
				}
				if !config.Value.Valid {
					config.IgnoreUpdate = true
				}

			default:
				config.DeviceClass = SetDefault(config.DeviceClass, "")
				config.Icon = SetDefault(config.Icon, "")
				config.ValueTemplate = SetDefault(config.ValueTemplate, "{{ value_json.value }}")
		}

		if config.LastReset != "" {
			break
		}

		// pt := api.GetDevicePoint(config.FullId)
		// if !pt.Valid {
		// 	break
		// }

		if config.StateClass == "instant" {
			config.StateClass = "measurement"
			break
		}

		if config.StateClass == "" {
			config.StateClass = "measurement"
			break
		}

		// config.LastReset = pt.WhenReset()
		config.LastResetValueTemplate = SetDefault(config.LastResetValueTemplate, "{{ value_json.last_reset | as_datetime() }}")
		// config.LastResetValueTemplate = SetDefault(config.LastResetValueTemplate, "{{ value_json.last_reset | int | timestamp_local | as_datetime }}")

		if config.LastReset == "" {
			config.StateClass = "measurement"
			break
		}
		config.StateClass = "total"
	}
}

func SetDefault(value string, def string) string {
	if value == "" {
		value = def
	}
	return value
}
