package io.wealthwizards.appsynctesting.utils;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import java.time.Instant;

import static org.junit.jupiter.api.Assertions.assertEquals;

@DisplayName("$util.time")
public class TimeTest {
    @Test
    @DisplayName(".nowISO8601")
    public void shouldImplementNowISO8601() {
        var instant = Instant.ofEpochMilli(1517943695749L);

        try (var mocked = Mockito.mockStatic(Instant.class)) {
            mocked.when(Instant::now).thenReturn(instant);

            var time = new Time();

            var actual = time.nowISO8601();
            var expected = "2018-02-06T19:01:35.749Z";

            assertEquals(expected, actual);
        }
    }

    @Test
    @DisplayName(".nowEpochSeconds")
    public void shouldImplementNowEpochSeconds() {
        var instant = Instant.ofEpochMilli(1517943695749L);

        try (var mocked = Mockito.mockStatic(Instant.class)) {
            mocked.when(Instant::now).thenReturn(instant);

            var time = new Time();

            var actual = time.nowEpochSeconds();
            var expected = 1517943695L;

            assertEquals(expected, actual);
        }
    }

    @Test
    @DisplayName(".nowEpochMilliSeconds")
    public void shouldImplementNowEpochMilliSeconds() {
        var instant = Instant.ofEpochMilli(1517943695749L);

        try (var mocked = Mockito.mockStatic(Instant.class)) {
            mocked.when(Instant::now).thenReturn(instant);

            var time = new Time();

            var actual = time.nowEpochMilliSeconds();
            var expected = 1517943695749L;

            assertEquals(expected, actual);
        }
    }

    @Nested
    @DisplayName(".nowFormatted")
    class NowFormatted {
        @Test
        @DisplayName("should default to utc")
        public void shouldDefaultToUtc() {
            var instant = Instant.ofEpochMilli(1517943695749L);

            try (var mocked = Mockito.mockStatic(Instant.class)) {
                mocked.when(Instant::now).thenReturn(instant);
                mocked.when(() -> Instant.from(instant)).thenReturn(instant);

                var time = new Time();

                var actual = time.nowFormatted("yyyy-MM-dd HH:mm:ssZ");
                var expected = "2018-02-06 19:01:35+0000";

                assertEquals(expected, actual);
            }
        }

        @Test
        @DisplayName("should accept hours offset")
        public void shouldAcceptHoursOffset() {
            var instant = Instant.ofEpochMilli(1517943695749L);

            try (var mocked = Mockito.mockStatic(Instant.class)) {
                mocked.when(Instant::now).thenReturn(instant);
                mocked.when(() -> Instant.from(instant)).thenReturn(instant);

                var time = new Time();

                var actual = time.nowFormatted("yyyy-MM-dd HH:mm:ssZ", "+08:00");
                var expected = "2018-02-07 03:01:35+0800";

                assertEquals(expected, actual);
            }
        }

        @Test
        @DisplayName("should accept location offset")
        public void shouldAcceptLocationOffset() {
            var instant = Instant.ofEpochMilli(1517943695749L);

            try (var mocked = Mockito.mockStatic(Instant.class)) {
                mocked.when(Instant::now).thenReturn(instant);
                mocked.when(() -> Instant.from(instant)).thenReturn(instant);
                mocked.when(
                        () -> Instant.ofEpochSecond(1517943695L, 749000000L)
                ).thenReturn(instant);

                var time = new Time();

                var actual = time.nowFormatted("yyyy-MM-dd HH:mm:ssZ", "Australia/Perth");
                var expected = "2018-02-07 03:01:35+0800";

                assertEquals(expected, actual);
            }
        }
    }

    @Nested
    @DisplayName(".parseFormattedToEpochMilliSeconds")
    class ParseFormattedToEpochMilliSeconds {
        @Test
        @DisplayName("should default to UTC offset")
        public void shouldDefaultToUtc() {
            var time = new Time();

            var actual = time.parseFormattedToEpochMilliSeconds(
                    "2018-02-02 01:19:22+0800",
                    "yyyy-MM-dd HH:mm:ssZ"
            );
            var expected = 1517505562000L;

            assertEquals(expected, actual);
        }

        @Test
        @DisplayName("should handle offset timezone")
        public void shouldHandleTimezone() {
            var time = new Time();

            var actual = time.parseFormattedToEpochMilliSeconds(
                    "2018-02-02 01:19:22",
                    "yyyy-MM-dd HH:mm:ss",
                    "+0800"
            );
            var expected = 1517505562000L;

            assertEquals(expected, actual);
        }
    }

    @Nested
    @DisplayName(".parseISO8601ToEpochMilliSeconds")
    class ParseISO8601ToEpochMilliSeconds {
        @Test
        @DisplayName("should handle UTC")
        public void shouldHandleUtc() {
            var time = new Time();

            var actual = time.parseISO8601ToEpochMilliSeconds("2018-02-06T19:01:35.749Z");
            var expected = 1517943695749L;

            assertEquals(expected, actual);
        }

        @Test
        @DisplayName("should handle offset timezone")
        public void shouldHandleTimezone() {
            var time = new Time();

            var actual = time.parseISO8601ToEpochMilliSeconds("2018-02-01T17:21:05.180+08:00");
            var expected = 1517476865180L;

            assertEquals(expected, actual);
        }
    }

    @Test
    @DisplayName(".epochMilliSecondsToSeconds")
    public void shouldImplementEpochMilliSecondsToSeconds() {
        var time = new Time();

        var actual = time.epochMilliSecondsToSeconds(1517943695758L);
        var expected = 1517943695L;

        assertEquals(expected, actual);
    }

    @Test
    @DisplayName(".epochMilliSecondsToISO8601")
    public void shouldImplementEpochMilliSecondsToISO8601() {
        var time = new Time();

        var actual = time.epochMilliSecondsToISO8601(1517943695758L);
        var expected = "2018-02-06T19:01:35.758Z";

        assertEquals(expected, actual);
    }

    @Nested
    @DisplayName(".epochMilliSecondsToFormatted")
    class EpochMilliSecondsToFormatted {
        @Test
        @DisplayName("should default to UTC timezone")
        public void shouldDefaultToUtc() {
            var time = new Time();

            var actual = time.epochMilliSecondsToFormatted(
                    1517943695758L,
                    "yyyy-MM-dd HH:mm:ssZ"
            );
            var expected = "2018-02-06 19:01:35+0000";

            assertEquals(expected, actual);
        }

        @Test
        @DisplayName("should handle offset timezone")
        public void shouldHandleTimezone() {
            var time = new Time();

            var actual = time.epochMilliSecondsToFormatted(
                    1517943695758L,
                    "yyyy-MM-dd HH:mm:ssZ",
                    "+08:00"
            );
            var expected = "2018-02-07 03:01:35+0800";

            assertEquals(expected, actual);
        }
    }
}
