package com.tt.honeyDue.widget

import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.test.core.app.ApplicationProvider
import com.tt.honeyDue.models.TaskCompletionCreateRequest
import com.tt.honeyDue.models.TaskCompletionResponse
import com.tt.honeyDue.network.APILayer
import com.tt.honeyDue.network.ApiResult
import io.mockk.Ordering
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockkObject
import io.mockk.unmockkAll
import kotlinx.coroutines.test.runTest
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
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config

/**
 * Tests for [WidgetActionProcessor].
 *
 * Mirrors iOS WidgetActionProcessor.swift semantics:
 *  - Free tier taps open paywall deep link instead of completing.
 *  - Premium taps perform optimistic mark-pending, API call, refresh-or-rollback.
 *  - Double-taps while a completion is pending are a no-op.
 */
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.TIRAMISU])
class WidgetActionProcessorTest {

    private lateinit var context: Context
    private lateinit var repo: WidgetDataRepository
    private var refreshCalls: Int = 0
    private var lastRefreshContext: Context? = null

    @Before
    fun setUp() = runTest {
        context = ApplicationProvider.getApplicationContext()
        repo = WidgetDataRepository.get(context)
        repo.clearAll()

        refreshCalls = 0
        lastRefreshContext = null
        WidgetActionProcessor.refreshTrigger = { ctx ->
            refreshCalls += 1
            lastRefreshContext = ctx
        }

        mockkObject(APILayer)
    }

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

    private fun successResponse(taskId: Int): TaskCompletionResponse =
        TaskCompletionResponse(
            id = 1,
            taskId = taskId,
            completedBy = null,
            completedAt = "2026-01-01T00:00:00Z",
            notes = "Completed from widget",
            actualCost = null,
            rating = null,
            images = emptyList(),
            createdAt = "2026-01-01T00:00:00Z",
            updatedTask = null
        )

    // ---------- 1. Free tier: paywall only, no API call ----------

    @Test
    fun processComplete_freeTier_opensPaywall_doesNotCallApi() = runTest {
        repo.saveTierState("free")

        val result = WidgetActionProcessor.processComplete(context, taskId = 42L)

        assertEquals(WidgetActionProcessor.Result.FreeTier, result)
        coVerify(exactly = 0) { APILayer.createTaskCompletion(any()) }
        assertEquals("Widget refresh should not fire on free tier", 0, refreshCalls)

        // ACTION_VIEW intent with honeydue://paywall?from=widget was fired.
        val shadowApp = shadowOf(context.applicationContext as android.app.Application)
        val next = shadowApp.nextStartedActivity
        assertNotNull("Expected paywall intent to be started", next)
        assertEquals(Intent.ACTION_VIEW, next.action)
        assertNotNull(next.data)
        assertEquals("honeydue", next.data!!.scheme)
        assertEquals("paywall", next.data!!.host)
        assertEquals("widget", next.data!!.getQueryParameter("from"))
    }

    // ---------- 2. Premium success: mark pending → API → clear pending ----------

    @Test
    fun processComplete_premium_marksPendingThenCompletes() = runTest {
        repo.saveTierState("premium")
        repo.saveTasks(listOf(fakeTask(id = 7L)))

        coEvery { APILayer.createTaskCompletion(any()) } coAnswers {
            // At the instant the API is hit, the task MUST be in the pending set.
            assertTrue(
                "Task should be marked pending before API call",
                repo.isPendingCompletion(7L)
            )
            ApiResult.Success(successResponse(7))
        }

        val result = WidgetActionProcessor.processComplete(context, taskId = 7L)

        assertEquals(WidgetActionProcessor.Result.Success, result)
        coVerify(exactly = 1) {
            APILayer.createTaskCompletion(match<TaskCompletionCreateRequest> {
                it.taskId == 7 && it.notes == "Completed from widget"
            })
        }
        assertFalse(
            "Pending should be cleared after successful API call",
            repo.isPendingCompletion(7L)
        )
    }

    // ---------- 3. Premium API failure: rollback pending ----------

