package settings

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"server/log"
)

type JsonDB struct {
	Path              string
	filenameDelimiter string
	filenameExtension string
	fileMode          fs.FileMode
	xPathDelimeter    string
}

var globalJsonDB TorrServerDB
var jsonDbLocks = make(map[string]*sync.Mutex)
var jsonDbLocksMutex sync.Mutex

func NewJsonDB() TorrServerDB {
	if globalJsonDB != nil {
		return globalJsonDB
	}
	globalJsonDB := &JsonDB{
		Path:              Path,
		filenameDelimiter: ".",
		filenameExtension: ".json",
		fileMode:          fs.FileMode(0o666),
		xPathDelimeter:    "/",
	}
	return globalJsonDB
}

func (v *JsonDB) CloseDB() {
	// Not necessary
}

func (v *JsonDB) Set(xPath, name string, value []byte) {
	var err error = nil
	jsonObj := map[string]interface{}{}
	if err := json.Unmarshal(value, &jsonObj); err == nil {
		if filename, err := v.xPathToFilename(xPath); err == nil {
			v.lock(filename)
			defer v.unlock(filename)
			if root, err := v.readJsonFileAsMap(filename); err == nil {
				root[name] = jsonObj
				if err = v.writeMapAsJsonFile(filename, root); err == nil {
					return
				}
			}
		}
	}
	v.log(fmt.Sprintf("Set: error writing entry %s->%s", xPath, name), err)
}

func (v *JsonDB) Get(xPath, name string) []byte {
	var err error = nil
	if filename, err := v.xPathToFilename(xPath); err == nil {
		v.lock(filename)
		defer v.unlock(filename)
		if root, err := v.readJsonFileAsMap(filename); err == nil {
			if jsonData, ok := root[name]; ok {
				if byteData, err := json.Marshal(jsonData); err == nil {
					// Return a copy to be safe
					data := make([]byte, len(byteData))
					copy(data, byteData)
					return data
				}
			} else {
				// We assume this is not 'error' but 'no entry' which is normal
				return nil
			}
		}
	}
	v.log(fmt.Sprintf("Get: error reading entry %s->%s", xPath, name), err)
	return nil
}

func (v *JsonDB) List(xPath string) []string {
	var err error = nil
	if filename, err := v.xPathToFilename(xPath); err == nil {
		v.lock(filename)
		defer v.unlock(filename)
		if root, err := v.readJsonFileAsMap(filename); err == nil {
			nameList := make([]string, 0, len(root))
			for k := range root {
				nameList = append(nameList, k)
			}
			return nameList
		}
	}
	v.log(fmt.Sprintf("List: error reading entries in xPath %s", xPath), err)
	return nil
}

func (v *JsonDB) Rem(xPath, name string) {
	var err error = nil
	if filename, err := v.xPathToFilename(xPath); err == nil {
		v.lock(filename)
		defer v.unlock(filename)
		if root, err := v.readJsonFileAsMap(filename); err == nil {
			delete(root, name)
			if err = v.writeMapAsJsonFile(filename, root); err == nil {
				return
			}
		}
	}
	v.log(fmt.Sprintf("Rem: error removing entry %s->%s", xPath, name), err)
}

func (v *JsonDB) Clear(xPath string) {
	filename, err := v.xPathToFilename(xPath)
	if err != nil {
		v.log(fmt.Sprintf("Clear: error converting xPath %s to filename: %v", xPath, err))
		return
	}

	v.lock(filename)
	defer v.unlock(filename)

	path := filepath.Join(v.Path, filename)
	emptyData := []byte("{}")

	if err := os.WriteFile(path, emptyData, v.fileMode); err != nil {
		v.log(fmt.Sprintf("Clear: error writing empty file for xPath %s: %v", xPath, err))
	}
}

func (v *JsonDB) lock(filename string) {
	jsonDbLocksMutex.Lock()
	mtx, ok := jsonDbLocks[filename]
	if !ok {
		mtx = &sync.Mutex{}
		jsonDbLocks[filename] = mtx
	}
	jsonDbLocksMutex.Unlock()
	mtx.Lock()
}

func (v *JsonDB) unlock(filename string) {
	jsonDbLocksMutex.Lock()
	if mtx, ok := jsonDbLocks[filename]; ok {
		mtx.Unlock()
	}
	jsonDbLocksMutex.Unlock()
}

func (v *JsonDB) xPathToFilename(xPath string) (string, error) {
	if pathComponents := strings.Split(xPath, v.xPathDelimeter); len(pathComponents) > 0 {
		return strings.ToLower(strings.Join(pathComponents, v.filenameDelimiter) + v.filenameExtension), nil
	}
	return "", errors.New("xPath has no components")
}

func (v *JsonDB) readJsonFileAsMap(filename string) (map[string]interface{}, error) {
	var err error = nil
	jsonData := map[string]interface{}{}
	path := filepath.Join(v.Path, filename)
	if fileData, err := os.ReadFile(path); err == nil {
		if err = json.Unmarshal(fileData, &jsonData); err != nil {
			v.log(fmt.Sprintf("readJsonFileAsMap(%s) fileData: %s error", filename, fileData), err)
		}
	}
	return jsonData, err
}

func (v *JsonDB) writeMapAsJsonFile(filename string, o map[string]interface{}) error {
	var err error = nil
	path := filepath.Join(v.Path, filename)
	if fileData, err := json.MarshalIndent(o, "", "  "); err == nil {
		if err = os.WriteFile(path, fileData, v.fileMode); err != nil {
			v.log(fmt.Sprintf("writeMapAsJsonFile path: %s, fileMode: %s, fileData: %s error", path, v.fileMode, fileData), err)
		}
	}
	return err
}

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