// File: workspace_accessor_test.go

package workspace_test

import (
	"errors"
	"testing"

	"cloud.o-forge.io/core/oc-lib/dbs"
	"cloud.o-forge.io/core/oc-lib/models/utils"
	"cloud.o-forge.io/core/oc-lib/models/workspace"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

type MockWorkspaceAccessor struct {
	mock.Mock
	workspace.Workspace
}

func safeDBObject(v interface{}) utils.DBObject {
	if v == nil {
		return nil
	}
	return v.(utils.DBObject)
}

func safeShallowList(v interface{}) []utils.ShallowDBObject {
	if v == nil {
		return nil
	}
	return v.([]utils.ShallowDBObject)
}

func (m *MockWorkspaceAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
	args := m.Called(data)
	return safeDBObject(args.Get(0)), args.Int(1), args.Error(2)
}

func (m *MockWorkspaceAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
	args := m.Called(set, id)
	return safeDBObject(args.Get(0)), args.Int(1), args.Error(2)
}

func (m *MockWorkspaceAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
	args := m.Called(id)
	return safeDBObject(args.Get(0)), args.Int(1), args.Error(2)
}

func (m *MockWorkspaceAccessor) LoadOne(id string) (utils.DBObject, int, error) {
	args := m.Called(id)
	return safeDBObject(args.Get(0)), args.Int(1), args.Error(2)
}

func (m *MockWorkspaceAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
	args := m.Called(isDraft)
	return safeShallowList(args.Get(0)), args.Int(1), args.Error(2)
}

func (m *MockWorkspaceAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
	args := m.Called(filters, search, isDraft)
	return safeShallowList(args.Get(0)), args.Int(1), args.Error(2)
}

func TestStoreOne_Success(t *testing.T) {
	mockAcc := new(MockWorkspaceAccessor)
	ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{Name: "test_ws"}}
	mockAcc.On("StoreOne", ws).Return(ws, 200, nil)

	res, code, err := mockAcc.StoreOne(ws)
	assert.NoError(t, err)
	assert.Equal(t, 200, code)
	assert.Equal(t, ws, res)
	mockAcc.AssertExpectations(t)
}

func TestStoreOne_Conflict(t *testing.T) {
	mockAcc := new(MockWorkspaceAccessor)
	ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{Name: "duplicate"}}
	mockAcc.On("StoreOne", ws).Return(nil, 409, errors.New("a workspace with the same name already exists"))

	res, code, err := mockAcc.StoreOne(ws)
	assert.Error(t, err)
	assert.Equal(t, 409, code)
	assert.Nil(t, res)
}

func TestUpdateOne_Success(t *testing.T) {
	mockAcc := new(MockWorkspaceAccessor)
	ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{UUID: "123", IsDraft: false}}
	mockAcc.On("UpdateOne", ws, "123").Return(ws, 200, nil)

	res, code, err := mockAcc.UpdateOne(ws, "123")
	assert.NoError(t, err)
	assert.Equal(t, 200, code)
	assert.Equal(t, ws, res)
}

func TestUpdateOne_Error(t *testing.T) {
	mockAcc := new(MockWorkspaceAccessor)
	ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{UUID: "999"}}
	err := errors.New("update failed")
	mockAcc.On("UpdateOne", ws, "999").Return(nil, 500, err)

	res, code, err := mockAcc.UpdateOne(ws, "999")
	assert.Error(t, err)
	assert.Equal(t, 500, code)
	assert.Nil(t, res)
}

func TestDeleteOne_Success(t *testing.T) {
	mockAcc := new(MockWorkspaceAccessor)
	ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{UUID: "321"}}
	mockAcc.On("DeleteOne", "321").Return(ws, 200, nil)

	res, code, err := mockAcc.DeleteOne("321")
	assert.NoError(t, err)
	assert.Equal(t, 200, code)
	assert.Equal(t, ws, res)
}

