package nerd.tuxmobil.fahrplan.congress.changes

import com.google.common.truth.Truth.assertThat
import info.metadude.android.eventfahrplan.commons.temporal.Duration
import info.metadude.android.eventfahrplan.commons.temporal.Moment
import nerd.tuxmobil.fahrplan.congress.R
import nerd.tuxmobil.fahrplan.congress.changes.ChangeType.CANCELED
import nerd.tuxmobil.fahrplan.congress.changes.ChangeType.CHANGED
import nerd.tuxmobil.fahrplan.congress.changes.ChangeType.NEW
import nerd.tuxmobil.fahrplan.congress.changes.ChangeType.UNCHANGED
import nerd.tuxmobil.fahrplan.congress.commons.DaySeparatorFactory
import nerd.tuxmobil.fahrplan.congress.commons.DaySeparatorProperty
import nerd.tuxmobil.fahrplan.congress.commons.FormattingDelegate
import nerd.tuxmobil.fahrplan.congress.commons.ResourceResolving
import nerd.tuxmobil.fahrplan.congress.commons.VideoRecordingState.Drawable.Available
import nerd.tuxmobil.fahrplan.congress.commons.VideoRecordingState.Drawable.Unavailable
import nerd.tuxmobil.fahrplan.congress.commons.VideoRecordingState.None
import nerd.tuxmobil.fahrplan.congress.models.Session
import nerd.tuxmobil.fahrplan.congress.utils.ContentDescriptionFormatter
import nerd.tuxmobil.fahrplan.congress.utils.SessionPropertiesFormatter
import nerd.tuxmobil.fahrplan.congress.utils.SessionPropertiesFormatting
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.fail
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.threeten.bp.ZoneOffset

private const val SOME_DATE = "8/13/15"
private const val SOME_TIME = "5:15 PM"
private const val SOME_DAY_SEPARATOR_TEXT = "Day 1 - 8/13/2015"

class SessionChangeParametersFactoryTest {

    @Test
    fun `createSessionChangeParameters returns empty list when sessions is empty`() {
        val factory = SessionChangeParametersFactory(
            CompleteResourceResolver,
            createSessionPropertiesFormatter(),
            ContentDescriptionFormatter(mock()),
            createDaySeparatorFactory(),
            FakeFormattingDelegate(),
        )
        val actual = factory.createSessionChangeParameters(emptyList(), useDeviceTimeZone = true)
        assertThat(actual).isEmpty()
    }

    @Test
    fun `createSessionChangeParameters returns UNCHANGED parameter when session is unchanged`() {
        val factory = SessionChangeParametersFactory(
            CompleteResourceResolver,
            createSessionPropertiesFormatter(),
            createContentDescriptionFormatter(),
            createDaySeparatorFactory(),
            FakeFormattingDelegate(),
        )
        val sessions = listOf(createUnchangedSession())
        val actual = factory.createSessionChangeParameters(sessions, useDeviceTimeZone = true)
        val expectedSeparator = SessionChangeParameter.Separator(
            daySeparator = DaySeparatorProperty(
                value = SOME_DAY_SEPARATOR_TEXT,
                contentDescription = "",
            )
        )
        val expectedSessionChange = SessionChangeParameter.SessionChange(
            id = "2342",
            title = SessionChangeProperty(
                value = "Title",
                contentDescription = "Title",
                changeType = UNCHANGED,
            ),
            subtitle = SessionChangeProperty(
                value = "Subtitle",
                contentDescription = "",
                changeType = UNCHANGED,
            ),
            videoRecordingState = SessionChangeProperty(
                value = None,
                contentDescription = "",
                changeType = UNCHANGED,
            ),
            speakerNames = SessionChangeProperty(
                value = "Jane Doe, John Doe",
                contentDescription = "",
                changeType = UNCHANGED,
            ),
            dayText = SessionChangeProperty(
                value = SOME_DATE,
                contentDescription = SOME_DATE,
                changeType = UNCHANGED,
            ),
            startsAt = SessionChangeProperty(
                value = SOME_TIME,
                contentDescription = "",
                changeType = UNCHANGED,
            ),
            duration = SessionChangeProperty(
                value = "30",
                contentDescription = "",
                changeType = UNCHANGED,
            ),
            roomName = SessionChangeProperty(
                value = "Main room",
                contentDescription = "",
                changeType = UNCHANGED,
            ),
            languages = SessionChangeProperty(
                value = "de, en",
                contentDescription = "",
                changeType = UNCHANGED,
            ),
            isCanceled = false,
        )
        assertThat(actual).containsExactly(expectedSeparator, expectedSessionChange)
    }

