//go:build mipsle || riscv64
// +build mipsle riscv64

package database

import (
	"encoding/json"
	"errors"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

func newDatabase(dbfile string, readOnlyMode bool) (*Database, error) {
	dbRootPath := filepath.ToSlash(filepath.Clean(dbfile))
	dbRootPath = "fsdb/" + dbRootPath
	err := os.MkdirAll(dbRootPath, 0755)
	if err != nil {
		return nil, err
	}

	tableMap := sync.Map{}
	//build the table list from file system
	files, err := filepath.Glob(filepath.Join(dbRootPath, "/*"))
	if err != nil {
		return nil, err
	}

	for _, file := range files {
		if isDirectory(file) {
			tableMap.Store(filepath.Base(file), "")
		}
	}

	log.Println("Filesystem Emulated Key-value Database Service Started: " + dbRootPath)
	return &Database{
		Db:       dbRootPath,
		Tables:   tableMap,
		ReadOnly: readOnlyMode,
	}, nil
}

func (d *Database) dump(filename string) ([]string, error) {
	//Get all file objects from root
	rootfiles, err := filepath.Glob(filepath.Join(d.Db.(string), "/*"))
	if err != nil {
		return []string{}, err
	}

	//Filter out the folders
	rootFolders := []string{}
	for _, file := range rootfiles {
		if !isDirectory(file) {
			rootFolders = append(rootFolders, filepath.Base(file))
		}
	}

	return rootFolders, nil
}

func (d *Database) newTable(tableName string) error {
	if d.ReadOnly {
		return errors.New("Operation rejected in ReadOnly mode")
	}
	tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
	if !fileExists(tablePath) {
		return os.MkdirAll(tablePath, 0755)
	}
	return nil
}

func (d *Database) tableExists(tableName string) bool {
	tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
	if _, err := os.Stat(tablePath); errors.Is(err, os.ErrNotExist) {
		return false
	}

	if !isDirectory(tablePath) {
		return false
	}

	return true
}

func (d *Database) dropTable(tableName string) error {
	if d.ReadOnly {
		return errors.New("Operation rejected in ReadOnly mode")
	}
	tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
	if d.tableExists(tableName) {
		return os.RemoveAll(tablePath)
	} else {
		return errors.New("table not exists")
	}

}

func (d *Database) write(tableName string, key string, value interface{}) error {
	if d.ReadOnly {
		return errors.New("Operation rejected in ReadOnly mode")
	}
	tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
	js, err := json.Marshal(value)
	if err != nil {
		return err
	}

	key = strings.ReplaceAll(key, "/", "-SLASH_SIGN-")

	return os.WriteFile(filepath.Join(tablePath, key+".entry"), js, 0755)
}

func (d *Database) read(tableName string, key string, assignee interface{}) error {
	if !d.keyExists(tableName, key) {
		return errors.New("key not exists")
	}

	key = strings.ReplaceAll(key, "/", "-SLASH_SIGN-")

	tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
	entryPath := filepath.Join(tablePath, key+".entry")
	content, err := os.ReadFile(entryPath)
	if err != nil {
		return err
	}

	err = json.Unmarshal(content, &assignee)
	return err
}

func (d *Database) keyExists(tableName string, key string) bool {
	key = strings.ReplaceAll(key, "/", "-SLASH_SIGN-")
	tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
	entryPath := filepath.Join(tablePath, key+".entry")
	return fileExists(entryPath)
}

func (d *Database) delete(tableName string, key string) error {
	if d.ReadOnly {
		return errors.New("Operation rejected in ReadOnly mode")
	}
	if !d.keyExists(tableName, key) {
		return errors.New("key not exists")
	}
	key = strings.ReplaceAll(key, "/", "-SLASH_SIGN-")
	tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
	entryPath := filepath.Join(tablePath, key+".entry")

	return os.Remove(entryPath)
}

func (d *Database) listTable(tableName string) ([][][]byte, error) {
	if !d.tableExists(tableName) {
		return [][][]byte{}, errors.New("table not exists")
	}
	tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
	entries, err := filepath.Glob(filepath.Join(tablePath, "/*.entry"))
	if err != nil {
		return [][][]byte{}, err
	}

	var results [][][]byte = [][][]byte{}
	for _, entry := range entries {
		if !isDirectory(entry) {
			//Read it
			key := filepath.Base(entry)
			key = strings.TrimSuffix(key, filepath.Ext(key))
			key = strings.ReplaceAll(key, "-SLASH_SIGN-", "/")

			bkey := []byte(key)
			bval := []byte("")
			c, err := os.ReadFile(entry)
			if err != nil {
				break
			}

			bval = c
			results = append(results, [][]byte{bkey, bval})
		}
	}
	return results, nil
}

func (d *Database) close() {
	//Nothing to close as it is file system
}

func isDirectory(path string) bool {
	fileInfo, err := os.Stat(path)
	if err != nil {
		return false
	}

	return fileInfo.IsDir()
}

func fileExists(name string) bool {
	_, err := os.Stat(name)
	if err == nil {
		return true
	}
	if errors.Is(err, os.ErrNotExist) {
		return false
	}
	return false
}