package org.motechproject.mds.json;

import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.stream.JsonWriter;
import org.junit.Assert;
import org.junit.Test;
import org.motechproject.mds.domain.Entity;
import org.motechproject.mds.testutil.EntitySchemaBuilder;

import java.io.IOException;
import java.io.StringWriter;

public class EntityWriterTest {

    public static final String ID = "id";
    public static final String SOME_INT = "someInt";
    public static final String SOME_STR = "someStr";
    public static final String CREATION_DATE = "creationDate";
    public static final String BY_INT = "byInt";
    public static final String SOME_INT_DISPLAY_NAME = "Some Int";
    public static final String SOME_STR_DISPLAY_NAME = "Some Str";
    public static final String MDS_FIELD_STRING = "mds.field.string";
    public static final String MDS_FIELD_INTEGER = "mds.field.integer";
    public static final String MDS_FIELD_VALIDATION_MIN_VALUE = "mds.field.validation.minValue";
    public static final String MDS_FIELD_VALIDATION_MAX_VALUE = "mds.field.validation.maxValue";
    public static final String MDS_FIELD_VALIDATION_MIN_LENGTH = "mds.field.validation.minLength";
    public static final String MDS_FIELD_VALIDATION_MAX_LENGTH = "mds.field.validation.maxLength";
    public static final String ID_DISPLAY_NAME = "Id";
    public static final String MDS_FIELD_LONG = "mds.field.long";
    public static final String CREATION_DATE_DISPLAY_NAME = "Creation Date";
    public static final String MDS_FIELD_DATETIME = "mds.field.datetime";
    public static final String EXTRA_BOOL = "extraBool";
    public static final String EXTRA_STR = "extraStr";
    public static final String BY_CREATION_DATE_AND_EXTRA_STR = "byCreationDateAndExtraStr";
    public static final String BY_EXTRA_BOOL_AND_EXTRA_STR = "byExtraBoolAndExtraStr";
    public static final String BY_CREATION_DATE_AND_EXTRA_STR_METHOD = "byCreationDateAndExtraStrMethod";
    public static final String BY_EXTRA_BOOL_AND_EXTRA_STR_METHOD = "byExtraBoolAndExtraStrMethod";
    public static final String EXTRA_BOOL_DISPLAY_NAME = "Extra Bool";
    public static final String MDS_FIELD_BOOLEAN = "mds.field.boolean";
    public static final String EXTRA_STR_DISPLAY_NAME = "Extra Str";
    public static final String MDS_FIELD_VALIDATION_REGEX = "mds.field.validation.regex";
    public static final String MDS_FORM_LABEL_TEXTAREA = "mds.form.label.textarea";
    public static final String MDS_FORM_LABEL_MAX_TEXT_LENGTH = "mds.form.label.maxTextLength";

    @Test
    public void shouldWriteUnmodifiedDDE() throws IOException {
        Entity dde = buildUnmodifiedDDE();
        String ddeJsonResource = "json/EntityWriterTest_UnmodifiedDDE.json";

        assertEntityWrittenCorrectly(dde, ddeJsonResource);
    }

    @Test
    public void shouldWriteModifiedDDE() throws IOException {
        Entity dde = buildModifiedDDE();
        String ddeJsonResource = "json/EntityWriterTest_ModifiedDDE.json";

        assertEntityWrittenCorrectly(dde, ddeJsonResource);
    }

    @Test
    public void shouldWriteEUDE() throws IOException {
        Entity eude = buildEUDE();
        String eudeJsonResource = "json/EntityWriterTest_EUDE.json";

        assertEntityWrittenCorrectly(eude, eudeJsonResource);
    }

    private void assertEntityWrittenCorrectly(Entity dde, String ddeJsonResource) throws IOException {
        StringWriter stringWriter = new StringWriter();
        JsonWriter jsonWriter = new JsonWriter(stringWriter);
        EntityWriter entityWriter = new EntityWriter(jsonWriter, dde);

        entityWriter.write();
        jsonWriter.flush();

        JsonParser parser = new JsonParser();
        JsonElement actual = parser.parse(stringWriter.toString());
        JsonElement expected = parser.parse(Resources.toString(Resources.getResource(ddeJsonResource), Charsets.UTF_8));

        Assert.assertEquals(expected, actual);
    }

    private Entity buildUnmodifiedDDE() {
        EntitySchemaBuilder builder = EntitySchemaBuilder.dde("test.UnmodifiedTestEntity", "UnmodifiedTestModule", "UnmodifiedTestNamespace");
        buildAutoGeneratedFields(builder);
        buildReadOnlyFields(builder);
        buildReadOnlyLookups(builder);

        builder.browsing()
                .fields(ID, SOME_INT, SOME_STR);

        builder.rest()
                .fields(ID, CREATION_DATE, SOME_STR)
                .lookups(BY_INT)
                .crud(false, true, false, false);

        builder.auditing()
                .recordHistory(true)
                .events(true, false, false);

        return builder.build();
    }

