package utils_test

import (
	"testing"

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

// ---- AbstractObject ----

func TestAbstractObject_GetID(t *testing.T) {
	obj := &utils.AbstractObject{UUID: "abc-123"}
	assert.Equal(t, "abc-123", obj.GetID())
}

func TestAbstractObject_GetName(t *testing.T) {
	obj := &utils.AbstractObject{Name: "test-name"}
	assert.Equal(t, "test-name", obj.GetName())
}

func TestAbstractObject_GetCreatorID(t *testing.T) {
	obj := &utils.AbstractObject{CreatorID: "peer-xyz"}
	assert.Equal(t, "peer-xyz", obj.GetCreatorID())
}

func TestAbstractObject_SetID(t *testing.T) {
	obj := &utils.AbstractObject{}
	obj.SetID("new-id")
	assert.Equal(t, "new-id", obj.UUID)
}

func TestAbstractObject_SetName(t *testing.T) {
	obj := &utils.AbstractObject{}
	obj.SetName("hello")
	assert.Equal(t, "hello", obj.Name)
}

func TestAbstractObject_GenerateID_WhenEmpty(t *testing.T) {
	obj := &utils.AbstractObject{}
	obj.GenerateID()
	assert.NotEmpty(t, obj.UUID)
}

func TestAbstractObject_GenerateID_KeepsExisting(t *testing.T) {
	obj := &utils.AbstractObject{UUID: "existing-id"}
	obj.GenerateID()
	assert.Equal(t, "existing-id", obj.UUID)
}

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

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

	obj.IsDraft = false
	assert.False(t, obj.IsDrafted())
}

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

func TestAbstractObject_CanUpdate(t *testing.T) {
	obj := &utils.AbstractObject{UUID: "id-1"}
	other := &utils.AbstractObject{UUID: "id-2"}
	ok, returned := obj.CanUpdate(other)
	assert.True(t, ok)
	assert.Equal(t, other, returned)
}

func TestAbstractObject_Unsign(t *testing.T) {
	obj := &utils.AbstractObject{Signature: []byte("sig")}
	obj.Unsign()
	assert.Nil(t, obj.Signature)
}

func TestAbstractObject_GetSignature(t *testing.T) {
	obj := &utils.AbstractObject{Signature: []byte("sig")}
	assert.Equal(t, []byte("sig"), obj.GetSignature())
}

func TestAbstractObject_DeepCopy(t *testing.T) {
	obj := &utils.AbstractObject{UUID: "id-1", Name: "original"}
	copy := obj.DeepCopy()
	assert.NotNil(t, copy)
	assert.Equal(t, obj.UUID, copy.UUID)
	assert.Equal(t, obj.Name, copy.Name)

	// Mutating the copy should not affect the original
	copy.Name = "modified"
	assert.Equal(t, "original", obj.Name)
}

func TestAbstractObject_UpToDate_Create(t *testing.T) {
	obj := &utils.AbstractObject{CreatorID: ""}
	obj.UpToDate("user1", "peer1", true)
	assert.Equal(t, "peer1", obj.UpdaterID)
	assert.Equal(t, "user1", obj.UserUpdaterID)
	// CreatorID was empty so create branch is skipped
	assert.Empty(t, obj.CreatorID)
}

func TestAbstractObject_UpToDate_CreateWithExistingCreator(t *testing.T) {
	obj := &utils.AbstractObject{CreatorID: "existing-peer"}
	obj.UpToDate("user1", "peer1", true)
	assert.Equal(t, "peer1", obj.CreatorID)
	assert.Equal(t, "user1", obj.UserCreatorID)
}

func TestAbstractObject_UpToDate_Update(t *testing.T) {
	obj := &utils.AbstractObject{CreatorID: "original-peer"}
	obj.UpToDate("user2", "peer2", false)
	assert.Equal(t, "peer2", obj.UpdaterID)
	assert.Equal(t, "original-peer", obj.CreatorID) // unchanged
}

// ---- VerifyAuth ----

func TestAbstractObject_VerifyAuth_NilRequest_GetPublic(t *testing.T) {
	obj := &utils.AbstractObject{AccessMode: 1} // Public = 1
	assert.True(t, obj.VerifyAuth("get", nil))
}

func TestAbstractObject_VerifyAuth_NilRequest_DeletePublic(t *testing.T) {
	obj := &utils.AbstractObject{AccessMode: 1} // Public = 1
	// non-"get" call with nil request → false
	assert.False(t, obj.VerifyAuth("delete", nil))
}

func TestAbstractObject_VerifyAuth_NilRequest_Private(t *testing.T) {
	obj := &utils.AbstractObject{AccessMode: 0} // Private
	assert.False(t, obj.VerifyAuth("get", nil))
}

func TestAbstractObject_VerifyAuth_AdminRequest(t *testing.T) {
	obj := &utils.AbstractObject{}
	req := &tools.APIRequest{Admin: true}
	assert.True(t, obj.VerifyAuth("get", req))
	assert.True(t, obj.VerifyAuth("delete", req))
}

