package com.tt.honeyDue.widget

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import kotlinx.coroutines.test.runTest
import kotlinx.datetime.Clock
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.TimeZone
import kotlinx.datetime.plus
import kotlinx.datetime.toLocalDateTime
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

/**
 * Unit tests for [WidgetDataRepository].
 *
 * Mirrors iOS WidgetDataManager.swift semantics:
 *  - Save/load tasks as JSON
 *  - Pending-completion tracking (optimistic UI while server sync in flight)
 *  - Stats computation: overdueCount / dueWithin7 / dueWithin8To30
 *  - Tier state persistence ("free" vs "premium")
 */
@RunWith(RobolectricTestRunner::class)
class WidgetDataRepositoryTest {

    private lateinit var context: Context
    private lateinit var repo: WidgetDataRepository

    @Before
    fun setUp() = runTest {
        context = ApplicationProvider.getApplicationContext()
        repo = WidgetDataRepository.get(context)
        // Ensure a clean slate between tests — DataStore singletons persist per-process.
        repo.clearAll()
    }

    @After
    fun tearDown() = runTest {
        repo.clearAll()
    }

    // ---------- Helpers ----------

    /** Produce an ISO-8601 date string N days from now (at midnight local). */
    private fun isoDateDaysFromNow(days: Int): String {
        val now = Clock.System.now()
        val future = now.plus(days, DateTimeUnit.DAY, TimeZone.currentSystemDefault())
        val local = future.toLocalDateTime(TimeZone.currentSystemDefault())
        // YYYY-MM-DD
        val month = local.monthNumber.toString().padStart(2, '0')
        val day = local.dayOfMonth.toString().padStart(2, '0')
        return "${local.year}-$month-$day"
    }

    private fun task(
        id: Long,
        title: String = "Task $id",
        dueInDays: Int? = null,
        isOverdue: Boolean = false,
        residenceId: Long = 1L,
        residenceName: String = "Home",
        categoryIcon: String = "house.fill",
        priority: Long = 2L,
        completed: Boolean = false
    ): WidgetTaskDto {
        val dueDate = dueInDays?.let { isoDateDaysFromNow(it) }
        val daysUntilDue = dueInDays ?: 0
        return WidgetTaskDto(
            id = id,
            title = title,
            priority = priority,
            dueDate = dueDate,
            isOverdue = isOverdue,
            daysUntilDue = daysUntilDue,
            residenceId = residenceId,
            residenceName = residenceName,
            categoryIcon = categoryIcon,
            completed = completed
        )
    }

    // ---------- Tests ----------

    @Test
    fun saveTasks_then_loadTasks_roundTrip() = runTest {
        val input = listOf(
            task(id = 1L, title = "Change air filter", dueInDays = 3),
            task(id = 2L, title = "Clean gutters", dueInDays = 14),
            task(id = 3L, title = "Check smoke detector", isOverdue = true, dueInDays = -2)
        )

        repo.saveTasks(input)
        val output = repo.loadTasks()

        assertEquals(3, output.size)
        val byId = output.associateBy { it.id }
        assertEquals("Change air filter", byId[1L]?.title)
        assertEquals("Clean gutters", byId[2L]?.title)
        assertEquals("Check smoke detector", byId[3L]?.title)
        assertEquals(true, byId[3L]?.isOverdue)
        assertEquals(1L, byId[1L]?.residenceId)
        assertEquals("Home", byId[1L]?.residenceName)
        assertEquals("house.fill", byId[1L]?.categoryIcon)
        assertNotNull(byId[1L]?.dueDate)
    }

    @Test
    fun empty_initial_state_returns_empty_list() = runTest {
        val loaded = repo.loadTasks()
        assertTrue("Expected empty list from fresh store", loaded.isEmpty())
    }

    @Test
    fun markPendingCompletion_excludes_task_from_loadTasks() = runTest {
        val input = listOf(
            task(id = 10L, dueInDays = 2),
            task(id = 20L, dueInDays = 5),
            task(id = 30L, dueInDays = 9)
        )
        repo.saveTasks(input)

        repo.markPendingCompletion(20L)
        val loaded = repo.loadTasks()

        assertEquals(2, loaded.size)
        assertFalse(loaded.any { it.id == 20L })
        assertTrue(loaded.any { it.id == 10L })
        assertTrue(loaded.any { it.id == 30L })
    }