    private Entity buildModifiedDDE() {
        EntitySchemaBuilder builder = EntitySchemaBuilder.dde("test.ModifiedTestEntity", "ModifiedTestModule", "ModifiedTestNamespace");
        buildAutoGeneratedFields(builder);
        buildReadOnlyFields(builder);
        buildExtraFields(builder);
        buildReadOnlyLookups(builder);
        buildExtraLookups(builder);

        builder.browsing()
                .fields(ID, SOME_INT, SOME_STR, EXTRA_BOOL, EXTRA_STR)
                .filters(EXTRA_BOOL);

        builder.rest()
                .fields(ID, SOME_STR, EXTRA_BOOL, EXTRA_STR)
                .lookups(BY_CREATION_DATE_AND_EXTRA_STR, BY_EXTRA_BOOL_AND_EXTRA_STR)
                .crud(true, true, false, true);

        builder.auditing()
                .recordHistory(false)
                .events(true, false, true);

        return builder.build();
    }

    private Entity buildEUDE() {
        EntitySchemaBuilder builder = EntitySchemaBuilder.eude("test.TestEntity");
        buildAutoGeneratedFields(builder);
        buildExtraFields(builder);
        buildExtraLookups(builder);

        builder.browsing()
                .fields(ID, CREATION_DATE, EXTRA_BOOL, EXTRA_STR)
                .filters(CREATION_DATE, EXTRA_BOOL);

        builder.rest()
                .fields(ID, EXTRA_BOOL, EXTRA_STR)
                .lookups(BY_CREATION_DATE_AND_EXTRA_STR)
                .crud(false, true, false, false);

        builder.auditing()
                .recordHistory(false)
                .events(true, false, true);

        return builder.build();
    }

    private void buildExtraLookups(EntitySchemaBuilder builder) {
        builder.lookup(BY_CREATION_DATE_AND_EXTRA_STR)
                .methodName(BY_CREATION_DATE_AND_EXTRA_STR_METHOD)
                .singleObjectReturn(true)
                .fields(CREATION_DATE, EXTRA_STR);

        builder.lookup(BY_EXTRA_BOOL_AND_EXTRA_STR)
                .methodName(BY_EXTRA_BOOL_AND_EXTRA_STR_METHOD)
                .singleObjectReturn(false)
                .fields(EXTRA_BOOL, EXTRA_STR);
    }

    private void buildReadOnlyLookups(EntitySchemaBuilder builder) {
        builder.lookup(BY_INT).readOnly(true).singleObjectReturn(true).fields(SOME_INT);
    }

    private void buildExtraFields(EntitySchemaBuilder builder) {
        builder.field(EXTRA_BOOL, EXTRA_BOOL_DISPLAY_NAME, MDS_FIELD_BOOLEAN)
                .required(true)
                .unique(true)
                .defaultValue("true")
                .tooltip("Cupcake?")
                .placeholder("yes")
                .nonEditable(true)
                .nonDisplayable(true)
                .metadata("superBoolean", "absolutely");

        builder.field(EXTRA_STR, EXTRA_STR_DISPLAY_NAME, MDS_FIELD_STRING)
                .validation(MDS_FIELD_VALIDATION_REGEX, "^([a-z]+|[0-9]+)$", true)
                .setting(MDS_FORM_LABEL_TEXTAREA, "false")
                .setting(MDS_FORM_LABEL_MAX_TEXT_LENGTH, "128");
    }

    private void buildReadOnlyFields(EntitySchemaBuilder builder) {
        builder.field(SOME_INT, SOME_INT_DISPLAY_NAME, MDS_FIELD_INTEGER).readOnly(true)
                .metadata("superInteger", "yes")
                .validation(MDS_FIELD_VALIDATION_MIN_VALUE, "-100", false)
                .validation(MDS_FIELD_VALIDATION_MAX_VALUE, "100", true);

        builder.field(SOME_STR, SOME_STR_DISPLAY_NAME, MDS_FIELD_STRING).readOnly(true)
                .validation(MDS_FIELD_VALIDATION_MIN_LENGTH, "3", true)
                .validation(MDS_FIELD_VALIDATION_MAX_LENGTH, "128", true);
    }

    private void buildAutoGeneratedFields(EntitySchemaBuilder builder) {
        builder.field(ID, ID_DISPLAY_NAME, MDS_FIELD_LONG).autoGenerated();
        builder.field(CREATION_DATE, CREATION_DATE_DISPLAY_NAME, MDS_FIELD_DATETIME).autoGenerated();
    }
}
