package main

import (
	"bufio"
	"bytes"
	"fmt"
	"github.com/gomodule/redigo/redis"
	"github.com/jordan-wright/email"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"
)

func sendEmailAccu(username string, alias string, deveui string, accu_percent string, threshold int, level string) {
	fmt.Printf("SEND EMAIL ACCU (%s) - %s:%s\n", level, username, deveui)
	mail_message := `Lieber Benutzer von wo-bisch.ch

Der Akku von "` + alias + `" (DevEUI: ` + deveui + `) ist noch zu ` + accu_percent + ` Prozent geladen.

Bitte rechtzeitig wieder laden! Bei 10%-er Ladung erscheint die letzte Warnung.

Mit freundlichen Grüssen
--
wo-bisch.ch`

	e := email.NewEmail()
	e.From = "wo-bisch.ch <info@wo-bisch.ch>"
	e.To = []string{username}
	e.Bcc = []string{"joerg.lehmann@nbit.ch"}
	e.Subject = level + " - wo-bisch.ch: Akku Ladezustand (" + alias + ")"
	e.Text = []byte(mail_message)
	e.Send("127.0.0.1:25", nil)
}

func sendEmailAbo(username string, alias string, deveui string, days_left int, level string) {
	fmt.Printf("SEND EMAIL ABO (%s) - %s:%s\n", level, username, deveui)
	mail_message := `Lieber Benutzer von wo-bisch.ch

Das Abo von "` + alias + `" (DevEUI: ` + deveui + `) laeuft in ` + strconv.Itoa(days_left) + ` Tagen ab.

Bitte Abo verlaengern auf https://wo-bisch.ch

Mit freundlichen Grüssen
--
wo-bisch.ch`

	e := email.NewEmail()
	e.From = "wo-bisch.ch <info@wo-bisch.ch>"
	e.To = []string{username}
	e.Bcc = []string{"joerg.lehmann@nbit.ch"}
	e.Subject = level + " - wo-bisch.ch: Abo laeuft ab (" + alias + ")"
	e.Text = []byte(mail_message)
	e.Send("127.0.0.1:25", nil)
}

var globalPool *redis.Pool

const userPrefix string = "user:"
const devPrefix string = "dev:"
const confirmPrefix string = "confirm:"

func newPool() *redis.Pool {
	return &redis.Pool{
		// Maximum number of idle connections in the pool.
		MaxIdle: 80,
		// max number of connections
		MaxActive: 12000,
		// Dial is an application supplied function for creating and
		// configuring a connection.
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", ":6379")
			if err != nil {
				panic(err.Error())
			}
			return c, err
		},
	}
}

// ping tests connectivity for redis (PONG should be returned)
func ping(c redis.Conn) error {
	// Send PING command to Redis
	// PING command returns a Redis "Simple String"
	// Use redis.String to convert the interface type to string
	s, err := redis.String(c.Do("PING"))
	if err != nil {
		return err
	}

	logit("PING Response = " + s)
	// Output: PONG

	return nil
}

type Dev struct {
	Deveui      string
	Alias       string
	Alarmactive string
	Smsnumber   string
	ActiveUntil string // Abo bezahlt bis TT.MM.YYYY
}

func initDB() {

	// newPool returns a pointer to a redis.Pool
	pool := newPool()
	// get a connection from the globalPool (redis.Conn)
	conn := pool.Get()
	defer conn.Close()

	globalPool = pool

	// wir machen einen Connection Test
	ping(conn)
}

func closeDB() {
	globalPool.Close()
}

func getUsers() []string {
	res := []string{}

	conn := globalPool.Get()
	defer conn.Close()

	//logit("getUsers")
	users, err := redis.Strings(conn.Do("KEYS", userPrefix+"*"))
	if err == nil {
		//logit("getUsers successful!")
		res = users
	} else {
		log.Print(err)
	}

	return res
}