func TestAbstractObject_VerifyAuth_MatchingPeerID(t *testing.T) {
	obj := &utils.AbstractObject{CreatorID: "peer-abc"}
	req := &tools.APIRequest{PeerID: "peer-abc"}
	assert.True(t, obj.VerifyAuth("get", req))
}

func TestAbstractObject_VerifyAuth_MismatchedPeerID(t *testing.T) {
	obj := &utils.AbstractObject{CreatorID: "peer-abc"}
	req := &tools.APIRequest{PeerID: "peer-xyz"}
	assert.False(t, obj.VerifyAuth("get", req))
}

func TestAbstractObject_VerifyAuth_EmptyPeerID(t *testing.T) {
	obj := &utils.AbstractObject{CreatorID: ""}
	req := &tools.APIRequest{PeerID: ""}
	// both empty → condition `ao.CreatorID == request.PeerID && request.PeerID != ""` is false
	assert.False(t, obj.VerifyAuth("get", req))
}

// ---- GetObjectFilters ----

func TestAbstractObject_GetObjectFilters_Star(t *testing.T) {
	obj := &utils.AbstractObject{}
	f := obj.GetObjectFilters("*")
	assert.NotNil(t, f)
}

func TestAbstractObject_GetObjectFilters_Search(t *testing.T) {
	obj := &utils.AbstractObject{}
	f := obj.GetObjectFilters("my-search")
	assert.NotNil(t, f)
}

// ---- Serialize / Deserialize ----

func TestAbstractObject_SerializeDeserialize(t *testing.T) {
	obj := &utils.AbstractObject{UUID: "serial-id", Name: "serial-name"}
	m := obj.Serialize(obj)
	assert.NotNil(t, m)

	dst := &utils.AbstractObject{}
	result := obj.Deserialize(m, dst)
	assert.NotNil(t, result)
	assert.Equal(t, "serial-id", result.GetID())
}

// ---- GetAccessor ----

func TestAbstractObject_GetAccessor_ReturnsNil(t *testing.T) {
	obj := &utils.AbstractObject{}
	acc := obj.GetAccessor(nil)
	assert.Nil(t, acc)
}

// ---- AbstractAccessor ----

func TestAbstractAccessor_GetUser_NilRequest(t *testing.T) {
	acc := &utils.AbstractAccessor[*utils.AbstractObject]{Request: nil}
	assert.Equal(t, "", acc.GetUser())
}

func TestAbstractAccessor_GetUser_WithRequest(t *testing.T) {
	acc := &utils.AbstractAccessor[*utils.AbstractObject]{
		Request: &tools.APIRequest{Username: "alice"},
	}
	assert.Equal(t, "alice", acc.GetUser())
}

func TestAbstractAccessor_GetPeerID_NilRequest(t *testing.T) {
	acc := &utils.AbstractAccessor[*utils.AbstractObject]{Request: nil}
	assert.Equal(t, "", acc.GetPeerID())
}

func TestAbstractAccessor_GetPeerID_WithRequest(t *testing.T) {
	acc := &utils.AbstractAccessor[*utils.AbstractObject]{
		Request: &tools.APIRequest{PeerID: "peer-42"},
	}
	assert.Equal(t, "peer-42", acc.GetPeerID())
}

func TestAbstractAccessor_GetGroups_NilRequest(t *testing.T) {
	acc := &utils.AbstractAccessor[*utils.AbstractObject]{Request: nil}
	assert.Equal(t, []string{}, acc.GetGroups())
}

func TestAbstractAccessor_GetGroups_WithRequest(t *testing.T) {
	acc := &utils.AbstractAccessor[*utils.AbstractObject]{
		Request: &tools.APIRequest{Groups: []string{"g1", "g2"}},
	}
	assert.Equal(t, []string{"g1", "g2"}, acc.GetGroups())
}

func TestAbstractAccessor_ShouldVerifyAuth(t *testing.T) {
	acc := &utils.AbstractAccessor[*utils.AbstractObject]{}
	assert.True(t, acc.ShouldVerifyAuth())
}

func TestAbstractAccessor_GetType(t *testing.T) {
	acc := &utils.AbstractAccessor[*utils.AbstractObject]{
		Type: tools.WORKFLOW,
	}
	assert.Equal(t, tools.WORKFLOW, acc.GetType())
}

func TestAbstractAccessor_GetRequest(t *testing.T) {
	req := &tools.APIRequest{Admin: true}
	acc := &utils.AbstractAccessor[*utils.AbstractObject]{Request: req}
	assert.Equal(t, req, acc.GetRequest())
}

func TestAbstractAccessor_GetCaller_NilRequest(t *testing.T) {
	acc := &utils.AbstractAccessor[*utils.AbstractObject]{Request: nil}
	assert.Nil(t, acc.GetCaller())
}
