package com.tt.honeyDue.ui.screens.task

import com.tt.honeyDue.analytics.AnalyticsEvents
import com.tt.honeyDue.models.BulkCreateTasksRequest
import com.tt.honeyDue.models.BulkCreateTasksResponse
import com.tt.honeyDue.models.TaskCategory
import com.tt.honeyDue.models.TaskResponse
import com.tt.honeyDue.models.TaskTemplate
import com.tt.honeyDue.models.TaskTemplateCategoryGroup
import com.tt.honeyDue.models.TaskTemplatesGroupedResponse
import com.tt.honeyDue.models.TotalSummary
import com.tt.honeyDue.network.ApiResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertIs
import kotlin.test.assertNull
import kotlin.test.assertTrue

/**
 * Unit tests for TaskTemplatesBrowserViewModel covering:
 *  1. Load grouped templates on load()
 *  2. Category filter narrows templates
 *  3. Multi-select toggle add/remove
 *  4. canApply reflects empty selection
 *  5. apply() calls bulkCreateTasks with templateId backlink
 *  6. apply() fires analytics events on success (onboarding vs non-onboarding)
 *  7. apply() surfaces API error and does NOT clear selection
 */
@OptIn(ExperimentalCoroutinesApi::class)
class TaskTemplatesBrowserViewModelTest {

    private val dispatcher = StandardTestDispatcher()

    @BeforeTest
    fun setUp() {
        Dispatchers.setMain(dispatcher)
    }

    @AfterTest
    fun tearDown() {
        Dispatchers.resetMain()
    }

    // ---------- Fixtures ----------

    private val plumbingCat = TaskCategory(id = 1, name = "Plumbing")
    private val hvacCat = TaskCategory(id = 2, name = "HVAC")

    private val template1 = TaskTemplate(
        id = 100,
        title = "Change Water Filter",
        description = "Replace every 6 months.",
        categoryId = 1,
        category = plumbingCat
    )
    private val template2 = TaskTemplate(
        id = 101,
        title = "Flush Water Heater",
        description = "Annual flush.",
        categoryId = 1,
        category = plumbingCat
    )
    private val template3 = TaskTemplate(
        id = 200,
        title = "Replace HVAC Filter",
        description = "Every 3 months.",
        categoryId = 2,
        category = hvacCat
    )

    private val grouped = TaskTemplatesGroupedResponse(
        categories = listOf(
            TaskTemplateCategoryGroup(
                categoryName = "Plumbing",
                categoryId = 1,
                templates = listOf(template1, template2),
                count = 2
            ),
            TaskTemplateCategoryGroup(
                categoryName = "HVAC",
                categoryId = 2,
                templates = listOf(template3),
                count = 1
            )
        ),
        totalCount = 3
    )

    private fun fakeBulkResponse(count: Int) = BulkCreateTasksResponse(
        tasks = emptyList<TaskResponse>(),
        summary = TotalSummary(
            totalResidences = 1,
            totalTasks = count,
            totalPending = count,
            totalOverdue = 0,
            tasksDueNextWeek = 0,
            tasksDueNextMonth = count
        ),
        createdCount = count
    )

    private fun makeViewModel(
        loadResult: ApiResult<TaskTemplatesGroupedResponse> = ApiResult.Success(grouped),
        bulkResult: ApiResult<BulkCreateTasksResponse> = ApiResult.Success(fakeBulkResponse(2)),
        onBulkCall: (BulkCreateTasksRequest) -> Unit = {},
        onAnalytics: (String, Map<String, Any>) -> Unit = { _, _ -> },
        fromOnboarding: Boolean = false
    ) = TaskTemplatesBrowserViewModel(
        residenceId = 42,
        fromOnboarding = fromOnboarding,
        loadTemplates = { loadResult },
        bulkCreate = { request ->
            onBulkCall(request)
            bulkResult
        },
        analytics = onAnalytics
    )

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

