package mapdatastore

import (
	"encoding/json"
	"log"
	"os"
	"reflect"
	"sync"

	"github.com/pikami/cosmium/internal/datastore"
	"github.com/pikami/cosmium/internal/logger"
)

type State struct {
	sync.RWMutex

	// Map databaseId -> Database
	Databases map[string]datastore.Database `json:"databases"`

	// Map databaseId -> collectionId -> Collection
	Collections map[string]map[string]datastore.Collection `json:"collections"`

	// Map databaseId -> collectionId -> documentId -> Documents
	Documents map[string]map[string]map[string]datastore.Document `json:"documents"`

	// Map databaseId -> collectionId -> triggerId -> Trigger
	Triggers map[string]map[string]map[string]datastore.Trigger `json:"triggers"`

	// Map databaseId -> collectionId -> spId -> StoredProcedure
	StoredProcedures map[string]map[string]map[string]datastore.StoredProcedure `json:"sprocs"`

	// Map databaseId -> collectionId -> udfId -> UserDefinedFunction
	UserDefinedFunctions map[string]map[string]map[string]datastore.UserDefinedFunction `json:"udfs"`
}

func (r *MapDataStore) InitializeDataStore() {
	if r.initialDataFilePath != "" {
		r.LoadStateFS(r.initialDataFilePath)
		return
	}

	if r.persistDataFilePath != "" {
		stat, err := os.Stat(r.persistDataFilePath)
		if err != nil {
			return
		}

		if stat.IsDir() {
			logger.ErrorLn("Argument '-Persist' must be a path to file, not a directory.")
			os.Exit(1)
		}

		r.LoadStateFS(r.persistDataFilePath)
		return
	}
}

func (r *MapDataStore) LoadStateFS(filePath string) {
	data, err := os.ReadFile(filePath)
	if err != nil {
		log.Fatalf("Error reading state JSON file: %v", err)
		return
	}

	err = r.LoadStateJSON(string(data))
	if err != nil {
		log.Fatalf("Error unmarshalling state JSON: %v", err)
	}
}

func (r *MapDataStore) LoadStateJSON(jsonData string) error {
	r.storeState.Lock()
	defer r.storeState.Unlock()

	var state State
	if err := json.Unmarshal([]byte(jsonData), &state); err != nil {
		return err
	}

	r.storeState.Collections = state.Collections
	r.storeState.Databases = state.Databases
	r.storeState.Documents = state.Documents

	r.ensureStoreStateNoNullReferences()

	logger.InfoLn("Loaded state:")
	logger.Infof("Databases: %d\n", getLength(r.storeState.Databases))
	logger.Infof("Collections: %d\n", getLength(r.storeState.Collections))
	logger.Infof("Documents: %d\n", getLength(r.storeState.Documents))
	logger.Infof("Triggers: %d\n", getLength(r.storeState.Triggers))
	logger.Infof("Stored procedures: %d\n", getLength(r.storeState.StoredProcedures))
	logger.Infof("User defined functions: %d\n", getLength(r.storeState.UserDefinedFunctions))

	return nil
}

func (r *MapDataStore) SaveStateFS(filePath string) {
	r.storeState.RLock()
	defer r.storeState.RUnlock()

	data, err := json.MarshalIndent(r.storeState, "", "\t")
	if err != nil {
		logger.Errorf("Failed to save state: %v\n", err)
		return
	}

	os.WriteFile(filePath, data, os.ModePerm)

	logger.InfoLn("Saved state:")
	logger.Infof("Databases: %d\n", getLength(r.storeState.Databases))
	logger.Infof("Collections: %d\n", getLength(r.storeState.Collections))
	logger.Infof("Documents: %d\n", getLength(r.storeState.Documents))
	logger.Infof("Triggers: %d\n", getLength(r.storeState.Triggers))
	logger.Infof("Stored procedures: %d\n", getLength(r.storeState.StoredProcedures))
	logger.Infof("User defined functions: %d\n", getLength(r.storeState.UserDefinedFunctions))
}