    @Test
    fun `createSessionChangeParameters returns NEW parameter when session is new`() {
        val factory = SessionChangeParametersFactory(
            CompleteResourceResolver,
            createSessionPropertiesFormatter(),
            createContentDescriptionFormatter(),
            createDaySeparatorFactory(),
            FakeFormattingDelegate(),
        )
        val sessions = listOf(createNewSession())
        val actual = factory.createSessionChangeParameters(sessions, useDeviceTimeZone = true)
        val expectedSeparator = SessionChangeParameter.Separator(
            daySeparator = DaySeparatorProperty(
                value = SOME_DAY_SEPARATOR_TEXT,
                contentDescription = "",
            )
        )
        val expectedSessionChange = SessionChangeParameter.SessionChange(
            id = "2342",
            title = SessionChangeProperty(
                value = "Title",
                contentDescription = "Title",
                changeType = NEW,
            ),
            subtitle = SessionChangeProperty(
                value = "Subtitle",
                contentDescription = "",
                changeType = NEW,
            ),
            videoRecordingState = SessionChangeProperty(
                value = None,
                contentDescription = "",
                changeType = NEW,
            ),
            speakerNames = SessionChangeProperty(
                value = "Jane Doe, John Doe",
                contentDescription = "",
                changeType = NEW,
            ),
            dayText = SessionChangeProperty(
                value = SOME_DATE,
                contentDescription = SOME_DATE,
                changeType = NEW,
            ),
            startsAt = SessionChangeProperty(
                value = SOME_TIME,
                contentDescription = "",
                changeType = NEW,
            ),
            duration = SessionChangeProperty(
                value = "30",
                contentDescription = "",
                changeType = NEW,
            ),
            roomName = SessionChangeProperty(
                value = "Main room",
                contentDescription = "",
                changeType = NEW,
            ),
            languages = SessionChangeProperty(
                value = "de, en",
                contentDescription = "",
                changeType = NEW,
            ),
            isCanceled = false,
        )
        assertThat(actual).containsExactly(expectedSeparator, expectedSessionChange)
    }

    @Test
    fun `createSessionChangeParameters returns CANCELED parameter when session is canceled`() {
        val factory = SessionChangeParametersFactory(
            CompleteResourceResolver,
            createSessionPropertiesFormatter(),
            createContentDescriptionFormatter(),
            createDaySeparatorFactory(),
            FakeFormattingDelegate(),
        )
        val sessions = listOf(createCanceledSession())
        val actual = factory.createSessionChangeParameters(sessions, useDeviceTimeZone = true)
        val expectedSeparator = SessionChangeParameter.Separator(
            daySeparator = DaySeparatorProperty(
                value = SOME_DAY_SEPARATOR_TEXT,
                contentDescription = "",
            )
        )
        val expectedSessionChange = SessionChangeParameter.SessionChange(
            id = "2342",
            title = SessionChangeProperty(
                value = "Title",
                contentDescription = "Title",
                changeType = CANCELED,
            ),
            subtitle = SessionChangeProperty(
                value = "Subtitle",
                contentDescription = "",
                changeType = CANCELED,
            ),
            videoRecordingState = SessionChangeProperty(
                value = None,
                contentDescription = "",
                changeType = CANCELED,
            ),
            speakerNames = SessionChangeProperty(
                value = "Jane Doe, John Doe",
                contentDescription = "",
                changeType = CANCELED,
            ),
            dayText = SessionChangeProperty(
                value = SOME_DATE,
                contentDescription = SOME_DATE,
                changeType = CANCELED,
            ),
            startsAt = SessionChangeProperty(
                value = SOME_TIME,
                contentDescription = "",
                changeType = CANCELED,
            ),
            duration = SessionChangeProperty(
                value = "30",
                contentDescription = "",
                changeType = CANCELED,
            ),
            roomName = SessionChangeProperty(
                value = "Main room",
                contentDescription = "",
                changeType = CANCELED,
            ),
            languages = SessionChangeProperty(
                value = "de, en",
                contentDescription = "",
                changeType = CANCELED,
            ),
            isCanceled = true,
        )
        assertThat(actual).containsExactly(expectedSeparator, expectedSessionChange)
    }