    @Test
    fun processComplete_premium_apiFailure_clearsPending() = runTest {
        repo.saveTierState("premium")
        repo.saveTasks(listOf(fakeTask(id = 11L)))
        coEvery { APILayer.createTaskCompletion(any()) } returns
            ApiResult.Error("Server exploded", 500)

        val result = WidgetActionProcessor.processComplete(context, taskId = 11L)

        assertTrue(
            "Expected Failed result but got $result",
            result is WidgetActionProcessor.Result.Failed
        )
        assertFalse(
            "Pending must be cleared on failure so the task reappears in widget",
            repo.isPendingCompletion(11L)
        )
        assertEquals("No widget refresh on failure", 0, refreshCalls)
    }

    // ---------- 4. Idempotent: duplicate taps are no-ops ----------

    @Test
    fun processComplete_idempotent() = runTest {
        repo.saveTierState("premium")
        repo.saveTasks(listOf(fakeTask(id = 99L)))
        // Seed the pending set — simulates a tap still in flight.
        repo.markPendingCompletion(99L)

        val result = WidgetActionProcessor.processComplete(context, taskId = 99L)

        assertEquals(WidgetActionProcessor.Result.AlreadyPending, result)
        coVerify(exactly = 0) { APILayer.createTaskCompletion(any()) }
        assertEquals(0, refreshCalls)
    }

    // ---------- 5. Premium success triggers widget refresh ----------

    @Test
    fun processComplete_premium_success_triggersWidgetRefresh() = runTest {
        repo.saveTierState("premium")
        repo.saveTasks(listOf(fakeTask(id = 5L)))
        coEvery { APILayer.createTaskCompletion(any()) } returns
            ApiResult.Success(successResponse(5))

        val result = WidgetActionProcessor.processComplete(context, taskId = 5L)

        assertEquals(WidgetActionProcessor.Result.Success, result)
        assertEquals("forceRefresh should fire exactly once on success", 1, refreshCalls)
        assertNotNull(lastRefreshContext)
    }

    // ---------- 6. Order of operations: API before refresh ----------

    @Test
    fun processComplete_premium_ordersOperations_apiBeforeRefresh() = runTest {
        repo.saveTierState("premium")
        repo.saveTasks(listOf(fakeTask(id = 3L)))
        var apiCalledAt: Int = -1
        var refreshCalledAt: Int = -1
        var tick = 0
        coEvery { APILayer.createTaskCompletion(any()) } coAnswers {
            apiCalledAt = tick++
            ApiResult.Success(successResponse(3))
        }
        WidgetActionProcessor.refreshTrigger = {
            refreshCalledAt = tick++
        }

        WidgetActionProcessor.processComplete(context, taskId = 3L)

        assertTrue("API must fire before refresh", apiCalledAt >= 0)
        assertTrue("refresh must fire before or after API but both must run", refreshCalledAt >= 0)
        assertTrue(
            "API should be ordered before refresh ($apiCalledAt < $refreshCalledAt)",
            apiCalledAt < refreshCalledAt
        )
    }

    // ---------- 7. Missing tier defaults to free ----------

    @Test
    fun processComplete_missingTier_treatedAsFree() = runTest {
        // No saveTierState call — repo defaults to "free".

        val result = WidgetActionProcessor.processComplete(context, taskId = 1L)

        assertEquals(WidgetActionProcessor.Result.FreeTier, result)
        coVerify(exactly = 0) { APILayer.createTaskCompletion(any()) }
    }

    // ---------- 8. Paywall intent carries NEW_TASK flag so it can start from app context ----------

    @Test
    fun processComplete_freeTier_paywallIntentIsStartable() = runTest {
        repo.saveTierState("free")

        WidgetActionProcessor.processComplete(context, taskId = 77L)

        val shadowApp = shadowOf(context.applicationContext as android.app.Application)
        val next = shadowApp.nextStartedActivity
        assertNotNull(next)
        // Must have NEW_TASK so it can be launched outside an Activity context
        // (the callback fires from a broadcast-adjacent context).
        assertTrue(
            "Paywall intent should include FLAG_ACTIVITY_NEW_TASK",
            (next.flags and Intent.FLAG_ACTIVITY_NEW_TASK) != 0
        )
    }

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

    private fun fakeTask(id: Long): WidgetTaskDto = WidgetTaskDto(
        id = id,
        title = "Task $id",
        priority = 2L,
        dueDate = null,
        isOverdue = false,
        daysUntilDue = 1,
        residenceId = 1L,
        residenceName = "Home",
        categoryIcon = "house.fill",
        completed = false
    )
}
