package mapdatastore

import (
	"fmt"
	"time"

	"github.com/google/uuid"
	"github.com/pikami/cosmium/internal/datastore"
	"github.com/pikami/cosmium/internal/resourceid"
	"golang.org/x/exp/maps"
)

func (r *MapDataStore) GetAllDocuments(databaseId string, collectionId string) ([]datastore.Document, datastore.DataStoreStatus) {
	r.storeState.RLock()
	defer r.storeState.RUnlock()

	if _, ok := r.storeState.Databases[databaseId]; !ok {
		return make([]datastore.Document, 0), datastore.StatusNotFound
	}

	if _, ok := r.storeState.Collections[databaseId][collectionId]; !ok {
		return make([]datastore.Document, 0), datastore.StatusNotFound
	}

	return maps.Values(r.storeState.Documents[databaseId][collectionId]), datastore.StatusOk
}

func (r *MapDataStore) GetDocument(databaseId string, collectionId string, documentId string) (datastore.Document, datastore.DataStoreStatus) {
	r.storeState.RLock()
	defer r.storeState.RUnlock()

	if _, ok := r.storeState.Databases[databaseId]; !ok {
		return datastore.Document{}, datastore.StatusNotFound
	}

	if _, ok := r.storeState.Collections[databaseId][collectionId]; !ok {
		return datastore.Document{}, datastore.StatusNotFound
	}

	if _, ok := r.storeState.Documents[databaseId][collectionId][documentId]; !ok {
		return datastore.Document{}, datastore.StatusNotFound
	}

	return r.storeState.Documents[databaseId][collectionId][documentId], datastore.StatusOk
}

func (r *MapDataStore) DeleteDocument(databaseId string, collectionId string, documentId string) datastore.DataStoreStatus {
	r.storeState.Lock()
	defer r.storeState.Unlock()

	if _, ok := r.storeState.Databases[databaseId]; !ok {
		return datastore.StatusNotFound
	}

	if _, ok := r.storeState.Collections[databaseId][collectionId]; !ok {
		return datastore.StatusNotFound
	}

	if _, ok := r.storeState.Documents[databaseId][collectionId][documentId]; !ok {
		return datastore.StatusNotFound
	}

	delete(r.storeState.Documents[databaseId][collectionId], documentId)

	return datastore.StatusOk
}

func (r *MapDataStore) CreateDocument(databaseId string, collectionId string, document map[string]interface{}) (datastore.Document, datastore.DataStoreStatus) {
	r.storeState.Lock()
	defer r.storeState.Unlock()

	var ok bool
	var documentId string
	var database datastore.Database
	var collection datastore.Collection
	if documentId, ok = document["id"].(string); !ok || documentId == "" {
		documentId = fmt.Sprint(uuid.New())
		document["id"] = documentId
	}

	if database, ok = r.storeState.Databases[databaseId]; !ok {
		return datastore.Document{}, datastore.StatusNotFound
	}

	if collection, ok = r.storeState.Collections[databaseId][collectionId]; !ok {
		return datastore.Document{}, datastore.StatusNotFound
	}

	if _, ok := r.storeState.Documents[databaseId][collectionId][documentId]; ok {
		return datastore.Document{}, datastore.Conflict
	}

	document["_ts"] = time.Now().Unix()
	document["_rid"] = resourceid.NewCombined(collection.ResourceID, resourceid.New(resourceid.ResourceTypeDocument))
	document["_etag"] = fmt.Sprintf("\"%s\"", uuid.New())
	document["_self"] = fmt.Sprintf("dbs/%s/colls/%s/docs/%s/", database.ResourceID, collection.ResourceID, document["_rid"])

	r.storeState.Documents[databaseId][collectionId][documentId] = document

	return document, datastore.StatusOk
}

func (r *MapDataStore) GetDocumentIterator(databaseId string, collectionId string) (datastore.DocumentIterator, datastore.DataStoreStatus) {
	documents, status := r.GetAllDocuments(databaseId, collectionId)
	if status != datastore.StatusOk {
		return nil, status
	}

	return &ArrayDocumentIterator{
		documents: documents,
		index:     -1,
	}, datastore.StatusOk
}