func getMyDevs(username string) []string {
	res := []string{}

	if username == "" {
		return res
	}

	conn := globalPool.Get()
	defer conn.Close()

	//logit("getMyDevs: User: " + username)
	mydevs, err := redis.String(conn.Do("HGET", userPrefix+username, "my_devs"))
	if err == nil {
		//logit("getMyDevs: mydevs: " + mydevs)
		res = strings.Split(mydevs, ",")
	} else {
		log.Print(err)
	}

	return res
}

func getDevAlias(deveui string) string {
	res := deveui

	if deveui == "" {
		return res
	}

	conn := globalPool.Get()
	defer conn.Close()

	//logit("getDevAlias: Deveui: " + deveui)
	alias, err := redis.String(conn.Do("HGET", devPrefix+deveui, "alias"))
	if err == nil {
		//logit("getDevAlias: alias: " + alias)
		res = alias
	} else {
		log.Print(err)
	}

	return res
}

func getActiveUntil(deveui string) string {
	res := ""

	if deveui == "" {
		return res
	}

	conn := globalPool.Get()
	defer conn.Close()

	//logit("getActiveUntil: Deveui: " + deveui)
	activeuntil, err := redis.String(conn.Do("HGET", devPrefix+deveui, "active_until"))
	if err == nil {
		//logit("getActiveUntil: activeuntil: " + activeuntil)
		res = activeuntil
	} else {
		log.Print(err)
	}

	return res
}

func InsertAlert(prefix string, deveui string, email string, threshold int) {
	conn := globalPool.Get()
	defer conn.Close()

	_, err := conn.Do("SET", prefix+deveui+":"+email, threshold)
	if err != nil {
		logit("InsertAlert: Error inserting: " + prefix + deveui + ":"  + email)
	}
}

func DeleteAlert(prefix string, deveui string, email string) {
	conn := globalPool.Get()
	defer conn.Close()

	exists, _ := redis.Bool(conn.Do("EXISTS", prefix+deveui+":"+email))

	if exists {
		_, err := conn.Do("DEL", prefix+deveui+":"+email)
		if err != nil {
			logit("DeleteAlert: Error deleting: " + prefix + deveui + ":" + email)
		}
	}
}

func AlarmNotAlreadySent(prefix string, deveui string, email string, threshold int) bool {
	conn := globalPool.Get()
	defer conn.Close()

	exists, _ := redis.Bool(conn.Do("EXISTS", prefix+deveui+":"+email))
	if !exists {
		return true
	}
	alarm_threshold, _ := redis.Int(conn.Do("GET", prefix+deveui+":"+email))
	return threshold != alarm_threshold
}

type OneMetric struct {
	Deveui               string
	Alias                string
	Timestamp            string
	BatteryPercent       string
	ActiveUntil          string
	DaysUntilDeactivated int // berechneter Wert
}

func CalcDaysUntil(mydate string) int {
	var days int
	layout := "02.01.2006"
	t, err := time.Parse(layout, mydate)

	if err != nil {
		days = 0
	}
	days = int(t.Sub(time.Now()).Hours() / 24)

	return days
}

