package com.box.sdk;

import org.junit.Assert;
import org.junit.Test;
import org.junit.experimental.categories.Category;

import com.eclipsesource.json.JsonArray;
import com.eclipsesource.json.JsonObject;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

public class MetadataTest {

    @Test
    @Category(UnitTest.class)
    public void testConstructor() {
        Metadata m = new Metadata();
        Assert.assertEquals("{}", m.toString());
    }

    @Test
    @Category(UnitTest.class)
    public void testCopyConstructor() {
        Metadata m1 = new Metadata().add("/foo", "bar");
        Metadata m2 = new Metadata(m1);
        Assert.assertEquals("{\"foo\":\"bar\"}", m2.toString());
    }

    @Test
    @Category(UnitTest.class)
    public void testAdd() {
        Metadata m = new Metadata().add("/foo", "bar");
        JsonArray operations = JsonArray.readFrom(m.getPatch());
        Assert.assertEquals(1, operations.size());
        JsonObject op = operations.get(0).asObject();
        Assert.assertEquals("add", op.get("op").asString());
        Assert.assertEquals("/foo", op.get("path").asString());
        Assert.assertEquals("bar", op.get("value").asString());
    }

    @Test
    @Category(UnitTest.class)
    public void testReplace() {
        Metadata m = new Metadata().replace("/foo", "bar");
        JsonArray operations = JsonArray.readFrom(m.getPatch());
        Assert.assertEquals(1, operations.size());
        JsonObject op = operations.get(0).asObject();
        Assert.assertEquals("replace", op.get("op").asString());
        Assert.assertEquals("/foo", op.get("path").asString());
        Assert.assertEquals("bar", op.get("value").asString());
    }

    @Test
    @Category(UnitTest.class)
    public void testReplaceWithMultiSelect() {
        List<String> valueList = new ArrayList<String>();
        valueList.add("bar");
        valueList.add("qux");
        Metadata m = new Metadata().replace("/foo", valueList);
        JsonArray operations = JsonArray.readFrom(m.getPatch());
        Assert.assertEquals(1, operations.size());
        JsonObject op = operations.get(0).asObject();
        Assert.assertEquals("replace", op.get("op").asString());
        Assert.assertEquals("/foo", op.get("path").asString());
        Assert.assertEquals("[\"bar\",\"qux\"]", op.get("value").toString());
    }

    @Test
    @Category(UnitTest.class)
    public void testTest() {
        Metadata m = new Metadata().test("/foo", "bar");
        JsonArray operations = JsonArray.readFrom(m.getPatch());
        Assert.assertEquals(1, operations.size());
        JsonObject op = operations.get(0).asObject();
        Assert.assertEquals("test", op.get("op").asString());
        Assert.assertEquals("/foo", op.get("path").asString());
        Assert.assertEquals("bar", op.get("value").asString());
    }

    @Test
    @Category(UnitTest.class)
    public void testMultiSelect() {
        String expectedList = "[\"public test 1\",\"public test 2\",\"public test 3\"]";
        List<String> list = new ArrayList<>();
        list.add("public test 1");
        list.add("public test 2");
        list.add("public test 3");
        Metadata m = new Metadata().test("/foo", list);
        JsonArray operations = JsonArray.readFrom(m.getPatch());
        JsonObject op = operations.get(0).asObject();
        Assert.assertEquals("test", op.get("op").asString());
        Assert.assertEquals("/foo", op.get("path").asString());
        Assert.assertEquals(expectedList, op.get("value").toString());
    }

    @Test
    @Category(UnitTest.class)
    public void testRemove() {
        Metadata m = new Metadata().remove("/foo");
        JsonArray operations = JsonArray.readFrom(m.getPatch());
        Assert.assertEquals(1, operations.size());
        JsonObject op = operations.get(0).asObject();
        Assert.assertEquals("remove", op.get("op").asString());
        Assert.assertEquals("/foo", op.get("path").asString());
    }

    @Test
    @Category(UnitTest.class)
    public void testInvalidGet() {
        Metadata m = new Metadata();
        Assert.assertEquals(null, m.getValue("/foo"));
    }

