package models_test

import (
	"testing"
	"time"

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

func TestGenerateID(t *testing.T) {
	ao := &utils.AbstractObject{}
	ao.GenerateID()
	assert.NotEmpty(t, ao.UUID)
	_, err := uuid.Parse(ao.UUID)
	assert.NoError(t, err)
}

func TestStoreDraftDefault(t *testing.T) {
	ao := &utils.AbstractObject{IsDraft: true}
	ao.StoreDraftDefault()
	assert.False(t, ao.IsDraft)
}

func TestCanUpdate(t *testing.T) {
	ao := &utils.AbstractObject{}
	res, set := ao.CanUpdate(nil)
	assert.True(t, res)
	assert.Nil(t, set)
}

func TestCanDelete(t *testing.T) {
	ao := &utils.AbstractObject{}
	assert.True(t, ao.CanDelete())
}

func TestIsDrafted(t *testing.T) {
	ao := &utils.AbstractObject{IsDraft: true}
	assert.True(t, ao.IsDrafted())
}

func TestGetID(t *testing.T) {
	u := uuid.New().String()
	ao := &utils.AbstractObject{UUID: u}
	assert.Equal(t, u, ao.GetID())
}

func TestGetName(t *testing.T) {
	name := "MyObject"
	ao := &utils.AbstractObject{Name: name}
	assert.Equal(t, name, ao.GetName())
}

func TestGetCreatorID(t *testing.T) {
	id := "creator-123"
	ao := &utils.AbstractObject{CreatorID: id}
	assert.Equal(t, id, ao.GetCreatorID())
}

func TestUpToDate_CreateFalse(t *testing.T) {
	ao := &utils.AbstractObject{}
	now := time.Now()
	time.Sleep(time.Millisecond) // ensure time difference
	ao.UpToDate("user123", "peer456", false)
	assert.WithinDuration(t, now, ao.UpdateDate, time.Second)
	assert.Equal(t, "peer456", ao.UpdaterID)
	assert.Equal(t, "user123", ao.UserUpdaterID)
	assert.True(t, ao.CreationDate.IsZero())
}

func TestUpToDate_CreateTrue(t *testing.T) {
	ao := &utils.AbstractObject{}
	now := time.Now()
	time.Sleep(time.Millisecond)
	ao.UpToDate("user123", "peer456", true)
	assert.WithinDuration(t, now, ao.UpdateDate, time.Second)
	assert.WithinDuration(t, now, ao.CreationDate, time.Second)
	assert.Equal(t, "peer456", ao.UpdaterID)
	assert.Equal(t, "peer456", ao.CreatorID)
	assert.Equal(t, "user123", ao.UserUpdaterID)
	assert.Equal(t, "user123", ao.UserCreatorID)
}

func TestVerifyAuth(t *testing.T) {
	request := &tools.APIRequest{PeerID: "peer123"}
	ao := &utils.AbstractObject{CreatorID: "peer123"}
	assert.True(t, ao.VerifyAuth(request))

	ao = &utils.AbstractObject{AccessMode: utils.Public}
	assert.True(t, ao.VerifyAuth(nil))

	ao = &utils.AbstractObject{AccessMode: utils.Private, CreatorID: "peer123"}
	request = &tools.APIRequest{PeerID: "wrong"}
	assert.False(t, ao.VerifyAuth(request))
}

func TestGetObjectFilters(t *testing.T) {
	ao := &utils.AbstractObject{}
	f := ao.GetObjectFilters("*")
	assert.NotNil(t, f)
	assert.Contains(t, f.Or, "abstractobject.name")
	assert.Equal(t, dbs.LIKE.String(), f.Or["abstractobject.name"][0].Operator)
}

func TestDeserialize(t *testing.T) {
	ao := &utils.AbstractObject{}
	input := map[string]interface{}{"name": "test", "id": uuid.New().String()}
	res := ao.Deserialize(input, &utils.AbstractObject{})
	assert.NotNil(t, res)
}

func TestSerialize(t *testing.T) {
	ao := &utils.AbstractObject{Name: "test", UUID: uuid.New().String()}
	m := ao.Serialize(ao)
	assert.Equal(t, "test", m["name"])
}

func TestAbstractAccessorMethods(t *testing.T) {
	r := &utils.AbstractAccessor{Request: &tools.APIRequest{Username: "alice", PeerID: "peer1", Groups: []string{"dev"}}}
	assert.True(t, r.ShouldVerifyAuth())
	assert.Equal(t, "alice", r.GetUser())
	assert.Equal(t, "peer1", r.GetPeerID())
	assert.Equal(t, []string{"dev"}, r.GetGroups())
	assert.Equal(t, r.Request.Caller, r.GetCaller())
}