package com.atlassian.stash.internal.server;

import com.atlassian.bitbucket.annotation.Dao;
import com.atlassian.bitbucket.annotation.DataSet;
import com.atlassian.stash.internal.AbstractDaoTest;
import com.atlassian.stash.internal.DataSets;
import com.google.common.collect.ImmutableList;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;
import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException;
import org.springframework.util.ReflectionUtils;

import javax.annotation.Nullable;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.lang.reflect.Field;
import java.util.List;

import static com.atlassian.stash.internal.server.ApplicationProperty.MAX_VALUE_SIZE;

@DataSet({DataSets.PROPERTIES})
public class JdbcApplicationPropertyDaoTest extends AbstractDaoTest {

    // from dbunit/properties.xml
    private static final ApplicationProperty SAMPLE_PROPERTY =
            new ApplicationProperty(ApplicationProperty.Key.BASE_URL, "http://example.com");

    // test strings
    private final List<String> tests = ImmutableList.<String>builder()
            .add("test") // sanity check
            .add("\u5718\u96c6") // cjk
            .add("\u0627\u062e\u062a\u0628\u0627\u0631") // arabic
            .add("\u0411\u0412\u0413\u0490\u0414\u0402") // cyrillic
            .add("; drop table app_property")
            .build();

    @Dao
    private JdbcApplicationPropertyDao dao;

    @Test
    public void testCreate() {
        for (String s : tests) {
            testCreate(ApplicationProperty.Key.NAME, s);
        }
    }

    @Test
    public void testDeleteById() {
        ApplicationProperty property = dao.getById(ApplicationProperty.Key.BASE_URL);
        assertNotNull(property);

        dao.deleteById(ApplicationProperty.Key.BASE_URL);

        property = dao.getById(ApplicationProperty.Key.BASE_URL);
        assertNull(property);
    }

    @Test
    public void testDeleteByIdWithNonexistentProperty() {
        assertNull(dao.getById(ApplicationProperty.Key.MAIL_HOST_USERNAME));

        dao.deleteById(ApplicationProperty.Key.MAIL_HOST_USERNAME);
    }

    @Test
    public void testUpdate() throws Exception {
        for (String s : tests) {
            testUpdate(SAMPLE_PROPERTY.getKey(), s);
        }
    }

    @Test(expected = IncorrectUpdateSemanticsDataAccessException.class)
    public void testUpdateWhenNotExistingRow() throws Exception {
        testUpdate(ApplicationProperty.Key.NAME, "yo");
    }

    @Test(expected = ValidationException.class)
    public void testCreateWithNullValue() {
        testCreate(ApplicationProperty.Key.NAME, null);
    }

    @Test(expected = ValidationException.class)
    public void testUpdateWithNullValue() {
        testUpdate(SAMPLE_PROPERTY.getKey(), null);
    }

    @Test(expected = ValidationException.class)
    public void testCreateWithBlankValue() {
        testCreate(ApplicationProperty.Key.NAME, "");
    }

    @Test(expected = ValidationException.class)
    public void testUpdateWithBlankValue() {
        testUpdate(SAMPLE_PROPERTY.getKey(), "");
    }

    @Test(expected = ValidationException.class)
    public void testCreateWithOversizedValue() {
        testCreate(ApplicationProperty.Key.NAME, StringUtils.repeat("t", MAX_VALUE_SIZE + 1));
    }

    @Test(expected = ConstraintViolationException.class)
    public void testUpdateWithOversizedValue() {
        testUpdate(SAMPLE_PROPERTY.getKey(), StringUtils.repeat("t", MAX_VALUE_SIZE + 1));
    }

    @Test
    public void testGetBydId() throws Exception {
        ApplicationProperty retrieved = dao.getById(SAMPLE_PROPERTY.getKey());
        assertEquals(SAMPLE_PROPERTY, retrieved);
    }

    @Test
    public void testIsSetup() throws Exception {
        // see dbunit/properties.xml
        assertTrue(dao.isSetup());
    }

    private void testCreate(ApplicationProperty.Key key, @Nullable String value) {
        dao.deleteById(key);

        dao.create(createProperty(key, value));

        ApplicationProperty property = dao.getById(key);
        assertNotNull(property);

        assertEquals(value, property.getValue());
    }

    private void testUpdate(ApplicationProperty.Key key, @Nullable String value) {
        dao.update(createProperty(key, value));
    }

    private ApplicationProperty createProperty(ApplicationProperty.Key key, String value) {
        if (value != null) {
            return new ApplicationProperty(key, value);
        } else {
            // the constructor of ApplicationProperty will prevent us from creating a property with a null value,
            // so to test the JSR-303 validation, use reflection to set the field
            ApplicationProperty p = new ApplicationProperty(key, "will be null");
            Field field = ReflectionUtils.findField(p.getClass(), "value");
            ReflectionUtils.makeAccessible(field);
            ReflectionUtils.setField(field, p, value);
            return p;
        }
    }

}