    @Test(expected = IllegalArgumentException.class)
    @Category(UnitTest.class)
    public void testNullPath() {
        new Metadata().add(null, "value");
    }

    @Test(expected = IllegalArgumentException.class)
    @Category(UnitTest.class)
    public void testInvalidPath() {
        new Metadata().add("key", "value");
    }

    @Test
    @Category(UnitTest.class)
    public void testMetaProperties() {
        String json = "{\"$id\":\"123\",\"$type\":\"my type\",\"$parent\":\"456\"}";
        Metadata m = new Metadata(JsonObject.readFrom(json));
        Assert.assertEquals("123", m.getID());
        Assert.assertEquals("my type", m.getTypeName());
        Assert.assertEquals("456", m.getParentID());
    }

    @Test
    @Category(UnitTest.class)
    public void testMissingMetaProperties() {
        Metadata m = new Metadata();
        Assert.assertEquals(null, m.getID());
        Assert.assertEquals(null, m.getTypeName());
        Assert.assertEquals(null, m.getParentID());
    }

    @Test
    @Category(IntegrationTest.class)
    public void testMetadataDates() {
        // Run test with various Date formats.
        this.testMetadataDate("2017-10-10T22:10:00-08:00", 1507702200000L);
        this.testMetadataDate("2017-10-10T22:10:00.000-08:00", 1507702200000L);
        this.testMetadataDate("2017-10-10T22:10:00.123-08:00", 1507702200123L);
        this.testMetadataDate("2017-10-10T22:10:00.100-08:00", 1507702200100L);
    }

    public void testMetadataDate(String dateString, Long dateLong) {

        BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken());

        long timestamp = Calendar.getInstance().getTimeInMillis();
        String templateKey = "date" + timestamp;
        String fieldKey = "testDate";

        List<MetadataTemplate.Field> fields = new ArrayList<MetadataTemplate.Field>();
        MetadataTemplate.Field dateField = new MetadataTemplate.Field();
        dateField.setKey(fieldKey);
        dateField.setType("date");
        dateField.setDisplayName("Date Field");
        fields.add(dateField);

        MetadataTemplate template = MetadataTemplate.createMetadataTemplate(api, "enterprise",
                templateKey, "Date " + timestamp, false, fields);

        Assert.assertEquals("date", template.getFields().get(0).getType());

        Date expectedDateValue = new Date();
        expectedDateValue.setTime(dateLong);

        // Add template to item
        Metadata mdValues = new Metadata();
        mdValues.add("/" + fieldKey, dateString);
        BoxFolder.Info folder = BoxFolder.getRootFolder(api).createFolder("Metadata Test " + timestamp);
        Metadata actualMD = folder.getResource().createMetadata(templateKey, mdValues);

        Assert.assertEquals(templateKey, actualMD.getTemplateName());