    @Test
    fun clearPendingCompletion_restores_task_to_loadTasks() = runTest {
        val input = listOf(
            task(id = 100L, dueInDays = 1),
            task(id = 200L, dueInDays = 4)
        )
        repo.saveTasks(input)

        repo.markPendingCompletion(200L)
        assertEquals(1, repo.loadTasks().size)

        repo.clearPendingCompletion(200L)
        val restored = repo.loadTasks()
        assertEquals(2, restored.size)
        assertTrue(restored.any { it.id == 200L })
    }

    @Test
    fun computeStats_overdueCount() = runTest {
        val input = listOf(
            task(id = 1L, isOverdue = true, dueInDays = -1),
            task(id = 2L, isOverdue = true, dueInDays = -5),
            task(id = 3L, dueInDays = 3)
        )
        repo.saveTasks(input)

        val stats = repo.computeStats()
        assertEquals(2, stats.overdueCount)
    }

    @Test
    fun computeStats_dueWithin7() = runTest {
        val input = listOf(
            task(id = 1L, dueInDays = 1),
            task(id = 2L, dueInDays = 3),
            task(id = 3L, dueInDays = 6)
        )
        repo.saveTasks(input)

        val stats = repo.computeStats()
        assertEquals(3, stats.dueWithin7)
    }

    @Test
    fun computeStats_dueWithin8To30() = runTest {
        val input = listOf(
            task(id = 1L, dueInDays = 10),
            task(id = 2L, dueInDays = 25)
        )
        repo.saveTasks(input)

        val stats = repo.computeStats()
        assertEquals(2, stats.dueWithin8To30)
    }

    @Test
    fun computeStats_boundaries() = runTest {
        val input = listOf(
            task(id = 1L, dueInDays = 7),  // inclusive upper bound of 7-day window
            task(id = 2L, dueInDays = 8),  // inclusive lower bound of 8-30 window
            task(id = 3L, dueInDays = 30), // inclusive upper bound of 8-30 window
            task(id = 4L, dueInDays = 31)  // outside both windows
        )
        repo.saveTasks(input)

        val stats = repo.computeStats()
        assertEquals("7-day task should count in dueWithin7", 1, stats.dueWithin7)
        assertEquals("8-day and 30-day tasks should count in dueWithin8To30", 2, stats.dueWithin8To30)
        assertEquals("No overdue tasks in this set", 0, stats.overdueCount)
    }

    @Test
    fun saveTierState_loadTierState_roundTrip() = runTest {
        // Default should be "free".
        assertEquals("free", repo.loadTierState())

        repo.saveTierState("premium")
        assertEquals("premium", repo.loadTierState())

        repo.saveTierState("free")
        assertEquals("free", repo.loadTierState())
    }

    @Test
    fun overwrite_tasks_replaces_not_appends() = runTest {
        val first = listOf(
            task(id = 1L, dueInDays = 1),
            task(id = 2L, dueInDays = 2),
            task(id = 3L, dueInDays = 3)
        )
        repo.saveTasks(first)
        assertEquals(3, repo.loadTasks().size)

        val second = listOf(
            task(id = 4L, dueInDays = 4),
            task(id = 5L, dueInDays = 5)
        )
        repo.saveTasks(second)

        val loaded = repo.loadTasks()
        assertEquals(2, loaded.size)
        assertFalse("Previous tasks should be gone after overwrite", loaded.any { it.id == 1L })
        assertTrue(loaded.any { it.id == 4L })
        assertTrue(loaded.any { it.id == 5L })
    }

    @Test
    fun saveTasks_empty_list_clears_previous() = runTest {
        repo.saveTasks(listOf(task(id = 1L, dueInDays = 1), task(id = 2L, dueInDays = 2)))
        assertEquals(2, repo.loadTasks().size)

        repo.saveTasks(emptyList())
        assertTrue(repo.loadTasks().isEmpty())
    }

    @Test
    fun multiple_pending_completions_all_excluded() = runTest {
        val input = listOf(
            task(id = 1L, dueInDays = 1),
            task(id = 2L, dueInDays = 2),
            task(id = 3L, dueInDays = 3),
            task(id = 4L, dueInDays = 4)
        )
        repo.saveTasks(input)

        repo.markPendingCompletion(2L)
        repo.markPendingCompletion(4L)

        val loaded = repo.loadTasks()
        assertEquals(2, loaded.size)
        assertTrue(loaded.any { it.id == 1L })
        assertTrue(loaded.any { it.id == 3L })
        assertFalse(loaded.any { it.id == 2L })
        assertFalse(loaded.any { it.id == 4L })
    }
}