func (r *MapDataStore) DumpToJson() (string, error) {
	r.storeState.RLock()
	defer r.storeState.RUnlock()

	data, err := json.MarshalIndent(r.storeState, "", "\t")
	if err != nil {
		logger.Errorf("Failed to serialize state: %v\n", err)
		return "", err
	}

	return string(data), nil

}

func (r *MapDataStore) Close() {
	if r.persistDataFilePath != "" {
		r.SaveStateFS(r.persistDataFilePath)
	}
}

func getLength(v interface{}) int {
	switch v.(type) {
	case datastore.Database,
		datastore.Collection,
		datastore.Document,
		datastore.Trigger,
		datastore.StoredProcedure,
		datastore.UserDefinedFunction:
		return 1
	}

	rv := reflect.ValueOf(v)
	if rv.Kind() != reflect.Map {
		return -1
	}

	count := 0
	for _, key := range rv.MapKeys() {
		if rv.MapIndex(key).Kind() == reflect.Map {
			count += getLength(rv.MapIndex(key).Interface())
		} else {
			count++
		}
	}

	return count
}

func (r *MapDataStore) ensureStoreStateNoNullReferences() {
	if r.storeState.Databases == nil {
		r.storeState.Databases = make(map[string]datastore.Database)
	}

	if r.storeState.Collections == nil {
		r.storeState.Collections = make(map[string]map[string]datastore.Collection)
	}

	if r.storeState.Documents == nil {
		r.storeState.Documents = make(map[string]map[string]map[string]datastore.Document)
	}

	if r.storeState.Triggers == nil {
		r.storeState.Triggers = make(map[string]map[string]map[string]datastore.Trigger)
	}

	if r.storeState.StoredProcedures == nil {
		r.storeState.StoredProcedures = make(map[string]map[string]map[string]datastore.StoredProcedure)
	}

	if r.storeState.UserDefinedFunctions == nil {
		r.storeState.UserDefinedFunctions = make(map[string]map[string]map[string]datastore.UserDefinedFunction)
	}

	for database := range r.storeState.Databases {
		if r.storeState.Collections[database] == nil {
			r.storeState.Collections[database] = make(map[string]datastore.Collection)
		}

		if r.storeState.Documents[database] == nil {
			r.storeState.Documents[database] = make(map[string]map[string]datastore.Document)
		}

		if r.storeState.Triggers[database] == nil {
			r.storeState.Triggers[database] = make(map[string]map[string]datastore.Trigger)
		}

		if r.storeState.StoredProcedures[database] == nil {
			r.storeState.StoredProcedures[database] = make(map[string]map[string]datastore.StoredProcedure)
		}

		if r.storeState.UserDefinedFunctions[database] == nil {
			r.storeState.UserDefinedFunctions[database] = make(map[string]map[string]datastore.UserDefinedFunction)
		}

		for collection := range r.storeState.Collections[database] {
			if r.storeState.Documents[database][collection] == nil {
				r.storeState.Documents[database][collection] = make(map[string]datastore.Document)
			}

			for document := range r.storeState.Documents[database][collection] {
				if r.storeState.Documents[database][collection][document] == nil {
					delete(r.storeState.Documents[database][collection], document)
				}
			}

			if r.storeState.Triggers[database][collection] == nil {
				r.storeState.Triggers[database][collection] = make(map[string]datastore.Trigger)
			}

			if r.storeState.StoredProcedures[database][collection] == nil {
				r.storeState.StoredProcedures[database][collection] = make(map[string]datastore.StoredProcedure)
			}

			if r.storeState.UserDefinedFunctions[database][collection] == nil {
				r.storeState.UserDefinedFunctions[database][collection] = make(map[string]datastore.UserDefinedFunction)
			}
		}
	}
}