package lc.esp.eo;

import lc.esp.eo.sdk.EO;
import lc.esp.eo.sdk.EOAssociation;
import lc.esp.eo.sdk.EOId;
import lc.esp.eo.sdk.schema.AssociationSchema;
import lc.esp.eo.sdk.schema.Model;
import lc.mecha.log.MechaLogger;
import lc.mecha.log.MechaLoggerFactory;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Map;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
 * JUnit tests to exercise the EO system.
 *
 * @author Alex Leigh
 * @since mk20 (TOKYO PROTOCOL)
 */
public class EOTransactionTest {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(EOTransactionTest.class);
    private static final String KEY_LIKES = "likes";
    private static final String KEY_LIKED_BY = "liked_by";

    @Test
    public void testObjCreate() {
        try (EOClient client = new EOClient()) {
            try (EOTransaction txn = client.getTransaction("junit_" + UUID.randomUUID(), null)) {
                try {
                    EOId id = txn.objAdd("test", Map.of("first", "Alex"));
                    EO obj = txn.objGet(id);
                    assert (obj != null);
                    txn.rollback();
                    obj = txn.objGet(id);
                    assert (obj == null);
                } finally {
                    txn.drop();
                }
            }
        }
    }

    @Test
    public void testAssocCreate() throws IOException, InterruptedException {
        try (EOClient client = new EOClient()) {
            try (EOTransaction txn = client.getTransaction("junit_" + UUID.randomUUID(), null)) {
                try {
                    txn.initialize();
                    EOId alexId = txn.objAdd("person", Map.of("first", "Alex"));
                    EOId nikkiId = txn.objAdd("person", Map.of("first", "Nikki"));
                    txn.assocAdd(alexId, nikkiId, "boyfriend");
                    txn.assocAdd(alexId, nikkiId, "girlfriend");
                    txn.commit();
                } finally {
                    txn.drop();
                }
            }
        }
    }

    @Test
    public void testMerge() {
        try (EOClient client = new EOClient()) {
            try (EOTransaction txn = client.getTransaction("junit_" + UUID.randomUUID())) {
                try {
                    txn.initialize();
                    EOId objId = txn.objAdd("test", Map.of("first", "Alex", "last", "Leigh"));
                    long cnt = txn.objUpdate(objId, Map.of("first", "Alexander"), true);
                    assertEquals(1, cnt);
                    EO obj = txn.objGet(objId);
                    assertEquals("Alexander", obj.get("first"));
                    assertEquals("Leigh", obj.get("last"));
                } finally {
                    txn.drop();
                }
            }
        }
    }

    @Test
    public void testUpdate() {
        try (EOClient client = new EOClient()) {
            try (EOTransaction txn = client.getTransaction("junit_" + UUID.randomUUID())) {
                try {
                    txn.initialize();
                    EOId objId = txn.objAdd("test", Map.of("first", "Alex", "last", "Leigh"));
                    long cnt = txn.objUpdate(objId, Map.of("first", "Alexander"), false);
                    assertEquals(1, cnt);
                    EO obj = txn.objGet(objId);
                    assertEquals("Alexander", obj.get("first"));
                    assertEquals(null, obj.get("last"));
                } finally {
                    txn.drop();
                }
            }
        }
    }


    @Test
    public void testObjDelete() {
        Model model = new Model();
        model.putAssociation(new AssociationSchema(KEY_LIKES, KEY_LIKED_BY));
        model.putAssociation(new AssociationSchema(KEY_LIKED_BY, KEY_LIKES));

        try (EOClient client = new EOClient()) {
            try (EOTransaction txn = client.getTransaction("junit_" + UUID.randomUUID(), model)) {
                try {
                    txn.initialize();
                    EOId id1 = txn.objAdd("test", Map.of("first", "Alex"));
                    EOId id2 = txn.objAdd("test", Map.of("first", "Nikki"));

                    txn.assocAdd(id1, id2, KEY_LIKES);

                    long cnt = txn.assocCount(id1, KEY_LIKES);
                    assertEquals(1, cnt);

                    txn.objDelete(id2);

                    cnt = txn.assocCount(id1, KEY_LIKES);
                    assertEquals(0, cnt);

                    EO obj = txn.objGet(id2);
                    assertEquals(null, obj);
                } finally {
                    txn.drop();
                }
            }
        }
    }

    @Test
    public void testMkdir() {
        try (EOClient client = new EOClient()) {
            try (EOTransaction txn = client.getTransaction("junit_" + UUID.randomUUID())) {
                try {
                    txn.initialize();

                    EOId root = txn.fsDirAdd();
                    logger.info("Created root: {}", root);

                    try (EOAssociationCursor cursor = txn.assocGet(root, EOTransaction.KEY_FS_LINK)) {
                        for (EOAssociation link : cursor) {
                            logger.info("Found link: {}", link);
                        }
                    }

                    EOId etc = txn.fsDirAdd(root, "etc");
                    logger.info("Created /etc: {}", etc);

                    try (EOAssociationCursor cursor = txn.assocGet(root, EOTransaction.KEY_FS_LINK)) {
                        for (EOAssociation link : cursor) {
                            logger.info("Found link: {}", link);
                        }
                    }
                } finally {
                    txn.drop();
                }
            }
        }
    }

    @Test
    public void testFilesystem() throws IOException {
        try (EOClient client = new EOClient()) {
            try (EOTransaction txn = client.getTransaction("junit_" + UUID.randomUUID())) {
                try {
                    txn.initialize();

                    // Initialize the filesystem & directories
                    EOId rootInodeId = txn.fsDirAdd();
                    txn.fsDirAdd(rootInodeId, "/var/log/junit");
                    EOId dirId = txn.fsPathTraverse(rootInodeId, Path.of("/var/log/junit"));
                    assert (dirId != null);
                    EO dir = txn.objGet(dirId);
                    logger.info("Final destination: {}", dir);
                    assertEquals(EOTransaction.TYPE_INODE, dir.getType());

                    // Write a file into the root directory
                    try (FileInputStream fis = new FileInputStream("build.gradle")) {
                        EOId fileId = txn.fsFileAdd(rootInodeId, "/build.gradle", fis, "text/plain");
                        logger.info("Created file: {}", fileId);

                        // Try to read the file back
                        ByteArrayOutputStream bos = new ByteArrayOutputStream();
                        txn.fsFileGet(rootInodeId, bos, "/build.gradle");
                        bos.close();
                        logger.info("Read file: {}", bos.size());
                        assert (bos.size() > 0);
                    }

                    try (FileInputStream fis = new FileInputStream("build.gradle")) {
                        EOId fileId = txn.fsFileAdd(rootInodeId, "/var/log/build.gradle", fis, "text/plain");
                        logger.info("Created file: {}", fileId);

                        // Try to read the file back
                        ByteArrayOutputStream bos = new ByteArrayOutputStream();
                        txn.fsFileGet(rootInodeId, bos, "/var/log/build.gradle");
                        bos.close();
                        logger.info("Read file: {}", bos.size());
                        assert (bos.size() > 0);
                    }
                } finally {
                    txn.drop();
                }
            }
        }
    }
}