    @Test
    fun `createSessionChangeParameters returns CHANGED parameter when session is changed`() {
        val factory = SessionChangeParametersFactory(
            CompleteResourceResolver,
            createSessionPropertiesFormatter(),
            createContentDescriptionFormatter(),
            createDaySeparatorFactory(),
            FakeFormattingDelegate(),
        )
        val sessions = listOf(createChangedSession())
        val actual = factory.createSessionChangeParameters(sessions, useDeviceTimeZone = true)
        val expectedSeparator = SessionChangeParameter.Separator(
            daySeparator = DaySeparatorProperty(
                value = SOME_DAY_SEPARATOR_TEXT,
                contentDescription = "",
            )
        )
        val expectedSessionChange = SessionChangeParameter.SessionChange(
            id = "2342",
            title = SessionChangeProperty(
                value = "Title",
                contentDescription = "Title",
                changeType = CHANGED,
            ),
            subtitle = SessionChangeProperty(
                value = "Subtitle",
                contentDescription = "",
                changeType = CHANGED,
            ),
            videoRecordingState = SessionChangeProperty(
                value = Available,
                contentDescription = "",
                changeType = CHANGED,
            ),
            speakerNames = SessionChangeProperty(
                value = "Jane Doe, John Doe",
                contentDescription = "",
                changeType = CHANGED,
            ),
            dayText = SessionChangeProperty(
                value = SOME_DATE,
                contentDescription = SOME_DATE,
                changeType = CHANGED,
            ),
            startsAt = SessionChangeProperty(
                value = SOME_TIME,
                contentDescription = "",
                changeType = CHANGED,
            ),
            duration = SessionChangeProperty(
                value = "30",
                contentDescription = "",
                changeType = CHANGED,
            ),
            roomName = SessionChangeProperty(
                value = "Main room",
                contentDescription = "",
                changeType = CHANGED,
            ),
            languages = SessionChangeProperty(
                value = "de, en",
                contentDescription = "",
                changeType = CHANGED,
            ),
            isCanceled = false,
        )
        assertThat(actual).containsExactly(expectedSeparator, expectedSessionChange)
    }

    @Test
    fun `createSessionChangeParameters returns CHANGED parameter with dashes when session is changed and empty`() {
        val factory = SessionChangeParametersFactory(
            CompleteResourceResolver,
            createSessionPropertiesFormatter(),
            createContentDescriptionFormatter(),
            createDaySeparatorFactory(),
            FakeFormattingDelegate(),
        )
        val sessions = listOf(createChangedEmptySession())
        val actual = factory.createSessionChangeParameters(sessions, useDeviceTimeZone = true)
        val expectedSeparator = SessionChangeParameter.Separator(
            daySeparator = DaySeparatorProperty(
                value = SOME_DAY_SEPARATOR_TEXT,
                contentDescription = "",
            )
        )
        val expectedSessionChange = SessionChangeParameter.SessionChange(
            id = "2342",
            title = SessionChangeProperty(
                value = "-",
                contentDescription = "-",
                changeType = CHANGED,
            ),
            subtitle = SessionChangeProperty(
                value = "-",
                contentDescription = "",
                changeType = CHANGED,
            ),
            videoRecordingState = SessionChangeProperty(
                value = Unavailable,
                contentDescription = "",
                changeType = CHANGED,
            ),
            speakerNames = SessionChangeProperty(
                value = "-",
                contentDescription = "",
                changeType = CHANGED,
            ),
            dayText = SessionChangeProperty(
                value = SOME_DATE,
                contentDescription = SOME_DATE,
                changeType = CHANGED,
            ),
            startsAt = SessionChangeProperty(
                value = SOME_TIME,
                contentDescription = "",
                changeType = CHANGED,
            ),
            duration = SessionChangeProperty(
                value = "30",
                contentDescription = "",
                changeType = CHANGED,
            ),
            roomName = SessionChangeProperty(
                value = "",
                contentDescription = "",
                changeType = CHANGED,
            ),
            languages = SessionChangeProperty(
                value = "-",
                contentDescription = "Language information has been removed",
                changeType = CHANGED,
            ),
            isCanceled = false,
        )
        assertThat(actual).containsExactly(expectedSeparator, expectedSessionChange)
    }

