package settings

import (
	"fmt"
	"reflect"
	"sort"
	"strings"

	"server/log"

	"golang.org/x/exp/slices"
)

type XPathDBRouter struct {
	dbs      []TorrServerDB
	routes   []string
	route2db map[string]TorrServerDB
	dbNames  map[TorrServerDB]string
}

func NewXPathDBRouter() *XPathDBRouter {
	router := &XPathDBRouter{
		dbs:      []TorrServerDB{},
		dbNames:  map[TorrServerDB]string{},
		routes:   []string{},
		route2db: map[string]TorrServerDB{},
	}
	return router
}

func (v *XPathDBRouter) RegisterRoute(db TorrServerDB, xPath string) error {
	newRoute := v.xPathToRoute(xPath)

	if slices.Contains(v.routes, newRoute) {
		return fmt.Errorf("route \"%s\" already in routing table", newRoute)
	}

	// First DB becomes Default DB with default route
	if len(v.dbs) == 0 && len(newRoute) != 0 {
		v.RegisterRoute(db, "")
	}

	if !slices.Contains(v.dbs, db) {
		v.dbs = append(v.dbs, db)
		v.dbNames[db] = reflect.TypeOf(db).Elem().Name()
		v.log(fmt.Sprintf("Registered new DB \"%s\", total %d DBs registered", v.getDBName(db), len(v.dbs)))
	}

	v.route2db[newRoute] = db
	v.routes = append(v.routes, newRoute)

	// Sort routes by length descending.
	//   It is important later to help selecting
	//   most suitable route in getDBForXPath(xPath)
	sort.Slice(v.routes, func(iLeft, iRight int) bool {
		return len(v.routes[iLeft]) > len(v.routes[iRight])
	})
	v.log(fmt.Sprintf("Registered new route \"%s\" for DB \"%s\", total %d routes", getDefaultRoureName(newRoute), v.getDBName(db), len(v.routes)))
	return nil
}

func getDefaultRoureName(route string) string {
	if len(route) > 0 {
		return route
	}
	return "default"
}

func (v *XPathDBRouter) xPathToRoute(xPath string) string {
	return strings.ToLower(strings.TrimSpace(xPath))
}

func (v *XPathDBRouter) getDBForXPath(xPath string) TorrServerDB {
	if len(v.dbs) == 0 {
		return nil
	}
	lookup_route := v.xPathToRoute(xPath)
	var db TorrServerDB = nil
	// Expected v.routes sorted by length descending
	for _, route_prefix := range v.routes {
		if strings.HasPrefix(lookup_route, route_prefix) {
			db = v.route2db[route_prefix]
			break
		}
	}
	return db
}

func (v *XPathDBRouter) Get(xPath, name string) []byte {
	return v.getDBForXPath(xPath).Get(xPath, name)
}

func (v *XPathDBRouter) Set(xPath, name string, value []byte) {
	v.getDBForXPath(xPath).Set(xPath, name, value)
}

func (v *XPathDBRouter) List(xPath string) []string {
	return v.getDBForXPath(xPath).List(xPath)
}

func (v *XPathDBRouter) Rem(xPath, name string) {
	v.getDBForXPath(xPath).Rem(xPath, name)
}

func (v *XPathDBRouter) Clear(xPath string) {
	v.getDBForXPath(xPath).Clear(xPath)
}

func (v *XPathDBRouter) CloseDB() {
	for _, db := range v.dbs {
		db.CloseDB()
	}
	v.dbs = nil
	v.routes = nil
	v.route2db = nil
	v.dbNames = nil
}

func (v *XPathDBRouter) getDBName(db TorrServerDB) string {
	return v.dbNames[db]
}

func (v *XPathDBRouter) log(s string, params ...interface{}) {
	if len(params) > 0 {
		log.TLogln(fmt.Sprintf("XPathDBRouter: %s: %s", s, fmt.Sprint(params...)))
	} else {
		log.TLogln(fmt.Sprintf("XPathDBRouter: %s", s))
	}
}