        try {
            Assert.assertEquals(expectedDateValue, actualMD.getDate("/" + fieldKey));
        } catch (ParseException ex) {
            Assert.fail("Could not parse date in metadata");
        }
    }

    @Test
    @Category(IntegrationTest.class)
    public void testMetadataPrecisionFloat() {
        final float expectedValueFloat = 1234567890f;
        BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken());

        long timestamp = Calendar.getInstance().getTimeInMillis();
        String templateKey = "precision" + timestamp;
        String fieldKey = "testPrecision";

        List<MetadataTemplate.Field> fields = new ArrayList<MetadataTemplate.Field>();
        MetadataTemplate.Field valueField = new MetadataTemplate.Field();
        valueField.setKey(fieldKey);
        valueField.setType("float");
        valueField.setDisplayName("Value Field");
        fields.add(valueField);

        MetadataTemplate template = MetadataTemplate.createMetadataTemplate(api, "enterprise",
                templateKey, "Precision " + timestamp, false, fields);

        Assert.assertEquals("float", template.getFields().get(0).getType());

        // Add template to item
        Metadata mdValues = new Metadata();
        mdValues.add("/" + fieldKey, expectedValueFloat);
        BoxFolder.Info folder = BoxFolder.getRootFolder(api).createFolder("Metadata Precision Test " + timestamp);
        Metadata actualMD = folder.getResource().createMetadata(templateKey, mdValues);

        Assert.assertEquals(templateKey, actualMD.getTemplateName());

        final double actualValueDouble = actualMD.getFloat("/" + fieldKey);

        // Instead of "hard-coding" the delta to 4.0, let's calculate it and then validate it
        final double delta = actualValueDouble - (double) expectedValueFloat;
        Assert.assertEquals(4.0, delta, 0);

        // Now that we know delta is 4.0, when can use it for this validation
        Assert.assertEquals((double) expectedValueFloat, actualValueDouble, delta);
    }

    @Test
    @Category(IntegrationTest.class)
    public void testMetadataPrecisionDouble() {
        final double valueDouble = 233333333333333340.0;
        BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken());

        long timestamp = Calendar.getInstance().getTimeInMillis();
        String templateKey = "precision" + timestamp;
        String fieldKey = "testPrecision";

        List<MetadataTemplate.Field> fields = new ArrayList<MetadataTemplate.Field>();
        MetadataTemplate.Field valueField = new MetadataTemplate.Field();
        valueField.setKey(fieldKey);
        valueField.setType("float");
        valueField.setDisplayName("Value Field");
        fields.add(valueField);

        MetadataTemplate template = MetadataTemplate.createMetadataTemplate(api, "enterprise",
                templateKey, "Precision " + timestamp, false, fields);

        Assert.assertEquals("float", template.getFields().get(0).getType());

        // Add template to item
        Metadata mdValues = new Metadata();
        mdValues.add("/" + fieldKey, valueDouble);
        BoxFolder.Info folder = BoxFolder.getRootFolder(api).createFolder("Metadata Precision Test " + timestamp);
        Metadata actualMD = folder.getResource().createMetadata(templateKey, mdValues);

        Assert.assertEquals(templateKey, actualMD.getTemplateName());
        Assert.assertEquals(valueDouble, actualMD.getFloat("/" + fieldKey), 0);
    }

    @Test
    @Category(IntegrationTest.class)
    public void testMultiSelectMetadataCRUD() {

        BoxAPIConnection api = new BoxAPIConnection(TestConfig.getAccessToken());

        long timestamp = Calendar.getInstance().getTimeInMillis();
        String templateKey = "multiselect" + timestamp;
        String fieldKey = "testMultiSelect";

        // Create new template with multiselect field
        List<String> fieldOptions = new ArrayList<String>();
        fieldOptions.add("foo");
        fieldOptions.add("bar");
        fieldOptions.add("baz");
        fieldOptions.add("quux");

        List<MetadataTemplate.Field> fields = new ArrayList<MetadataTemplate.Field>();
        MetadataTemplate.Field multiSelectField = new MetadataTemplate.Field();
        multiSelectField.setKey(fieldKey);
        multiSelectField.setType("multiSelect");
        multiSelectField.setDisplayName("MultiSelect Field");
        multiSelectField.setOptions(fieldOptions);
        fields.add(multiSelectField);

        MetadataTemplate template = MetadataTemplate.createMetadataTemplate(api, "enterprise",
                templateKey, "MultiSelect " + timestamp, false, fields);

        Assert.assertEquals("multiSelect", template.getFields().get(0).getType());
        List<String> actualOptions = template.getFields().get(0).getOptions();
        Assert.assertEquals("foo", actualOptions.get(0));
        Assert.assertEquals("bar", actualOptions.get(1));
        Assert.assertEquals("baz", actualOptions.get(2));
        Assert.assertEquals("quux", actualOptions.get(3));

        // Add template to item
        Metadata mdValues = new Metadata();
        List<String> values = new ArrayList<String>();
        values.add("foo");
        values.add("bar");
        mdValues.add("/" + fieldKey, values);
        BoxFolder.Info folder = BoxFolder.getRootFolder(api).createFolder("Metadata Test " + timestamp);
        Metadata actualMD = folder.getResource().createMetadata(templateKey, mdValues);

        Assert.assertEquals(templateKey, actualMD.getTemplateName());
        List<String> multiSelectValues = actualMD.getMultiSelect("/" + fieldKey);
        Assert.assertEquals(2, multiSelectValues.size());
        Assert.assertEquals("foo", multiSelectValues.get(0));
        Assert.assertEquals("bar", multiSelectValues.get(1));

        // Update template with multiselect operations - change existing field and add another multiselect field
        List<MetadataTemplate.FieldOperation> updates = new ArrayList<MetadataTemplate.FieldOperation>();

        MetadataTemplate.Field newOption = new MetadataTemplate.Field();
        newOption.setKey("blargh");
        MetadataTemplate.FieldOperation add = new MetadataTemplate.FieldOperation();
        add.setOp(MetadataTemplate.Operation.addMultiSelectOption);
        add.setFieldKey(fieldKey);
        add.setData(newOption);
        updates.add(add);

        MetadataTemplate.Field updatedField = new MetadataTemplate.Field();
        updatedField.setKey("foooooo");
        MetadataTemplate.FieldOperation edit = new MetadataTemplate.FieldOperation();
        edit.setOp(MetadataTemplate.Operation.editMultiSelectOption);
        edit.setFieldKey(fieldKey);
        edit.setMultiSelectOptionKey("foo");
        edit.setData(updatedField);
        updates.add(edit);

        MetadataTemplate.FieldOperation remove = new MetadataTemplate.FieldOperation();
        remove.setOp(MetadataTemplate.Operation.removeMultiSelectOption);
        remove.setFieldKey(fieldKey);
        remove.setMultiSelectOptionKey("baz");
        updates.add(remove);

        MetadataTemplate.FieldOperation reorder = new MetadataTemplate.FieldOperation();
        reorder.setOp(MetadataTemplate.Operation.reorderMultiSelectOptions);
        reorder.setFieldKey(fieldKey);
        List<String> reorderedFields = new ArrayList<String>();
        reorderedFields.add("quux");
        reorderedFields.add("blargh");
        reorderedFields.add("bar");
        reorderedFields.add("foooooo");
        reorder.setMultiSelectOptionKeys(reorderedFields);
        updates.add(reorder);

        MetadataTemplate.FieldOperation addField = new MetadataTemplate.FieldOperation();
        MetadataTemplate.Field newField = new MetadataTemplate.Field();
        List<String> opts = new ArrayList<String>();
        opts.add("one");
        opts.add("two");
        newField.setKey("otherMultiSelect");
        newField.setDisplayName("Another MultiSelect");
        newField.setType("multiSelect");
        newField.setOptions(opts);
        addField.setOp(MetadataTemplate.Operation.addField);
        addField.setData(newField);
        updates.add(addField);

        MetadataTemplate updatedTemplate = MetadataTemplate.updateMetadataTemplate(api, "enterprise",
                templateKey, updates);

        Assert.assertEquals(2, updatedTemplate.getFields().size());
        for (MetadataTemplate.Field field : updatedTemplate.getFields()) {

            if (field.getKey().equals(fieldKey)) {
                Assert.assertEquals("multiSelect", field.getType());
                actualOptions = field.getOptions();
                Assert.assertEquals("quux", actualOptions.get(0));
                Assert.assertEquals("blargh", actualOptions.get(1));
                Assert.assertEquals("bar", actualOptions.get(2));
                Assert.assertEquals("foooooo", actualOptions.get(3));
            } else if (field.getKey().equals("otherMultiSelect")) {
                Assert.assertEquals("multiSelect", field.getType());
                actualOptions = field.getOptions();
                Assert.assertEquals("one", actualOptions.get(0));
                Assert.assertEquals("two", actualOptions.get(1));
            } else {
                Assert.fail("Incorrect field found on metadata template: " + field.getKey());
            }
        }

        // Update instance multiselect field
        values = new ArrayList<String>();
        values.add("two");
        values.add("one");
        actualMD.add("/otherMultiSelect", values);
        actualMD.test("/" + fieldKey + "/0", "foooooo");
        actualMD.test("/" + fieldKey + "/1", "bar");
        actualMD.remove("/" + fieldKey + "/0");
        actualMD.add("/" + fieldKey + "/-", "blargh");
        Metadata updatedMD = folder.getResource().updateMetadata(actualMD);

        multiSelectValues = updatedMD.getMultiSelect("/" + fieldKey);
        Assert.assertEquals(2, multiSelectValues.size());
        Assert.assertEquals("bar", multiSelectValues.get(0));
        Assert.assertEquals("blargh", multiSelectValues.get(1));
        multiSelectValues = updatedMD.getMultiSelect("/otherMultiSelect");
        Assert.assertEquals(2, multiSelectValues.size());
        Assert.assertEquals("two", multiSelectValues.get(0));
        Assert.assertEquals("one", multiSelectValues.get(1));

        // Delete metadata template and folder
        MetadataTemplate.deleteMetadataTemplate(api, "enterprise", template.getTemplateKey());
        folder.getResource().delete(true);
    }

    @Test
    @Category(UnitTest.class)
    public void testGetValues() {
        // Run test with various Date formats.
        this.getValues("\"2017-10-10T22:10:00-08:00\"", 1507702200000L);
        this.getValues("\"2017-10-10T22:10:00.000-08:00\"", 1507702200000L);
        this.getValues("\"2017-10-10T22:10:00.123-08:00\"", 1507702200123L);
        this.getValues("\"2017-10-10T22:10:00.100-08:00\"", 1507702200100L);
    }

    public void getValues(String dateString, Long dateLong) {

        final String stringValue = "Q1 plans";
        final int intValue = 123456;
        final String[] arrayValue = new String[2];
        arrayValue[0] = "internal";
        arrayValue[1] = "internalEng";
        Date dateValue = new Date();
        dateValue.setTime(dateLong);

        String json = "{\n"
                + "    \"audiences\": [\"internal\", \"internalEng\"],\n"
                + "    \"documentType\": \"" + stringValue + "\",\n"
                + "    \"competitiveDocument\": \"no\",\n"
                + "    \"status\": \"active\",\n"
                + "    \"deadline\": " + dateString + ",\n"
                + "    \"capacity\": " + intValue + ",\n"
                + "    \"currentState\": \"proposal\",\n"
                + "    \"$type\": \"marketingCollateral-d086c908-2498-4d3e-8a1f-01e82bfc2abe\",\n"
                + "    \"$parent\": \"file_5010739061\",\n"
                + "    \"$id\": \"2094c584-68e1-475c-a581-534a4609594e\",\n"
                + "    \"$version\": 0,\n"
                + "    \"$typeVersion\": 0,\n"
                + "    \"$template\": \"marketingCollateral\",\n"
                + "    \"$scope\": \"enterprise_12345\"\n"
                + "}";
        Metadata md = new Metadata(JsonObject.readFrom(json));

        Assert.assertEquals(stringValue, md.getValue("/documentType").asString());
        Assert.assertEquals(stringValue, md.getString("/documentType"));

        Assert.assertEquals(intValue, md.getValue("/capacity").asInt());
        Assert.assertEquals((double) intValue, md.getFloat("/capacity"), 0);

        try {
            Assert.assertEquals(dateValue, md.getDate("/deadline"));
        } catch (ParseException ex) {
            Assert.fail("Could not parse date in metadata");
        }

        for (int i = 0; i < arrayValue.length; i++) {
            Assert.assertEquals(arrayValue[i], md.getValue("/audiences").asArray().get(i).asString());
        }
    }

    @Test
    @Category(UnitTest.class)
    public void getHandlesArrayValue() {

        String json = "{\n"
                + "    \"audiences\": [\"internal\", \"internalEng\"],\n"
                + "    \"$type\": \"marketingCollateral-d086c908-2498-4d3e-8a1f-01e82bfc2abe\",\n"
                + "    \"$parent\": \"file_5010739061\",\n"
                + "    \"$id\": \"2094c584-68e1-475c-a581-534a4609594e\",\n"
                + "    \"$version\": 0,\n"
                + "    \"$typeVersion\": 0,\n"
                + "    \"$template\": \"marketingCollateral\",\n"
                + "    \"$scope\": \"enterprise_12345\"\n"
                + "}";
        Metadata md = new Metadata(JsonObject.readFrom(json));

        String value = md.get("/audiences");
        Assert.assertEquals("[\"internal\",\"internalEng\"]", value);
    }
}