func TestDeleteOne_NotFound(t *testing.T) {
	mockAcc := new(MockWorkspaceAccessor)
	err := errors.New("not found")
	mockAcc.On("DeleteOne", "notfound").Return(nil, 404, err)

	res, code, err := mockAcc.DeleteOne("notfound")
	assert.Error(t, err)
	assert.Equal(t, 404, code)
	assert.Nil(t, res)
}

func TestLoadOne_Success(t *testing.T) {
	mockAcc := new(MockWorkspaceAccessor)
	ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{UUID: "loadid"}}
	mockAcc.On("LoadOne", "loadid").Return(ws, 200, nil)

	res, code, err := mockAcc.LoadOne("loadid")
	assert.NoError(t, err)
	assert.Equal(t, 200, code)
	assert.Equal(t, ws, res)
}

func TestLoadOne_Error(t *testing.T) {
	mockAcc := new(MockWorkspaceAccessor)
	err := errors.New("db error")
	mockAcc.On("LoadOne", "badid").Return(nil, 500, err)

	res, code, err := mockAcc.LoadOne("badid")
	assert.Error(t, err)
	assert.Equal(t, 500, code)
	assert.Nil(t, res)
}

func TestLoadAll_Success(t *testing.T) {
	mockAcc := new(MockWorkspaceAccessor)
	ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{UUID: "all1"}}
	mockAcc.On("LoadAll", true).Return([]utils.ShallowDBObject{ws}, 200, nil)

	res, code, err := mockAcc.LoadAll(true)
	assert.NoError(t, err)
	assert.Equal(t, 200, code)
	assert.Len(t, res, 1)
}

func TestLoadAll_Empty(t *testing.T) {
	mockAcc := new(MockWorkspaceAccessor)
	mockAcc.On("LoadAll", false).Return([]utils.ShallowDBObject{}, 200, nil)

	res, code, err := mockAcc.LoadAll(false)
	assert.NoError(t, err)
	assert.Equal(t, 200, code)
	assert.Empty(t, res)
}

func TestSearch_Success(t *testing.T) {
	mockAcc := new(MockWorkspaceAccessor)
	filters := &dbs.Filters{}
	mockAcc.On("Search", filters, "keyword", true).Return([]utils.ShallowDBObject{}, 200, nil)

	res, code, err := mockAcc.Search(filters, "keyword", true)
	assert.NoError(t, err)
	assert.Equal(t, 200, code)
	assert.NotNil(t, res)
}

func TestSearch_Error(t *testing.T) {
	mockAcc := new(MockWorkspaceAccessor)
	filters := &dbs.Filters{}
	err := errors.New("search failed")
	mockAcc.On("Search", filters, "fail", false).Return(nil, 500, err)

	res, code, err := mockAcc.Search(filters, "fail", false)
	assert.Error(t, err)
	assert.Equal(t, 500, code)
	assert.Nil(t, res)
}

// Additional edge test cases

func TestStoreOne_InvalidType(t *testing.T) {
	mockAcc := new(MockWorkspaceAccessor)
	mockAcc.On("StoreOne", mock.Anything).Return(nil, 400, errors.New("invalid type"))

	res, code, err := mockAcc.StoreOne(&utils.AbstractObject{})
	assert.Error(t, err)
	assert.Equal(t, 400, code)
	assert.Nil(t, res)
}

func TestUpdateOne_NilData(t *testing.T) {
	mockAcc := new(MockWorkspaceAccessor)
	mockAcc.On("UpdateOne", nil, "id").Return(nil, 400, errors.New("nil data"))

	res, code, err := mockAcc.UpdateOne(nil, "id")
	assert.Error(t, err)
	assert.Equal(t, 400, code)
	assert.Nil(t, res)
}

func TestDeleteOne_NilID(t *testing.T) {
	mockAcc := new(MockWorkspaceAccessor)
	mockAcc.On("DeleteOne", "").Return(nil, 400, errors.New("missing ID"))

	res, code, err := mockAcc.DeleteOne("")
	assert.Error(t, err)
	assert.Equal(t, 400, code)
	assert.Nil(t, res)
}