    @Test
    fun `createSessionChangeParameters returns day separator when session span two days`() {
        val factory = SessionChangeParametersFactory(
            CompleteResourceResolver,
            createSessionPropertiesFormatter(),
            createContentDescriptionFormatter(),
            createDaySeparatorFactory(),
            FakeFormattingDelegate(),
        )
        val sessions = listOf(
            createUnchangedSession(dayIndex = 1),
            createUnchangedSession(dayIndex = 2),
        )
        val actual = factory.createSessionChangeParameters(sessions, useDeviceTimeZone = true)
        assertThat(actual).hasSize(3)
        val expectedSeparator = SessionChangeParameter.Separator(
            daySeparator = DaySeparatorProperty(
                value = SOME_DAY_SEPARATOR_TEXT,
                contentDescription = "",
            )
        )
        assertThat(actual).contains(expectedSeparator)
    }

}

private fun createUnchangedSession(dayIndex: Int = 1) = Session(
    sessionId = "2342",
    title = "Title",
    subtitle = "Subtitle",
    recordingOptOut = false,
    speakers = listOf("Jane Doe", "John Doe"),
    dateUTC = 1439478900000L,
    duration = Duration.ofMinutes(30),
    roomName = "Main room",
    language = "de, en",
    dayIndex = dayIndex,

    changedTitle = false,
    changedSubtitle = false,
    changedRecordingOptOut = false,
    changedSpeakers = false,
    changedStartTime = false,
    changedRoomName = false,
    changedDuration = false,
    changedLanguage = false,
    changedDayIndex = false,

    changedIsNew = false,
    changedIsCanceled = false,
)

private fun createNewSession() = Session(
    sessionId = "2342",
    title = "Title",
    subtitle = "Subtitle",
    recordingOptOut = false,
    speakers = listOf("Jane Doe", "John Doe"),
    dateUTC = 1439478900000L,
    dayIndex = 1,
    duration = Duration.ofMinutes(30),
    roomName = "Main room",
    language = "de, en",

    changedTitle = false,
    changedSubtitle = false,
    changedRecordingOptOut = false,
    changedSpeakers = false,
    changedStartTime = false,
    changedRoomName = false,
    changedDuration = false,
    changedLanguage = false,
    changedDayIndex = false,

    changedIsNew = true,
    changedIsCanceled = false,
)

private fun createCanceledSession() = Session(
    sessionId = "2342",
    title = "Title",
    subtitle = "Subtitle",
    recordingOptOut = false,
    speakers = listOf("Jane Doe", "John Doe"),
    dateUTC = 1439478900000L,
    dayIndex = 1,
    duration = Duration.ofMinutes(30),
    roomName = "Main room",
    language = "de, en",

    changedTitle = false,
    changedSubtitle = false,
    changedRecordingOptOut = false,
    changedSpeakers = false,
    changedStartTime = false,
    changedRoomName = false,
    changedDuration = false,
    changedLanguage = false,
    changedDayIndex = false,

    changedIsNew = false,
    changedIsCanceled = true,
)

