package com.tt.honeyDue.widget

import android.content.Context
import androidx.glance.appwidget.GlanceAppWidgetManager
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.tt.honeyDue.network.APILayer
import com.tt.honeyDue.network.ApiResult
import com.tt.honeyDue.models.TaskColumnsResponse

/**
 * Abstraction over the data sources the worker consumes. Keeps
 * [WidgetRefreshWorker] unit-testable without having to mock the
 * [APILayer] singleton.
 */
interface WidgetRefreshDataSource {
    /** Fetch the task list that should be displayed on the widget. */
    suspend fun fetchTasks(): ApiResult<List<WidgetTaskDto>>
    /** Fetch the current user's subscription tier ("free" | "premium"). */
    suspend fun fetchTier(): String
}

/**
 * Default production data source — delegates to [APILayer] and maps the
 * backend task kanban into the flat list the widget caches.
 */
internal object DefaultWidgetRefreshDataSource : WidgetRefreshDataSource {

    private const val COMPLETED_COLUMN = "completed"
    private const val CANCELLED_COLUMN = "cancelled"
    private const val OVERDUE_COLUMN = "overdue"

    override suspend fun fetchTasks(): ApiResult<List<WidgetTaskDto>> {
        val result = APILayer.getTasks(forceRefresh = true)
        return when (result) {
            is ApiResult.Success -> ApiResult.Success(mapToWidgetTasks(result.data))
            is ApiResult.Error -> result
            ApiResult.Loading -> ApiResult.Error("Loading", null)
            ApiResult.Idle -> ApiResult.Error("Idle", null)
        }
    }

    override suspend fun fetchTier(): String {
        val result = APILayer.getSubscriptionStatus(forceRefresh = true)
        return when (result) {
            is ApiResult.Success -> result.data.tier
            else -> "free"
        }
    }

    private fun mapToWidgetTasks(response: TaskColumnsResponse): List<WidgetTaskDto> {
        val out = mutableListOf<WidgetTaskDto>()
        for (column in response.columns) {
            if (column.name == COMPLETED_COLUMN || column.name == CANCELLED_COLUMN) continue
            val isOverdue = column.name == OVERDUE_COLUMN
            for (task in column.tasks) {
                out.add(
                    WidgetTaskDto(
                        id = task.id.toLong(),
                        title = task.title,
                        priority = task.priorityId?.toLong() ?: 0L,
                        dueDate = task.effectiveDueDate,
                        isOverdue = isOverdue,
                        // Server computes overdue/column bucketing — we don't
                        // recompute daysUntilDue here; it's a best-effort hint
                        // the widget displays when present. Zero for overdue
                        // matches iOS behaviour (daysUntilDue is not surfaced
                        // on the iOS WidgetTask model).
                        daysUntilDue = 0,
                        residenceId = task.residenceId.toLong(),
                        residenceName = "",
                        categoryIcon = task.categoryName ?: "",
                        completed = false
                    )
                )
            }
        }
        return out
    }
}

/**
 * Background worker that refreshes the on-disk widget cache and asks each
 * Glance widget to redraw.
 *
 * **Error contract:**
 *  - [ApiResult.Success] → [Result.success]
 *  - transient [ApiResult.Error] (5xx / network) → [Result.retry]
 *  - auth [ApiResult.Error] (401/403) → [Result.failure]
 *
 * **Test hook:** set [dataSourceOverride] to swap the data source in tests.
 */
class WidgetRefreshWorker(
    appContext: Context,
    params: WorkerParameters
) : CoroutineWorker(appContext, params) {

    override suspend fun doWork(): Result {
        val ctx = applicationContext
        val dataSource = dataSourceOverride ?: DefaultWidgetRefreshDataSource

        // Always attempt tier refresh — tier persistence is cheap and useful
        // even if the task fetch later fails.
        val tier = runCatching { dataSource.fetchTier() }.getOrDefault("free")

        val tasksResult = runCatching { dataSource.fetchTasks() }.getOrElse { t ->
            return Result.retry() // Unexpected throw → transient.
        }

        when (tasksResult) {
            is ApiResult.Success -> {
                val repo = WidgetDataRepository.get(ctx)
                repo.saveTasks(tasksResult.data)
                repo.saveTierState(tier)
                refreshGlanceWidgets(ctx)
                // Chain the next scheduled refresh so cadence keeps ticking
                // even if the OS evicts our periodic request. Wrapped in
                // runCatching — an un-initialized WorkManager (e.g. in
                // unit tests) must not cause an otherwise-green refresh
                // to report failure.
                runCatching { WidgetUpdateManager.schedulePeriodic(ctx) }
                return Result.success()
            }
            is ApiResult.Error -> {
                // Still persist tier if we have it — subscription state is
                // independent of task fetch.
                runCatching { WidgetDataRepository.get(ctx).saveTierState(tier) }
                return if (isPermanentError(tasksResult.code)) Result.failure() else Result.retry()
            }
            ApiResult.Loading, ApiResult.Idle -> return Result.retry()
        }
    }

    private suspend fun refreshGlanceWidgets(ctx: Context) {
        val glanceManager = GlanceAppWidgetManager(ctx)

        runCatching {
            val smallIds = glanceManager.getGlanceIds(HoneyDueSmallWidget::class.java)
            val smallWidget = HoneyDueSmallWidget()
            smallIds.forEach { id -> smallWidget.update(ctx, id) }
        }

        runCatching {
            val mediumIds = glanceManager.getGlanceIds(HoneyDueMediumWidget::class.java)
            val mediumWidget = HoneyDueMediumWidget()
            mediumIds.forEach { id -> mediumWidget.update(ctx, id) }
        }

        runCatching {
            val largeIds = glanceManager.getGlanceIds(HoneyDueLargeWidget::class.java)
            val largeWidget = HoneyDueLargeWidget()
            largeIds.forEach { id -> largeWidget.update(ctx, id) }
        }
    }

    private fun isPermanentError(code: Int?): Boolean {
        // 401/403 — credentials invalid; no amount of retry helps.
        // 404 — endpoint removed; treat as permanent.
        // Everything else (including null / 5xx / network) is transient.
        return code == 401 || code == 403 || code == 404
    }

    companion object {
        /**
         * Test-only hook. Set to a fake data source before invoking
         * [TestListenableWorkerBuilder<WidgetRefreshWorker>]. Always nulled
         * in teardown.
         */
        @Volatile
        var dataSourceOverride: WidgetRefreshDataSource? = null
    }
}
