/**
 * Copyright © 2016-2023 The Thingsboard Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.thingsboard.server.edge;

import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.google.protobuf.AbstractMessage;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UplinkMsg;
import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg;

import java.util.Optional;
import java.util.UUID;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@DaoSqlTest
public class ResourceEdgeTest extends AbstractEdgeTest {

    private static final String TEST_DATA = " ";
    private static final String FILE_NAME = "test.jks";

    @Test
    public void testResources_create_update_delete() throws Exception {
        // create resource
        TbResource resource = new TbResource();
        resource.setResourceType(ResourceType.JKS);
        resource.setTitle("Edge Test Resource");
        resource.setFileName(FILE_NAME);
        resource.setEncodedData(TEST_DATA);

        edgeImitator.expectMessageAmount(1);
        TbResource savedResource = doPost("/api/resource", resource, TbResource.class);
        Assert.assertTrue(edgeImitator.waitForMessages());

        AbstractMessage latestMessage = edgeImitator.getLatestMessage();
        Assert.assertTrue(latestMessage instanceof ResourceUpdateMsg);
        ResourceUpdateMsg resourceUpdateMsg = (ResourceUpdateMsg) latestMessage;
        Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, resourceUpdateMsg.getMsgType());
        Assert.assertEquals(savedResource.getUuidId().getMostSignificantBits(), resourceUpdateMsg.getIdMSB());
        Assert.assertEquals(savedResource.getUuidId().getLeastSignificantBits(), resourceUpdateMsg.getIdLSB());
        TbResource tbResource = JacksonUtil.fromString(resourceUpdateMsg.getEntity(), TbResource.class, true);
        Assert.assertNotNull(tbResource);
        Assert.assertEquals("Edge Test Resource", tbResource.getTitle());
        Assert.assertEquals(ResourceType.JKS, tbResource.getResourceType());
        Assert.assertEquals(FILE_NAME, tbResource.getResourceKey());
        Assert.assertEquals(FILE_NAME, tbResource.getFileName());
        Assert.assertEquals(TEST_DATA, tbResource.getEncodedData());
        Assert.assertTrue(StringUtils.isNotBlank(tbResource.getEtag()));

        // update resource
        edgeImitator.expectMessageAmount(1);
        savedResource.setTitle("Updated Edge Test Resource");
        savedResource = doPost("/api/resource", savedResource, TbResource.class);
        Assert.assertTrue(edgeImitator.waitForMessages());
        latestMessage = edgeImitator.getLatestMessage();
        Assert.assertTrue(latestMessage instanceof ResourceUpdateMsg);
        resourceUpdateMsg = (ResourceUpdateMsg) latestMessage;
        tbResource = JacksonUtil.fromString(resourceUpdateMsg.getEntity(), TbResource.class, true);
        Assert.assertNotNull(tbResource);
        Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, resourceUpdateMsg.getMsgType());
        Assert.assertEquals("Updated Edge Test Resource", tbResource.getTitle());

        // delete resource
        edgeImitator.expectMessageAmount(1);
        doDelete("/api/resource/" + savedResource.getUuidId())
                .andExpect(status().isOk());
        Assert.assertTrue(edgeImitator.waitForMessages());
        latestMessage = edgeImitator.getLatestMessage();
        Assert.assertTrue(latestMessage instanceof ResourceUpdateMsg);
        resourceUpdateMsg = (ResourceUpdateMsg) latestMessage;
        Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, resourceUpdateMsg.getMsgType());
        Assert.assertEquals(savedResource.getUuidId().getMostSignificantBits(), resourceUpdateMsg.getIdMSB());
        Assert.assertEquals(savedResource.getUuidId().getLeastSignificantBits(), resourceUpdateMsg.getIdLSB());
    }

    @Test
    public void testSendResourceToCloud() throws Exception {
        TbResource tbResource = createTbResource();
        UUID uuid = Uuids.timeBased();

        UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
        ResourceUpdateMsg.Builder resourceUpdateMsgBuilder = ResourceUpdateMsg.newBuilder();
        resourceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
        resourceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
        resourceUpdateMsgBuilder.setEntity(JacksonUtil.toString(tbResource));
        resourceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
        testAutoGeneratedCodeByProtobuf(resourceUpdateMsgBuilder);
        uplinkMsgBuilder.addResourceUpdateMsg(resourceUpdateMsgBuilder.build());

        testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder);

        edgeImitator.expectResponsesAmount(1);
        edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());

        Assert.assertTrue(edgeImitator.waitForResponses());

        UplinkResponseMsg latestResponseMsg = edgeImitator.getLatestResponseMsg();
        Assert.assertTrue(latestResponseMsg.getSuccess());

        TbResource tb = doGet("/api/resource/" + uuid, TbResource.class);
        Assert.assertNotNull(tb);
        Assert.assertEquals("Edge Test Resource", tb.getName());
        Assert.assertEquals(TEST_DATA, tb.getEncodedData());
    }

    @Test
    public void testResourceToCloudWithNameThatAlreadyExistsOnCloud() throws Exception {
        TbResource resource = createTbResource();

        edgeImitator.expectMessageAmount(1);
        TbResource savedResource = doPost("/api/resource", resource, TbResource.class);
        Assert.assertTrue(edgeImitator.waitForMessages());

        UUID uuid = Uuids.timeBased();

        UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
        ResourceUpdateMsg.Builder resourceUpdateMsgBuilder = ResourceUpdateMsg.newBuilder();
        resourceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
        resourceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
        resourceUpdateMsgBuilder.setEntity(JacksonUtil.toString(resource));
        resourceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
        testAutoGeneratedCodeByProtobuf(resourceUpdateMsgBuilder);
        uplinkMsgBuilder.addResourceUpdateMsg(resourceUpdateMsgBuilder.build());

        testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder);

        edgeImitator.expectResponsesAmount(1);
        edgeImitator.expectMessageAmount(1);

        edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());

        Assert.assertTrue(edgeImitator.waitForResponses());
        Assert.assertTrue(edgeImitator.waitForMessages());

        Optional<ResourceUpdateMsg> resourceUpdateMsgOpt = edgeImitator.findMessageByType(ResourceUpdateMsg.class);
        Assert.assertTrue(resourceUpdateMsgOpt.isPresent());
        ResourceUpdateMsg latestResourceUpdateMsg = resourceUpdateMsgOpt.get();
        TbResource resourceMsg = JacksonUtil.fromString(latestResourceUpdateMsg.getEntity(), TbResource.class, true);
        Assert.assertNotNull(resourceMsg);
        Assert.assertNotEquals(FILE_NAME, resourceMsg.getResourceKey());

        Assert.assertNotEquals(savedResource.getUuidId(), uuid);

        TbResource tbResource = doGet("/api/resource/" + uuid, TbResource.class);
        Assert.assertNotNull(tbResource);
        Assert.assertNotEquals(FILE_NAME, tbResource.getName());
    }

    private TbResource createTbResource() {
        TbResource tbResource = new TbResource();
        tbResource.setTenantId(tenantId);
        tbResource.setTitle("Edge Test Resource");
        tbResource.setResourceType(ResourceType.JKS);
        tbResource.setResourceKey(FILE_NAME);
        tbResource.setFileName(FILE_NAME);
        tbResource.setEncodedData(TEST_DATA);
        return tbResource;
    }
}