    @Test
    fun initialStateIsIdleAndEmpty() {
        val vm = makeViewModel()
        assertIs<ApiResult.Idle>(vm.templatesState.value)
        assertIs<ApiResult.Idle>(vm.applyState.value)
        assertTrue(vm.selectedTemplateIds.value.isEmpty())
        assertNull(vm.selectedCategory.value)
        assertFalse(vm.canApply)
    }

    @Test
    fun loadPopulatesTemplatesStateOnSuccess() = runTest(dispatcher) {
        val vm = makeViewModel()
        vm.load()
        dispatcher.scheduler.advanceUntilIdle()

        val state = vm.templatesState.value
        assertIs<ApiResult.Success<TaskTemplatesGroupedResponse>>(state)
        assertEquals(3, state.data.totalCount)
        assertEquals(listOf("Plumbing", "HVAC"), vm.categoryNames)
    }

    @Test
    fun filteringByCategoryNarrowsTemplates() = runTest(dispatcher) {
        val vm = makeViewModel()
        vm.load()
        dispatcher.scheduler.advanceUntilIdle()

        // No filter: all three visible
        assertEquals(3, vm.filteredTemplates().size)

        vm.selectCategory("Plumbing")
        assertEquals(2, vm.filteredTemplates().size)
        assertTrue(vm.filteredTemplates().all { it.categoryId == 1 })

        vm.selectCategory("HVAC")
        assertEquals(1, vm.filteredTemplates().size)
        assertEquals(200, vm.filteredTemplates().first().id)

        vm.selectCategory(null)
        assertEquals(3, vm.filteredTemplates().size)
    }

    @Test
    fun toggleSelectionAddsAndRemovesIds() = runTest(dispatcher) {
        val vm = makeViewModel()

        vm.toggleSelection(100)
        assertEquals(setOf(100), vm.selectedTemplateIds.value)
        assertTrue(vm.canApply)

        vm.toggleSelection(101)
        assertEquals(setOf(100, 101), vm.selectedTemplateIds.value)

        vm.toggleSelection(100)
        assertEquals(setOf(101), vm.selectedTemplateIds.value)

        vm.toggleSelection(101)
        assertTrue(vm.selectedTemplateIds.value.isEmpty())
        assertFalse(vm.canApply)
    }

    @Test
    fun applyIsNoopWhenSelectionEmpty() = runTest(dispatcher) {
        var bulkCalled = false
        val vm = makeViewModel(onBulkCall = { bulkCalled = true })

        vm.apply()
        dispatcher.scheduler.advanceUntilIdle()

        assertFalse(bulkCalled, "bulkCreate should not be called when selection empty")
        assertIs<ApiResult.Idle>(vm.applyState.value)
    }

    @Test
    fun applyBuildsRequestsWithTemplateIdBacklink() = runTest(dispatcher) {
        var captured: BulkCreateTasksRequest? = null
        val vm = makeViewModel(onBulkCall = { captured = it })

        vm.load()
        dispatcher.scheduler.advanceUntilIdle()
        vm.toggleSelection(100)
        vm.toggleSelection(200)

        vm.apply()
        dispatcher.scheduler.advanceUntilIdle()

        val req = captured ?: error("bulkCreate was not called")
        assertEquals(42, req.residenceId)
        assertEquals(2, req.tasks.size)
        val templateIds = req.tasks.map { it.templateId }.toSet()
        assertEquals(setOf(100, 200), templateIds)

        // Every entry should inherit the residenceId and carry the template title.
        assertTrue(req.tasks.all { it.residenceId == 42 })
        val titles = req.tasks.map { it.title }.toSet()
        assertEquals(setOf("Change Water Filter", "Replace HVAC Filter"), titles)
    }

    @Test
    fun applySuccessSetsApplyStateWithCreatedCount() = runTest(dispatcher) {
        val vm = makeViewModel(
            bulkResult = ApiResult.Success(fakeBulkResponse(2))
        )

        vm.load()
        dispatcher.scheduler.advanceUntilIdle()
        vm.toggleSelection(100)
        vm.toggleSelection(101)

        vm.apply()
        dispatcher.scheduler.advanceUntilIdle()

        val state = vm.applyState.value
        assertIs<ApiResult.Success<Int>>(state)
        assertEquals(2, state.data)
    }

