package org.tasks.opentasks

import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.astrid.data.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.*
import org.junit.Test
import org.tasks.SuspendFreeze.Companion.freezeAt
import org.tasks.TestUtilities.withTZ
import org.tasks.caldav.iCalendar.Companion.collapsed
import org.tasks.caldav.iCalendar.Companion.order
import org.tasks.caldav.iCalendar.Companion.parent
import org.tasks.caldav.iCalendar.Companion.snooze
import org.tasks.data.Alarm
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
import org.tasks.data.AlarmDao
import org.tasks.data.TagDao
import org.tasks.data.TagDataDao
import org.tasks.injection.ProductionModule
import org.tasks.makers.CaldavTaskMaker
import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.REMOTE_ID
import org.tasks.makers.CaldavTaskMaker.newCaldavTask
import org.tasks.makers.TagDataMaker.NAME
import org.tasks.makers.TagDataMaker.newTagData
import org.tasks.makers.TagMaker.TAGDATA
import org.tasks.makers.TagMaker.TASK
import org.tasks.makers.TagMaker.newTag
import org.tasks.makers.TaskMaker
import org.tasks.makers.TaskMaker.COLLAPSED
import org.tasks.makers.TaskMaker.ORDER
import org.tasks.makers.TaskMaker.newTask
import org.tasks.time.DateTime
import java.util.*
import javax.inject.Inject

@UninstallModules(ProductionModule::class)
@HiltAndroidTest
class OpenTasksPropertiesTests : OpenTasksTest() {

    @Inject lateinit var tagDataDao: TagDataDao
    @Inject lateinit var tagDao: TagDao
    @Inject lateinit var alarmDao: AlarmDao

    @Test
    fun loadRemoteParentInfo() = runBlocking {
        val (_, list) = withVtodo(SUBTASK)

        synchronizer.sync()

        val task = caldavDao.getTaskByRemoteId(list.uuid!!, "dfede1b0-435b-4bba-9708-2422e781747c")
        assertEquals("7daa4a5c-cc76-4ddf-b4f8-b9d3a9cb00e7", task?.remoteParent)
    }

    @Test
    fun pushParentInfo() = runBlocking {
        val (listId, list) = openTaskDao.insertList()
        val taskId = taskDao.createNew(newTask(with(TaskMaker.PARENT, 594)))

        caldavDao.insert(newCaldavTask(
                with(CALENDAR, list.uuid),
                with(CaldavTaskMaker.TASK, taskId),
                with(REMOTE_ID, "abcd"),
                with(CaldavTaskMaker.REMOTE_PARENT, "1234")
        ))

        synchronizer.sync()

        assertEquals("1234", openTaskDao.getTask(listId, "abcd")?.task?.parent)
    }

    @Test
    fun createNewTags() = runBlocking {
        val (_, list) = withVtodo(TWO_TAGS)

        synchronizer.sync()

        assertEquals(
                setOf("Tag1", "Tag2"),
                caldavDao.getTaskByRemoteId(list.uuid!!, "3076145036806467726")
                        ?.task
                        ?.let { tagDao.getTagsForTask(it) }
                        ?.map { it.name }
                        ?.toSet()
        )
    }

    @Test
    fun matchExistingTag() = runBlocking {
        val (_, list) = withVtodo(ONE_TAG)
        val tag = newTagData(with(NAME, "Tag1"))
        tagDataDao.createNew(tag)

        synchronizer.sync()

        assertEquals(
                listOf(tag),
                caldavDao.getTaskByRemoteId(list.uuid!!, "3076145036806467726")
                        ?.task
                        ?.let { tagDataDao.getTagDataForTask(it)}
        )
    }

    @Test
    fun uploadTags() = runBlocking {
        val (listId, list) = openTaskDao.insertList()
        val task = newTask().apply { taskDao.createNew(this) }
        caldavDao.insert(newCaldavTask(
                with(CALENDAR, list.uuid),
                with(REMOTE_ID, "1234"),
                with(CaldavTaskMaker.TASK, task.id)
        ))
        insertTag(task, "Tag1")
        insertTag(task, "Tag2")

        synchronizer.sync()

        assertEquals(
                setOf("Tag1", "Tag2"),
                openTaskDao.getTask(listId, "1234")?.task?.categories?.toSet()
        )
    }