func getLastMetrics(deveui string) OneMetric {
	var res OneMetric

	url := "http://localhost:8086/api/v2/query?org=wobischorg"
	data := []byte(fmt.Sprintf(`from(bucket:"wobischbucket") 
                                 |> range(start:-5d) 
                                 |> filter(fn: (r) => r._measurement == "measurement" and r.deveui == "%s")
                                 |> filter(fn: (r) => r._field == "vp") 
                                 |> last() |> yield(name: "last")`, deveui))

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
	if err != nil {
		log.Fatal("Error reading request. ", err)
	}

	// Set headers
	req.Header.Set("Authorization", "Token nKYCoz3TA-LItYXG988DjdiStMhrfKmFXQqzxrjzJJ7Ek_iUttzFSE9lfe3s6q99EMdcrjuGlDAjp4Y0VnNRXw==")
	req.Header.Set("accept", "application/csv")
	req.Header.Set("content-type", "application/vnd.flux")

	// Set client timeout
	client := &http.Client{Timeout: time.Second * 10}

	// Send request
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal("Error reading response. ", err)
	}
	defer resp.Body.Close()

	//fmt.Println("response Status:", resp.Status)
	//fmt.Println("response Headers:", resp.Header)

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal("Error reading body. ", err)
	}
	//fmt.Println("response Body:", string(body))

	scanner := bufio.NewScanner(strings.NewReader(string(body)))
	location, err := time.LoadLocation("Europe/Zurich")
	for scanner.Scan() {
		s := strings.Split(scanner.Text(), ",")
		if (len(s) >= 7) && !(strings.HasPrefix(s[5], "_")) {
			mytime, err := time.Parse(time.RFC3339, s[5])
			if err != nil {
				continue
			}
			res.Timestamp = mytime.In(location).Format("02.01.2006 15:04")
			value := s[6]
			field := s[7]
			if field == "vp" {
				res.BatteryPercent = value
			}
		}
	}
	res.Deveui = deveui
	res.Alias = getDevAlias(deveui)
	res.ActiveUntil = getActiveUntil(deveui)
	res.DaysUntilDeactivated = CalcDaysUntil(res.ActiveUntil)
	return res
}

func CheckThreshold(d string, vp int, u2 string, last_metric OneMetric, info_threshold int, warning_threshold int, alert_threshold int) bool {
	var alias string
	if vp <= info_threshold {
		alias = getDevAlias(d)
	}
	if vp <= alert_threshold {
		if AlarmNotAlreadySent("alarm_sent_accu:", d, u2, alert_threshold) {
			sendEmailAccu(u2, alias, d, last_metric.BatteryPercent, alert_threshold, "ALARM")
			InsertAlert("alarm_sent_accu:", d , u2, alert_threshold)
		}
		return false
	}
	if vp <= warning_threshold {
		if AlarmNotAlreadySent("alarm_sent_accu:", d, u2, warning_threshold) {
			sendEmailAccu(u2, alias, d, last_metric.BatteryPercent, warning_threshold, "WARNING")
			InsertAlert("alarm_sent_accu:", d, u2, warning_threshold)
		}
		return false
	}
	if vp <= info_threshold {
		if AlarmNotAlreadySent("alarm_sent_accu:", d, u2, info_threshold) {
			sendEmailAccu(u2, alias, d, last_metric.BatteryPercent, alert_threshold, "INFO")
			InsertAlert("alarm_sent_accu:", d, u2, info_threshold)
		}
		return false
	}
	return true
}

func logit(log_message string) {
	log.Println(log_message)
}

func main() {
	logit("Starting check_battery...")
	initDB()
	defer closeDB()

	users := getUsers()
	for _, u := range users {
		//fmt.Println(u)
		u2 := u[5:]
		my_devs := getMyDevs(u2)
		for _, d := range my_devs {
			//fmt.Printf("%s:%s\n", u2, d)
			if !strings.HasPrefix(d, "@") {
				last_metric := getLastMetrics(d)
				// Zuerst der Batteriealarm
				if last_metric.BatteryPercent != "" {
					fmt.Printf("%s:%s:%s Percent:%s:%d\n", u2, d, last_metric.BatteryPercent, last_metric.ActiveUntil, last_metric.DaysUntilDeactivated)
					vp, _ := strconv.Atoi(last_metric.BatteryPercent)
					if CheckThreshold(d, vp, u2, last_metric, 50, 20, 10) {
						DeleteAlert("alarm_sent_accu:", d, u2)
					}
				}
				// Jetzt der Alarm wegen der Abodauer
				if last_metric.DaysUntilDeactivated < 30 {
					fmt.Printf("SEND EMAIL %s:%s:%s Percent:%s:%d\n", u2, d, last_metric.BatteryPercent, last_metric.ActiveUntil, last_metric.DaysUntilDeactivated)
					alias := getDevAlias(d)
					sendEmailAbo("joerg.lehmann@nbit.ch", alias, d, last_metric.DaysUntilDeactivated, "INFO")
				}
			}
		}
	}
	logit("Done with check_battery...")
}
