/*
 * Copyright (C) 2018-2023 Hedera Hashgraph, LLC
 *
 * 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 com.swirlds.jasperdb;

import static org.apache.commons.lang3.RandomUtils.nextInt;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.swirlds.common.crypto.DigestType;
import com.swirlds.common.crypto.Hash;
import com.swirlds.common.io.streams.SerializableDataInputStream;
import com.swirlds.common.io.streams.SerializableDataOutputStream;
import com.swirlds.jasperdb.files.DataItemHeader;
import com.swirlds.virtualmap.datasource.VirtualHashRecord;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class VirtualHashRecordSerializerTest {

    private VirtualHashRecordSerializer subject;

    @BeforeEach
    void setUp() {
        subject = new VirtualHashRecordSerializer();
    }

    @Test
    void deserializeEnforcesCurrentVersion() {
        final ByteBuffer someBuffer = ByteBuffer.allocate(1);
        assertThrows(
                IllegalArgumentException.class,
                () -> subject.deserialize(someBuffer, 123),
                "Should have rejected attempt to deserialize data not using the current version");
    }

    @Test
    void serializeEnforcesDefaultDigest() {
        final SerializableDataOutputStream bbuf = mock(SerializableDataOutputStream.class);
        final Hash nonDefaultHash = new Hash(DigestType.SHA_512);
        final VirtualHashRecord data = new VirtualHashRecord(1L, nonDefaultHash);
        assertEquals(Long.BYTES, subject.getHeaderSize(), "Header size should be 8 bytes");
        assertEquals(
                56, subject.getSerializedSize(), "Serialized size should be 8 bytes for header + 48 bytes for digest");
        assertEquals(1L, subject.getCurrentDataVersion(), "Current version should be 1");

        assertThrows(
                IllegalArgumentException.class,
                () -> subject.serialize(data, bbuf),
                "Should have rejected attempt to serialize data with non-default hash digest");
    }

    @Test
    void deserializeHappyPath() throws IOException {
        final ByteBuffer bb = mock(ByteBuffer.class);
        final Hash validHash = new Hash(DigestType.SHA_384);
        final VirtualHashRecord expectedData = new VirtualHashRecord(42L, validHash);
        when(bb.getLong()).thenReturn(42L);
        when(bb.get(any())).thenReturn(bb);
        final DataItemHeader expectedHeader = new DataItemHeader(56, 42L);
        assertEquals(expectedHeader, subject.deserializeHeader(bb, -1), "Deserialized header should match serialized");
        assertEquals(expectedData, subject.deserialize(bb, 1L), "Deserialized data should match serialized");
    }

    @Test
    void testVersion() {
        assertEquals(1, subject.getVersion(), "Version should be 1");
    }

    @Test
    void testSerializeDeserialize() throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        final SerializableDataOutputStream out = new SerializableDataOutputStream(byteArrayOutputStream);
        // no op
        subject.serialize(out);
        assertEquals(0, byteArrayOutputStream.toByteArray().length, "No data is expected");
        // no op
        subject.deserialize((SerializableDataInputStream) null, nextInt());
    }

    @Test
    void testEquals() {
        final VirtualHashRecordSerializer other = new VirtualHashRecordSerializer();
        assertEquals(subject, other, "Should be equal");
        assertEquals(subject, subject, "Should be equal");
        assertFalse(subject.equals(null), "Should not be equal to null");
        assertFalse(other.equals(new Object()), "Should not be equal to Object");
    }

    @Test
    void testHashCode() {
        VirtualHashRecordSerializer v = new VirtualHashRecordSerializer();
        assertEquals(87, v.hashCode());
    }
}
