package com.tt.honeyDue.ui.screens.residence

import com.tt.honeyDue.analytics.AnalyticsEvents
import com.tt.honeyDue.models.JoinResidenceResponse
import com.tt.honeyDue.models.ResidenceResponse
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

/**
 * State-logic unit tests for [JoinResidenceViewModel] covering the full
 * iOS parity surface of JoinResidenceView.swift:
 *
 * 1. Empty code → canSubmit is false.
 * 2. Code shorter than 6 chars → canSubmit is false.
 * 3. submit() with invalid length sets inline error and does NOT call API.
 * 4. submit() with valid code calls joinWithCode(code) with uppercased value.
 * 5. Successful API result fires `residence_joined` analytics event and
 *    publishes the joined residence id to the navigation callback.
 * 6. API error surfaces inline error message and does NOT trigger navigation.
 * 7. updateCode() coerces input to uppercase and caps at 6 chars, matching
 *    the iOS onChange handler.
 * 8. updateCode() clears a previously set inline error so the user can retry.
 */
@OptIn(ExperimentalCoroutinesApi::class)
class JoinResidenceViewModelTest {

    private val dispatcher = StandardTestDispatcher()

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

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

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

    private fun fakeResidence(id: Int = 42) = ResidenceResponse(
        id = id,
        ownerId = 1,
        name = "Joined Home",
        createdAt = "2026-01-01T00:00:00Z",
        updatedAt = "2026-01-01T00:00:00Z"
    )

    private fun fakeJoinResponse(id: Int = 42) = JoinResidenceResponse(
        message = "Joined",
        residence = fakeResidence(id),
        summary = TotalSummary(totalResidences = 1)
    )

    private fun makeViewModel(
        joinResult: ApiResult<JoinResidenceResponse> = ApiResult.Success(fakeJoinResponse()),
        onJoinCall: (String) -> Unit = {},
        onAnalytics: (String, Map<String, Any>) -> Unit = { _, _ -> }
    ) = JoinResidenceViewModel(
        joinWithCode = { code ->
            onJoinCall(code)
            joinResult
        },
        analytics = onAnalytics
    )

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

    @Test
    fun emptyCodeCannotSubmit() {
        val vm = makeViewModel()
        assertEquals("", vm.code.value)
        assertFalse(vm.canSubmit, "Submit should be disabled for empty code")
        assertNull(vm.errorMessage.value)
    }

    @Test
    fun shortCodeCannotSubmit() {
        val vm = makeViewModel()
        vm.updateCode("ABC")
        assertFalse(vm.canSubmit, "Submit should be disabled for 3-char code")
        vm.updateCode("ABCDE")
        assertFalse(vm.canSubmit, "Submit should be disabled for 5-char code")
    }

    @Test
    fun sixCharCodeCanSubmit() {
        val vm = makeViewModel()
        vm.updateCode("ABC123")
        assertTrue(vm.canSubmit)
    }

    @Test
    fun submitWithInvalidLengthSetsInlineErrorAndSkipsApi() = runTest(dispatcher) {
        var apiCalled = false
        val vm = makeViewModel(onJoinCall = { apiCalled = true })

        vm.updateCode("ABC")
        vm.submit()
        dispatcher.scheduler.advanceUntilIdle()

        assertFalse(apiCalled, "API must NOT be called for invalid length")
        val err = vm.errorMessage.value
        assertTrue(err != null && err.isNotBlank(), "Inline error should be set")
        assertIs<ApiResult.Idle>(vm.submitState.value)
    }

    @Test
    fun submitWithValidCodeCallsJoinWithCodeUppercased() = runTest(dispatcher) {
        var capturedCode: String? = null
        val vm = makeViewModel(onJoinCall = { capturedCode = it })

        // Simulate user typing lowercase — updateCode should uppercase it,
        // but also verify submit sends the canonical uppercase value.
        vm.updateCode("abc123")
        assertEquals("ABC123", vm.code.value)

        vm.submit()
        dispatcher.scheduler.advanceUntilIdle()

        assertEquals("ABC123", capturedCode)
    }

    @Test
    fun successResultTriggersNavigationWithResidenceId() = runTest(dispatcher) {
        val vm = makeViewModel(
            joinResult = ApiResult.Success(fakeJoinResponse(id = 77))
        )
        vm.updateCode("ABC123")
        vm.submit()
        dispatcher.scheduler.advanceUntilIdle()

        val state = vm.submitState.value
        assertIs<ApiResult.Success<Int>>(state)
        assertEquals(77, state.data)
        assertNull(vm.errorMessage.value, "Error should be cleared on success")
    }

    @Test
    fun apiErrorShowsInlineMessageAndDoesNotNavigate() = runTest(dispatcher) {
        val vm = makeViewModel(
            joinResult = ApiResult.Error("Invalid code", 404)
        )
        vm.updateCode("BADBAD")
        vm.submit()
        dispatcher.scheduler.advanceUntilIdle()

        assertIs<ApiResult.Error>(vm.submitState.value)
        assertEquals("Invalid code", vm.errorMessage.value)
        // submitState is Error, NOT Success — so the UI will NOT navigate.
        assertFalse(vm.submitState.value is ApiResult.Success<*>)
    }

    @Test
    fun analyticsEventFiredOnSuccessMatchingIosEventName() = runTest(dispatcher) {
        val events = mutableListOf<Pair<String, Map<String, Any>>>()
        val vm = makeViewModel(
            joinResult = ApiResult.Success(fakeJoinResponse(id = 7)),
            onAnalytics = { name, props -> events += name to props }
        )

        vm.updateCode("ABC123")
        vm.submit()
        dispatcher.scheduler.advanceUntilIdle()

        val joined = events.firstOrNull { it.first == AnalyticsEvents.RESIDENCE_JOINED }
        assertTrue(joined != null, "Expected residence_joined analytics event")
        assertEquals("residence_joined", joined.first)
        assertEquals(7, joined.second["residence_id"])
    }

    @Test
    fun analyticsNotFiredOnApiError() = runTest(dispatcher) {
        val events = mutableListOf<Pair<String, Map<String, Any>>>()
        val vm = makeViewModel(
            joinResult = ApiResult.Error("Nope", 400),
            onAnalytics = { name, props -> events += name to props }
        )
        vm.updateCode("ABC123")
        vm.submit()
        dispatcher.scheduler.advanceUntilIdle()

        assertTrue(events.none { it.first == AnalyticsEvents.RESIDENCE_JOINED })
    }

    @Test
    fun updateCodeUppercasesAndCapsAtSix() {
        val vm = makeViewModel()
        vm.updateCode("abcdefghij")
        assertEquals("ABCDEF", vm.code.value, "Should cap at 6 chars and uppercase")
    }

    @Test
    fun updateCodeClearsPreviousError() = runTest(dispatcher) {
        val vm = makeViewModel(joinResult = ApiResult.Error("Invalid", 400))
        vm.updateCode("BADBAD")
        vm.submit()
        dispatcher.scheduler.advanceUntilIdle()
        assertEquals("Invalid", vm.errorMessage.value)

        // User edits code → error should clear.
        vm.updateCode("GOODCO")
        assertNull(vm.errorMessage.value)
    }
}