    @Test
    fun loadOrder() = runBlocking {
        val (_, list) = withVtodo(ONE_TAG)

        synchronizer.sync()

        val task = caldavDao.getTaskByRemoteId(list.uuid!!, "3076145036806467726")!!.task
        assertEquals(633734058L, taskDao.fetch(task)?.order)
    }

    @Test
    fun pushOrder() = runBlocking {
        val (listId, list) = openTaskDao.insertList()
        val task = newTask(with(ORDER, 5678L))
        taskDao.createNew(task)
        caldavDao.insert(newCaldavTask(
                with(CALENDAR, list.uuid),
                with(REMOTE_ID, "1234"),
                with(CaldavTaskMaker.TASK, task.id)
        ))

        synchronizer.sync()

        assertEquals(
                5678L,
                openTaskDao.getTask(listId, "1234")?.task?.order
        )
    }

    @Test
    fun readCollapsedState() = runBlocking {
        val (_, list) = withVtodo(HIDE_SUBTASKS)

        synchronizer.sync()

        val task = caldavDao
                .getTaskByRemoteId(list.uuid!!, "2822976a-b71e-4962-92e4-db7297789c20")
                ?.let { taskDao.fetch(it.task) }
        assertTrue(task!!.isCollapsed)
    }

    @Test
    fun pushCollapsedState() = runBlocking {
        val (listId, list) = openTaskDao.insertList()
        val taskId = taskDao.createNew(newTask(with(COLLAPSED, true)))

        caldavDao.insert(newCaldavTask(
                with(CALENDAR, list.uuid),
                with(CaldavTaskMaker.TASK, taskId),
                with(REMOTE_ID, "abcd")
        ))

        synchronizer.sync()

        assertTrue(openTaskDao.getTask(listId, "abcd")?.task!!.collapsed)
    }

    @Test
    fun removeCollapsedState() = runBlocking {
        val (listId, list) = withVtodo(HIDE_SUBTASKS)

        synchronizer.sync()

        val task = caldavDao.getTaskByRemoteId(list.uuid!!, "2822976a-b71e-4962-92e4-db7297789c20")

        taskDao.setCollapsed(task!!.task, false)

        synchronizer.sync()

        assertFalse(
                openTaskDao
                        .getTask(listId, "2822976a-b71e-4962-92e4-db7297789c20")
                        ?.task
                        !!.collapsed
        )
    }

    @Test
    fun readSnoozeTime() = runBlocking {
        val (_, list) = withVtodo(SNOOZED)

        withTZ(CHICAGO) {
            synchronizer.sync()
        }

        val task = caldavDao
                .getTaskByRemoteId(list.uuid!!, "4CBBC669-70E3-474D-A0A3-0FC42A14A5A5")
                ?.let { taskDao.fetch(it.task) }

        assertEquals(
            listOf(Alarm(task!!.id, 1612972355000, TYPE_SNOOZE).apply { id = 1 }),
            alarmDao.getAlarms(task.id)
        )
    }

    @Test
    fun pushSnoozeTime() = withTZ(CHICAGO) {
        val (listId, list) = openTaskDao.insertList()
        val taskId = taskDao.createNew(newTask())
        alarmDao.insert(Alarm(taskId, DateTime(2021, 2, 4, 13, 30).millis, TYPE_SNOOZE))

        caldavDao.insert(newCaldavTask(
                with(CALENDAR, list.uuid),
                with(CaldavTaskMaker.TASK, taskId),
                with(REMOTE_ID, "abcd")
        ))

        freezeAt(DateTime(2021, 2, 4, 12, 30, 45, 125)) {
            synchronizer.sync()
        }

        assertEquals(1612467000000, openTaskDao.getTask(listId, "abcd")?.task!!.snooze)
    }

    @Test
    fun dontPushLapsedSnoozeTime() = withTZ(CHICAGO) {
        val (listId, list) = openTaskDao.insertList()
        val taskId = taskDao.createNew(newTask())
        alarmDao.insert(Alarm(taskId, DateTime(2021, 2, 4, 13, 30).millis, TYPE_SNOOZE))

        caldavDao.insert(newCaldavTask(
                with(CALENDAR, list.uuid),
                with(CaldavTaskMaker.TASK, taskId),
                with(REMOTE_ID, "abcd")
        ))

        freezeAt(DateTime(2021, 2, 4, 13, 30, 45, 125)) {
            synchronizer.sync()
        }

        assertNull(openTaskDao.getTask(listId, "abcd")?.task!!.snooze)
    }

