package main

import (
	_ "github.com/mattn/go-sqlite3"
)

import (
	s "shroom_server/shroom_internals"
)

import (
	"database/sql"
	"embed"
	"encoding/json"
	"fmt"
	"io/fs"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"
)

//go:embed auth_secret
var auth_secret string

//go:embed static/*
var content embed.FS

type statusJson struct {
	Connected   bool `json:"connected"`
	Humidifier  bool `json:"humidifier"`
	Humidifier2 bool `json:"humidifier2"`
	ManualMode  bool `json:"manual_mode"`
}

type adminMsg struct {
	Auth string                 `json:"auth"`
	Msg  map[string]interface{} `json:"data"`
}

// returns a function that multiplies the number at the very last segment of the url
// and returns the data that was collected in the last n*multiplier milliseconds
func dumpData(db *sql.DB, dc *s.DataCache, multiplier int64) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, req *http.Request) {
		now := time.Now().Unix()
		path := strings.Split(req.URL.Path, "/")
		last := path[len(path)-1]
		count, err := strconv.Atoi(last)
		if err != nil {
			w.WriteHeader(400)
			w.Write([]byte("could not read integer in path: " + err.Error()))
			return
		}
		offset := int64(count) * multiplier
		t := now*1000 - offset
		//log.Println("req start ", t)
		var msg []byte
		cachedPoints := dc.ReadSince(uint64(t))
		if cachedPoints != nil {
			msg, err = s.DatapointsToJson(cachedPoints)
			if err != nil {
				msg = nil
			}
		}
		// fallback to actually reading from the sql
		if msg == nil {
			log.Println("unable to read from cache for ", t, " using sql")
			msg, err = s.GetRows(db, t)
		}
		if err != nil {
			w.WriteHeader(500)
			w.Write([]byte(err.Error()))
		} else {
			w.Write(msg)
		}
	}
}

func main() {
	db, err := sql.Open("sqlite3", "shrooms.db")
	if err != nil {
		log.Fatal("unable to open db ", err)
	}
	if err = db.Ping(); err != nil {
		log.Fatal("unable to ping db ", err)
	}

	err = s.CreateTable(db)
	if err != nil {
		log.Fatal("unable to create table ", err)
	}

	state := s.NewShroomState()
	s.InitTcpServer(db, &state)

	contentSub, err := fs.Sub(content, "static")
	if err != nil {
		log.Fatal("unable to use subdirectory of embedded fs: ", err)
	}

	dumpWeek := dumpData(db, &state.Cache, 7*24*60*60*1000)
	dumpDay := dumpData(db, &state.Cache, 24*60*60*1000)
	dumpHour := dumpData(db, &state.Cache, 60*60*1000)
	dumpMinute := dumpData(db, &state.Cache, 60*1000)
	dumpSecond := dumpData(db, &state.Cache, 1000)

	lastPoint := func(w http.ResponseWriter, _req *http.Request) {
		var err error
		time := state.Cache.LatestTime()
		if time > 0 {
			msg, err := json.Marshal(time)
			if err != nil {
				w.WriteHeader(500)
				w.Write([]byte(err.Error()))
			} else {
				w.Write(msg)
			}
			return
		}

		msg, err := s.LastTime(db)
		if err != nil {
			w.WriteHeader(500)
			w.Write([]byte(err.Error()))
		} else {
			w.Write(msg)
		}
	}

	getStatus := func(w http.ResponseWriter, _req *http.Request) {
		state.RLock()
		num_connections := state.NumConnections
		humidifier := state.HumidifierOn
		humidifier2 := state.Humidifier2On
		manual_mode := state.ManualMode
		state.RUnlock()
		s := statusJson{
			Connected:   num_connections > 0,
			Humidifier:  humidifier,
			Humidifier2: humidifier2,
			ManualMode:  manual_mode,
		}
		msg, err := json.Marshal(s)
		if err != nil {
			err = fmt.Errorf("unable to marshal json: %w", err)
			w.WriteHeader(400)
			w.Write([]byte(err.Error()))
		} else {
			w.Write(msg)
		}
	}

	adminHandler := func(w http.ResponseWriter, req *http.Request) {
		if req.Method != "POST" {
			w.WriteHeader(405)
			w.Write([]byte("method must be POST"))
			return
		}
		msg := make([]byte, 256)
		l, err := req.Body.Read(msg)
		if err != nil && l == 0 {
			err = fmt.Errorf("unable to read body: %w", err)
			w.WriteHeader(400)
			w.Write([]byte(err.Error()))
			return
		}
		adminReq := adminMsg{}
		err = json.Unmarshal(msg[:l], &adminReq)
		if err != nil {
			err = fmt.Errorf("unable to unmarshal body as json: %w", err)
			w.WriteHeader(400)
			w.Write([]byte(err.Error()))
			return
		}

		// switch to embedded secret
		if adminReq.Auth != auth_secret {
			w.WriteHeader(401)
			w.Write([]byte("invalid secret"))
			return
		}

		inner_msg, err := json.Marshal(adminReq.Msg)
		if err != nil {
			err = fmt.Errorf("unable to marshal inner message: %w", err)
			w.WriteHeader(400)
			w.Write([]byte(err.Error()))
			return
		}
		select {
		case state.Commands <- inner_msg:
			w.Write([]byte("ok"))
		default:
			w.WriteHeader(503)
			w.Write([]byte("unable to forward request; controller may not be connected"))
		}
	}

	paramsHandler := func(w http.ResponseWriter, req *http.Request) {
		msg, err := s.QueryParams(&state)
		if err != nil {
			w.WriteHeader(500)
			w.Write([]byte(err.Error()))
			return
		}
		w.Write(msg)
	}

	updateHandler := func(w http.ResponseWriter, req *http.Request) {
		s.WaitForClose(state.Wait)
		w.Write([]byte("ok"))
	}
	statusUpdateHandler := func(w http.ResponseWriter, req *http.Request) {
		s.WaitForClose(state.StatusWait)
		w.Write([]byte("ok"))
	}

	http.Handle("/d/", http.StripPrefix("/d/", http.FileServer(http.Dir("./dev"))))
	http.Handle("/", http.FileServer(http.FS(contentSub)))
	http.HandleFunc("/api/last/weeks/", dumpWeek)
	http.HandleFunc("/api/last/days/", dumpDay)
	http.HandleFunc("/api/last/hours/", dumpHour)
	http.HandleFunc("/api/last/minutes/", dumpMinute)
	http.HandleFunc("/api/last/seconds/", dumpSecond)
	http.HandleFunc("/api/latest", lastPoint)
	http.HandleFunc("/api/status", getStatus)
	http.HandleFunc("/api/admin", adminHandler)
	http.HandleFunc("/api/params", paramsHandler)
	http.HandleFunc("/api/update", updateHandler)
	http.HandleFunc("/api/status_update", statusUpdateHandler)

	// periodically clear old entries from the database
	go func() {
		// TODO maybe make this exit gracefully
		for {
			t, err := s.OldestTime(db)
			if err != nil {
				log.Println("unable to get oldest time: ", err)
			}
			now := time.Now().Unix()
			diff := now*1000 - t
			log.Println("oldest time", t, " current time", now, "diff", diff)
			if diff > 2*7*24*60*60*1000 {
				err = s.ClearOldRows(db, 1000*now-8*24*60*60*1000)
				if err != nil {
					log.Println("unable to delete rows: ", err)
				}
			}
			time.Sleep(24 * time.Hour)
		}
	}()

	err = http.ListenAndServe(":8085", nil)
	if err != nil {
		log.Fatal("unable to start server: ", err)
	}
	defer db.Close()
}