    @Test
    fun applyFiresNonOnboardingAnalyticsWhenNotFromOnboarding() = runTest(dispatcher) {
        val events = mutableListOf<Pair<String, Map<String, Any>>>()
        val vm = makeViewModel(
            bulkResult = ApiResult.Success(fakeBulkResponse(2)),
            onAnalytics = { name, props -> events += name to props },
            fromOnboarding = false
        )

        vm.load()
        dispatcher.scheduler.advanceUntilIdle()
        vm.toggleSelection(100)
        vm.toggleSelection(200)

        vm.apply()
        dispatcher.scheduler.advanceUntilIdle()

        // Per-template accepted events + one bulk_created summary event
        val perTemplate = events.filter {
            it.first == TaskTemplatesBrowserViewModel.EVENT_TASK_TEMPLATE_ACCEPTED
        }
        assertEquals(2, perTemplate.size)
        assertTrue(perTemplate.all { it.second["template_id"] is Int })

        val bulk = events.firstOrNull {
            it.first == TaskTemplatesBrowserViewModel.EVENT_TASK_TEMPLATES_BULK_CREATED
        }
        assertTrue(bulk != null, "expected bulk_created event")
        assertEquals(2, bulk.second["count"])

        // Onboarding events should NOT fire in the non-onboarding path.
        assertTrue(events.none { it.first == AnalyticsEvents.ONBOARDING_TASKS_CREATED })
    }

    @Test
    fun applyFiresOnboardingAnalyticsWhenFromOnboarding() = runTest(dispatcher) {
        val events = mutableListOf<Pair<String, Map<String, Any>>>()
        val vm = makeViewModel(
            bulkResult = ApiResult.Success(fakeBulkResponse(1)),
            onAnalytics = { name, props -> events += name to props },
            fromOnboarding = true
        )

        vm.load()
        dispatcher.scheduler.advanceUntilIdle()
        vm.toggleSelection(100)

        vm.apply()
        dispatcher.scheduler.advanceUntilIdle()

        val accepted = events.firstOrNull {
            it.first == AnalyticsEvents.ONBOARDING_BROWSE_TEMPLATE_ACCEPTED
        }
        assertTrue(accepted != null)
        assertEquals(100, accepted.second["template_id"])

        val created = events.firstOrNull {
            it.first == AnalyticsEvents.ONBOARDING_TASKS_CREATED
        }
        assertTrue(created != null)
        assertEquals(1, created.second["count"])
    }

    @Test
    fun applyErrorSurfacesErrorAndKeepsSelection() = runTest(dispatcher) {
        val vm = makeViewModel(
            bulkResult = ApiResult.Error("Network down", 500),
            fromOnboarding = false
        )
        vm.load()
        dispatcher.scheduler.advanceUntilIdle()
        vm.toggleSelection(100)
        val selectedBefore = vm.selectedTemplateIds.value

        vm.apply()
        dispatcher.scheduler.advanceUntilIdle()

        val state = vm.applyState.value
        assertIs<ApiResult.Error>(state)
        assertEquals("Network down", state.message)
        // Selection retained so the user can retry without re-picking.
        assertEquals(selectedBefore, vm.selectedTemplateIds.value)
    }

    @Test
    fun loadErrorLeavesTemplatesStateError() = runTest(dispatcher) {
        val vm = makeViewModel(
            loadResult = ApiResult.Error("Boom", 500)
        )
        vm.load()
        dispatcher.scheduler.advanceUntilIdle()

        assertIs<ApiResult.Error>(vm.templatesState.value)
        assertTrue(vm.filteredTemplates().isEmpty())
        assertTrue(vm.categoryNames.isEmpty())
    }
}
