
package org.apache.camel.component.salesforce.api.utils;

import java.io.IOException;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.OffsetTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Date;
import java.util.Objects;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;

public class SalesforceTimeFormatsTest {

    public static class DateTransferObject<T> {

        private T value;

        public DateTransferObject() {
        }

        public DateTransferObject(final T value) {
            this.value = value;
        }

        @Override
        public boolean equals(final Object obj) {
            if (obj == this) {
                return true;
            }

            if (!(obj instanceof DateTransferObject)) {
                return false;
            }

            final DateTransferObject<?> dto = (DateTransferObject<?>) obj;

            return Objects.equals(value, dto.value);
        }

        public T getValue() {
            return value;
        }

        @Override
        public int hashCode() {
            return Objects.hash(value, value);
        }

        public void setValue(final T value) {
            this.value = value;
        }

        @Override
        public String toString() {
            return String.valueOf(value);
        }
    }

    private static final String JSON_FMT = "{\"value\":\"%s\"}";

    private static final String XML_FMT = "<Dto><value>%s</value></Dto>";

    private final ObjectMapper objectMapper = JsonUtils.createObjectMapper();

    @ParameterizedTest
    @MethodSource("cases")
    public void shouldDeserializeJson(DateTransferObject<?> dto, String json, Class<?> parameterType)
            throws IOException {
        final JavaType javaType
                = TypeFactory.defaultInstance().constructParametricType(DateTransferObject.class, parameterType);

        final DateTransferObject<?> deserialized = objectMapper.readerFor(javaType).readValue(json);

        assertDeserializationResult(dto, deserialized);
    }

    @ParameterizedTest
    @MethodSource("cases")
    public void shouldSerializeJson(DateTransferObject<?> dto, String json, Class<?> parameterType)
            throws JsonProcessingException {
        String actual = objectMapper.writeValueAsString(dto).replaceAll("000\\+00:00", "000+0000");
        String expected = json;
        assertThat(actual).isEqualTo(expected);
    }

    private void assertDeserializationResult(DateTransferObject<?> dto, final DateTransferObject<?> deserialized) {
        if (dto.value instanceof ZonedDateTime) {
            // Salesforce expresses time in UTC+offset (ISO-8601 , with this we
            // loose time zone information
            final ZonedDateTime dtoValue = (ZonedDateTime) dto.value;
            final ZonedDateTime deserializedValue = (ZonedDateTime) deserialized.value;
            String actual
                    = deserializedValue.format(DateTimeFormatter.ISO_INSTANT).replaceAll("000\\+00:00", "000+0000");
            String expected = dtoValue.format(DateTimeFormatter.ISO_INSTANT);

            assertThat(actual).isEqualTo(expected);
        } else {
            assertThat(deserialized.value).isEqualTo(dto.value);
        }
    }

    public static Iterable<Object[]> cases() {
        final LocalDate localDate = LocalDate.of(2007, 03, 19);
        final ZonedDateTime zonedDateTime = ZonedDateTime.of(localDate.atTime(10, 54, 57), ZoneId.of("Z"));
        final Instant instant = zonedDateTime.toInstant();

        final String zone = DateTimeFormatter.ofPattern("XX").format(zonedDateTime.withZoneSameLocal(ZoneId.systemDefault()));

        return Arrays.asList(//
                dto(Date.from(instant), "2007-03-19T10:54:57.000+0000"), // 0
                dto(Date.from(localDate.atStartOfDay().toInstant(ZoneOffset.UTC)), "2007-03-19T00:00:00.000+0000"), // 1
                dto(localDate, "2007-03-19"), // 2
                dto(zonedDateTime.toLocalDateTime(), "2007-03-19T10:54:57.000" + zone), // 3
                dto(zonedDateTime.toOffsetDateTime(), "2007-03-19T10:54:57.000Z"), // 4
                dto(zonedDateTime.toOffsetDateTime(), "2007-03-19T10:54:57.000Z"), // 5
                dto(zonedDateTime.toOffsetDateTime().withOffsetSameInstant(ZoneOffset.of("-7")),
                        "2007-03-19T03:54:57.000-0700"), // 6
                dto(zonedDateTime, "2007-03-19T10:54:57.000Z"), // 7
                dto(zonedDateTime.withZoneSameInstant(ZoneId.of("Asia/Kolkata")), "2007-03-19T16:24:57.000+0530"), // 8
                dto(zonedDateTime.withZoneSameInstant(ZoneId.of("+3")), "2007-03-19T13:54:57.000+0300"), // 9
                dto(instant,
                        instant.atZone(ZoneId.systemDefault())
                                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXX"))), // 10
                dto(ZonedDateTime.of(2018, 03, 22, 9, 58, 8, 5000000, ZoneId.of("Z")), "2018-03-22T09:58:08.005Z"), // 11
                dto(OffsetTime.of(LocalTime.MIDNIGHT, ZoneOffset.UTC), "00:00:00.000Z"), // 12
                dto(OffsetTime.of(12, 13, 14, 7000000, ZoneOffset.UTC), "12:13:14.007Z") // 13
        );
    }

    private static Object[] dto(final Object value, final String serialized) {
        final DateTransferObject<?> dto = new DateTransferObject<>(value);

        final String json = format(JSON_FMT, serialized);

        return new Object[] { dto, json, value.getClass() };
    }
}
