/**
 * Copyright © 2016-2026 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.edqs.repo;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.edqs.AttributeKv;
import org.thingsboard.server.common.data.edqs.LatestTsKv;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.DeviceTypeFilter;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.StringFilterPredicate;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

public class DeviceTypeFilterTest extends AbstractEDQTest {

    private final DeviceProfileId loraProfileId = new DeviceProfileId(UUID.randomUUID());

    @Before
    public void setUp() {
        DeviceProfile deviceProfile = new DeviceProfile(loraProfileId);
        deviceProfile.setName("LoRa");
        deviceProfile.setDefault(false);
        deviceProfile.setType(DeviceProfileType.DEFAULT);
        addOrUpdate(EntityType.DEVICE_PROFILE, deviceProfile);
    }

    @After
    public void tearDown() {
    }

    @Test
    public void testFindTenantDevice() {
        DeviceId deviceId = new DeviceId(UUID.randomUUID());
        Device device = new Device();
        device.setId(deviceId);
        device.setTenantId(tenantId);
        device.setDeviceProfileId(loraProfileId);
        device.setName("LoRa-1");
        device.setCreatedTime(42L);
        addOrUpdate(EntityType.DEVICE, device);

        var result = repository.findEntityDataByQuery(tenantId, null, getDeviceTypeQuery("LoRa"), false);

        Assert.assertEquals(1, result.getTotalElements());
        var first = result.getData().get(0);
        Assert.assertEquals(deviceId, first.getEntityId());
        Assert.assertEquals("LoRa-1", first.getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue());
        Assert.assertEquals("42", first.getLatest().get(EntityKeyType.ENTITY_FIELD).get("createdTime").getValue());

        result = repository.findEntityDataByQuery(tenantId, null, getDeviceTypeQuery("Not LoRa"), false);
        Assert.assertEquals(0, result.getTotalElements());

        device.setCustomerId(customerId);
        addOrUpdate(EntityType.DEVICE, device);

        result = repository.findEntityDataByQuery(tenantId, customerId, getDeviceTypeQuery("LoRa"), false);
        Assert.assertEquals(1, result.getTotalElements());
        result = repository.findEntityDataByQuery(tenantId, customerId, getDeviceTypeQuery("default"), false);
        Assert.assertEquals(0, result.getTotalElements());
    }

    @Test
    public void testFindCustomerDevice() {
        DeviceId deviceId = new DeviceId(UUID.randomUUID());
        Device device = new Device();
        device.setId(deviceId);
        device.setTenantId(tenantId);
        device.setName("LoRa-1");
        device.setCreatedTime(42L);
        device.setDeviceProfileId(loraProfileId);

        addOrUpdate(EntityType.DEVICE, device);
        addOrUpdate(new LatestTsKv(deviceId, new BasicTsKvEntry(43, new StringDataEntry("state", "TEST")), 0L));

        var result = repository.findEntityDataByQuery(tenantId, customerId, getDeviceTypeQuery("LoRa"), false);
        Assert.assertEquals(0, result.getTotalElements());

        device.setCustomerId(customerId);
        addOrUpdate(EntityType.DEVICE, device);

        result = repository.findEntityDataByQuery(tenantId, customerId, getDeviceTypeQuery("LoRa"), false);

        Assert.assertEquals(1, result.getTotalElements());
        var first = result.getData().get(0);
        Assert.assertEquals(deviceId, first.getEntityId());
        Assert.assertEquals("LoRa-1", first.getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue());
        Assert.assertEquals("42", first.getLatest().get(EntityKeyType.ENTITY_FIELD).get("createdTime").getValue());
    }

    @Test
    public void testFindDeviceByBooleanAttributeWithMixedTypes() {
        DeviceId device1Id = createLoraDevice("LoRa-1");
        DeviceId device2Id = createLoraDevice("LoRa-2");
        DeviceId device3Id = createLoraDevice("LoRa-3");

        long ts = System.currentTimeMillis();
        addOrUpdate(new AttributeKv(device1Id, AttributeScope.SERVER_SCOPE,
                new BaseAttributeKvEntry(new BooleanDataEntry("active", true), ts), 1L));
        addOrUpdate(new AttributeKv(device2Id, AttributeScope.SERVER_SCOPE,
                new BaseAttributeKvEntry(new BooleanDataEntry("active", false), ts), 1L));
        addOrUpdate(new AttributeKv(device3Id, AttributeScope.SERVER_SCOPE,
                new BaseAttributeKvEntry(new StringDataEntry("active", "true"), ts), 1L));

        KeyFilter activeFilter = new KeyFilter();
        activeFilter.setKey(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, "active"));
        activeFilter.setValueType(EntityKeyValueType.BOOLEAN);
        BooleanFilterPredicate predicate = new BooleanFilterPredicate();
        predicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
        predicate.setValue(FilterPredicateValue.fromBoolean(true));
        activeFilter.setPredicate(predicate);

        var result = repository.countEntitiesByQuery(tenantId, null,
                getDeviceTypeQuery("LoRa", List.of(activeFilter)), false);
        Assert.assertEquals(2, result);
    }

    private DeviceId createLoraDevice(String name) {
        DeviceId deviceId = new DeviceId(UUID.randomUUID());
        Device device = new Device();
        device.setId(deviceId);
        device.setTenantId(tenantId);
        device.setDeviceProfileId(loraProfileId);
        device.setName(name);
        device.setCreatedTime(42L);
        addOrUpdate(EntityType.DEVICE, device);
        return deviceId;
    }

    private static EntityDataQuery getDeviceTypeQuery(String deviceType) {
        return getDeviceTypeQuery(deviceType, null);
    }

    private static EntityDataQuery getDeviceTypeQuery(String deviceType, List<KeyFilter> extraFilters) {
        DeviceTypeFilter filter = new DeviceTypeFilter();
        filter.setDeviceTypes(Collections.singletonList(deviceType));
        var pageLink = new EntityDataPageLink(20, 0, null, new EntityDataSortOrder(new EntityKey(EntityKeyType.TIME_SERIES, "state"), EntityDataSortOrder.Direction.DESC), false);

        var entityFields = Arrays.asList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"), new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"));
        var latestValues = Arrays.asList(new EntityKey(EntityKeyType.TIME_SERIES, "state"));
        KeyFilter nameFilter = new KeyFilter();
        nameFilter.setKey(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
        var predicate = new StringFilterPredicate();
        predicate.setIgnoreCase(false);
        predicate.setOperation(StringFilterPredicate.StringOperation.CONTAINS);
        predicate.setValue(new FilterPredicateValue<>("LoRa-"));
        nameFilter.setPredicate(predicate);
        nameFilter.setValueType(EntityKeyValueType.STRING);

        List<KeyFilter> keyFilters = new ArrayList<>();
        keyFilters.add(nameFilter);
        if (extraFilters != null) {
            keyFilters.addAll(extraFilters);
        }
        return new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
    }

}
