package com.increase.api.core

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.exc.MismatchedInputException
import com.fasterxml.jackson.module.kotlin.readValue
import java.time.LocalDate
import java.time.LocalTime
import java.time.OffsetDateTime
import java.time.ZoneOffset
import kotlin.reflect.KClass
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.catchThrowable
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.EnumSource
import org.junitpioneer.jupiter.cartesian.CartesianTest

internal class ObjectMappersTest {

    internal class ClassWithBooleanFieldPrefixedWithIs(private val isActive: JsonField<Boolean>) {

        @JsonProperty("is_active") @ExcludeMissing fun _isActive() = isActive
    }

    @Test
    fun write_whenFieldPrefixedWithIs_keepsPrefix() {
        val value = ClassWithBooleanFieldPrefixedWithIs(JsonField.of(true))

        val json = jsonMapper().writeValueAsString(value)

        assertThat(json).isEqualTo("{\"is_active\":true}")
    }

    internal class Class(@get:JsonProperty("field") @JsonProperty("field") val field: String)

    enum class ShapeTestCase(val value: Any, val kClass: KClass<*>) {
        STRING("Hello World!", String::class),
        BOOLEAN(true, Boolean::class),
        FLOAT(3.14F, Float::class),
        DOUBLE(3.14, Double::class),
        INTEGER(42, Int::class),
        LONG(42L, Long::class),
        MAP(mapOf("property" to "value"), Map::class),
        CLASS(Class("Hello World!"), Class::class),
        LIST(listOf(1, 2, 3), List::class);

        companion object {
            val VALID_CONVERSIONS =
                listOf(
                    FLOAT to DOUBLE,
                    DOUBLE to FLOAT,
                    INTEGER to FLOAT,
                    INTEGER to DOUBLE,
                    INTEGER to LONG,
                    LONG to FLOAT,
                    LONG to DOUBLE,
                    LONG to INTEGER,
                    CLASS to MAP,
                )
        }
    }

    @CartesianTest
    fun read(@CartesianTest.Enum shape1: ShapeTestCase, @CartesianTest.Enum shape2: ShapeTestCase) {
        val jsonMapper = jsonMapper()
        val json = jsonMapper.writeValueAsString(shape1.value)

        val e = catchThrowable { jsonMapper.readValue(json, shape2.kClass.java) }

        if (shape1 == shape2 || shape1 to shape2 in ShapeTestCase.VALID_CONVERSIONS) {
            assertThat(e).isNull()
        } else {
            assertThat(e).isInstanceOf(MismatchedInputException::class.java)
        }
    }

    enum class LenientOffsetDateTimeTestCase(
        val string: String,
        val expectedOffsetDateTime: OffsetDateTime,
    ) {
        DATE(
            "1998-04-21",
            expectedOffsetDateTime =
                OffsetDateTime.of(LocalDate.of(1998, 4, 21), LocalTime.of(0, 0), ZoneOffset.UTC),
        ),
        DATE_TIME(
            "1998-04-21T04:00:00",
            expectedOffsetDateTime =
                OffsetDateTime.of(LocalDate.of(1998, 4, 21), LocalTime.of(4, 0), ZoneOffset.UTC),
        ),
        ZONED_DATE_TIME_1(
            "1998-04-21T04:00:00+03:00",
            expectedOffsetDateTime =
                OffsetDateTime.of(
                    LocalDate.of(1998, 4, 21),
                    LocalTime.of(4, 0),
                    ZoneOffset.ofHours(3),
                ),
        ),
        ZONED_DATE_TIME_2(
            "1998-04-21T04:00:00Z",
            expectedOffsetDateTime =
                OffsetDateTime.of(LocalDate.of(1998, 4, 21), LocalTime.of(4, 0), ZoneOffset.UTC),
        ),
    }

    @ParameterizedTest
    @EnumSource
    fun readOffsetDateTime_lenient(testCase: LenientOffsetDateTimeTestCase) {
        val jsonMapper = jsonMapper()
        val json = jsonMapper.writeValueAsString(testCase.string)

        val offsetDateTime = jsonMapper().readValue<OffsetDateTime>(json)

        assertThat(offsetDateTime).isEqualTo(testCase.expectedOffsetDateTime)
    }
}
