package config

import (
	"encoding/json"
	"io/ioutil"
	"local/storage"
	"local/truckstop/logtr"
	"os"
	"sync"
	"time"
)

type Matrix struct {
	ReceiveEnabled bool
	Mock           bool
	Homeserver     string
	Username       string
	Token          string
	Device         string
	Room           string
}

type Config struct {
	Log struct {
		Path      string
		Level     logtr.Level
		SOSMatrix Matrix
	}
	Interval struct {
		Input   Duration
		OK      Duration
		Error   Duration
		JobInfo Duration
	}
	Images struct {
		ClientID      string
		ClientSecret  string
		RefreshToken  string
		AccessToken   string
		RefreshURI    string
		RefreshFormat string
		RefreshMethod string
		UploadURI     string
		UploadMethod  string
	}
	Maps struct {
		URIFormat string
		Pickup    bool
		Dropoff   bool
		Pathed    struct {
			Enabled             bool
			DirectionsURIFormat string
			PathedURIFormat     string
			Zoom                struct {
				AcceptableLatLngDelta float32
				Override              int
			}
		}
	}
	Clients map[string]Client
	Storage []string
	Message struct {
		Matrix Matrix
	}
	Once    bool
	Brokers struct {
		UseZips     bool
		RadiusMiles int
		NTG         struct {
			Enabled              bool
			JobInfo              bool
			Mock                 bool
			LoadPageURIFormat    string
			LoadPageAPIURIFormat string
			Username             string
			Password             string
		}
		FastExact struct {
			Enabled  bool
			Mock     bool
			Username string
			Password string
		}
	}

	lock sync.Mutex
	db   storage.DB
}

type Client struct {
	States []State
	Zips   []string
	IDs    struct {
		Matrix string
	}
	Available Time
}

var live Config

func configPath() string {
	p, ok := os.LookupEnv("CONFIG")
	if !ok {
		p = "./config.json"
	}
	return p
}

func Clients(t time.Time) map[string]Client {
	clients := Get().Clients
	result := map[string]Client{}
	for k := range clients {
		if clients[k].Available.Get().IsZero() || t.After(clients[k].Available.Get()) {
			result[k] = clients[k]
		}
	}
	return result
}

func AllZips() []string {
	zipm := map[string]struct{}{}
	for _, v := range Clients(time.Now().Add(time.Hour * 24 * 365)) {
		for _, state := range v.Zips {
			zipm[state] = struct{}{}
		}
	}
	zips := make([]string, 0, len(zipm)+1)
	for k := range zipm {
		zips = append(zips, k)
	}
	return zips
}

func AllStates() []State {
	statem := map[State]struct{}{}
	for _, v := range Clients(time.Now().Add(time.Hour * 24 * 365)) {
		for _, state := range v.States {
			statem[state] = struct{}{}
		}
	}
	states := make([]State, 0, len(statem)+1)
	for k := range statem {
		states = append(states, k)
	}
	return states
}

func Refresh(soser func() logtr.SOSer) error {
	b, err := ioutil.ReadFile(configPath())
	if err != nil {
		return err
	}
	var c Config
	if err := json.Unmarshal(b, &c); err != nil {
		return err
	}
	logtr.SetLogpath(c.Log.Path)
	logtr.SetLevel(c.Log.Level)
	if live.db != nil {
		live.db.Close()
	}
	live = c
	if soser != nil {
		logtr.SetSOSer(soser())
	}
	return nil
}

func Get() *Config {
	return &live
}

func Set(other Config) {
	b, err := json.MarshalIndent(other, "", "   ")
	if err != nil {
		return
	}
	ioutil.WriteFile(configPath(), b, os.ModePerm)
	Refresh(nil)
}

func (c *Config) DB() storage.DB {
	if c.db == nil {
		c.lock.Lock()
		defer c.lock.Unlock()
		if c.db == nil {
			if len(c.Storage) == 0 {
				c.Storage = []string{storage.MAP.String()}
			}
			db, err := storage.New(storage.TypeFromString(c.Storage[0]), c.Storage[1:]...)
			if err != nil {
				panic(err)
			}
			c.db = db
		}
	}
	return c.db
}