    @Test
    fun removeSnoozeTime() = runBlocking {
        val (listId, list) = withVtodo(SNOOZED)

        synchronizer.sync()

        val task = caldavDao.getTaskByRemoteId(list.uuid!!, "4CBBC669-70E3-474D-A0A3-0FC42A14A5A5")
            ?: throw IllegalStateException("Missing task")
        val snooze = alarmDao.getSnoozed(listOf(task.task))
        assertEquals(1, snooze.size)
        alarmDao.delete(snooze.first())
        assertTrue(alarmDao.getSnoozed(listOf(task.task)).isEmpty())
        taskDao.touch(task.task)

        synchronizer.sync()

        assertNull(
                openTaskDao
                        .getTask(listId, "4CBBC669-70E3-474D-A0A3-0FC42A14A5A5")
                        ?.task
                !!.snooze
        )
    }

    private suspend fun insertTag(task: Task, name: String) =
            newTagData(with(NAME, name))
                    .apply { tagDataDao.createNew(this) }
                    .let { tagDao.insert(newTag(with(TASK, task), with(TAGDATA, it))) }

    companion object {
        private val CHICAGO = TimeZone.getTimeZone("America/Chicago")

        private val SUBTASK = """
            BEGIN:VCALENDAR
            VERSION:2.0
            PRODID:-//Nextcloud Tasks v0.13.6
            BEGIN:VTODO
            UID:dfede1b0-435b-4bba-9708-2422e781747c
            CREATED:20210128T150333
            LAST-MODIFIED:20210128T150338
            DTSTAMP:20210128T150338
            SUMMARY:Child
            RELATED-TO:7daa4a5c-cc76-4ddf-b4f8-b9d3a9cb00e7
            END:VTODO
            END:VCALENDAR
        """.trimIndent()

        private val ONE_TAG = """
            BEGIN:VCALENDAR
            VERSION:2.0
            PRODID:+//IDN tasks.org//android-110304//EN
            BEGIN:VTODO
            DTSTAMP:20210201T204211Z
            UID:3076145036806467726
            CREATED:20210201T204143Z
            LAST-MODIFIED:20210201T204209Z
            SUMMARY:Tags
            CATEGORIES:Tag1
            X-APPLE-SORT-ORDER:633734058
            END:VTODO
            END:VCALENDAR
        """.trimIndent()

        private val TWO_TAGS = """
            BEGIN:VCALENDAR
            VERSION:2.0
            PRODID:+//IDN tasks.org//android-110304//EN
            BEGIN:VTODO
            DTSTAMP:20210201T204211Z
            UID:3076145036806467726
            CREATED:20210201T204143Z
            LAST-MODIFIED:20210201T204209Z
            SUMMARY:Tags
            CATEGORIES:Tag1,Tag2
            X-APPLE-SORT-ORDER:633734058
            END:VTODO
            END:VCALENDAR
        """.trimIndent()

        private val HIDE_SUBTASKS = """
            BEGIN:VCALENDAR
            VERSION:2.0
            PRODID:-//Nextcloud Tasks v0.13.6
            BEGIN:VTODO
            UID:2822976a-b71e-4962-92e4-db7297789c20
            CREATED:20210209T104536
            LAST-MODIFIED:20210209T104548
            DTSTAMP:20210209T104548
            SUMMARY:Parent
            X-OC-HIDESUBTASKS:1
            END:VTODO
            END:VCALENDAR
        """.trimIndent()

        private val SNOOZED = """
            BEGIN:VCALENDAR
            PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
            VERSION:2.0
            BEGIN:VTODO
            CREATED:20210210T151826Z
            LAST-MODIFIED:20210210T152235Z
            DTSTAMP:20210210T152235Z
            UID:4CBBC669-70E3-474D-A0A3-0FC42A14A5A5
            SUMMARY:Test snooze
            STATUS:NEEDS-ACTION
            X-MOZ-LASTACK:20210210T152235Z
            DTSTART;TZID=America/Chicago:20210210T091900
            DUE;TZID=America/Chicago:20210210T091900
            X-MOZ-SNOOZE-TIME:20210210T155235Z
            X-MOZ-GENERATION:1
            END:VTODO
            END:VCALENDAR
        """.trimIndent()
    }
}