private fun createChangedSession() = Session(
    sessionId = "2342",
    title = "Title",
    subtitle = "Subtitle",
    recordingOptOut = false,
    speakers = listOf("Jane Doe", "John Doe"),
    dateUTC = 1439478900000L,
    dayIndex = 1,
    duration = Duration.ofMinutes(30),
    roomName = "Main room",
    language = "de, en",

    changedTitle = true,
    changedSubtitle = true,
    changedRecordingOptOut = true,
    changedSpeakers = true,
    changedStartTime = true,
    changedRoomName = true,
    changedDuration = true,
    changedLanguage = true,
    changedDayIndex = true,

    changedIsNew = false,
    changedIsCanceled = false,
)

private fun createChangedEmptySession() = Session(
    sessionId = "2342",
    title = "",
    subtitle = "",
    recordingOptOut = true,
    speakers = emptyList(),
    dateUTC = 1439478900000L,
    dayIndex = 1,
    duration = Duration.ofMinutes(30),
    roomName = "",
    language = "",

    changedTitle = true,
    changedSubtitle = true,
    changedRecordingOptOut = true,
    changedSpeakers = true,
    changedStartTime = true,
    changedRoomName = true,
    changedDuration = true,
    changedLanguage = true,
    changedDayIndex = true,

    changedIsNew = false,
    changedIsCanceled = false,
)

private fun createSessionPropertiesFormatter(): SessionPropertiesFormatting {
    return SessionPropertiesFormatter(mock())
}

private fun createContentDescriptionFormatter() = mock<ContentDescriptionFormatter> {
    on { getDurationContentDescription(anyOrNull()) } doReturn ""
    on { getTitleContentDescription(anyOrNull()) } doReturn ""
    on { getSubtitleContentDescription(anyOrNull()) } doReturn ""
    on { getRoomNameContentDescription(anyOrNull()) } doReturn ""
    on { getSpeakersContentDescription(anyOrNull(), anyOrNull()) } doReturn ""
    on { getLanguageContentDescription(anyOrNull()) } doReturn ""
    on { getStartTimeContentDescription(anyOrNull()) } doReturn ""
    on { getStateContentDescription(anyOrNull(), anyOrNull()) } doReturn ""
}

private fun createDaySeparatorFactory() = mock<DaySeparatorFactory> {
    on { createDaySeparatorText(anyOrNull(), anyOrNull(), anyOrNull()) } doReturn SOME_DAY_SEPARATOR_TEXT
    on { createDaySeparatorContentDescription(anyOrNull(), anyOrNull(), anyOrNull()) } doReturn ""
}

private class FakeFormattingDelegate : FormattingDelegate {

    override fun getFormattedTimeShort(
        useDeviceTimeZone: Boolean,
        moment: Moment,
        timeZoneOffset: ZoneOffset?,
    ) = SOME_TIME

    override fun getFormattedDateShort(
        useDeviceTimeZone: Boolean,
        moment: Moment,
        timeZoneOffset: ZoneOffset?,
    ) = SOME_DATE

    override fun getFormattedDateLong(
        useDeviceTimeZone: Boolean,
        moment: Moment,
        timeZoneOffset: ZoneOffset?,
    ) = throw NotImplementedError("Not needed for this test.")

    override fun getFormattedDateTimeShort(
        useDeviceTimeZone: Boolean,
        moment: Moment,
        timeZoneOffset: ZoneOffset?,
    ) = throw NotImplementedError("Not needed for this test.")

    override fun getFormattedDateTimeLong(
        useDeviceTimeZone: Boolean,
        moment: Moment,
        timeZoneOffset: ZoneOffset?,
    ) = throw NotImplementedError("Not needed for this test.")

}

private object CompleteResourceResolver : ResourceResolving {
    override fun getString(id: Int, vararg formatArgs: Any) = when (id) {
        R.string.session_list_item_duration_text -> "30"
        R.string.day_separator -> "Day 1 ..."
        R.string.dash -> "-"
        R.string.session_list_item_language_removed_content_description -> "Language information has been removed"
        else -> fail("Unknown string id : $id")
    }

    override fun getQuantityString(id: Int, quantity: Int, vararg formatArgs: Any): String {
        throw NotImplementedError("Not needed for this test.")
    }
}
