package com.yorvana.testsupport

import android.content.pm.ActivityInfo
import android.net.Uri
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.yorvana.R
import com.yorvana.YorvanaApplication
import com.yorvana.data.model.CategoriesFile
import com.yorvana.data.model.CustomCategory
import com.yorvana.data.model.OdometerUnit
import com.yorvana.data.model.ServiceRecord
import com.yorvana.data.model.Vehicle
import com.yorvana.data.preferences.DebugBillingOverrideMode
import com.yorvana.testsupport.lifecycle.hardKillAndRelaunch
import com.yorvana.util.DateFormatter
import com.yorvana.util.UuidGenerator
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import org.junit.Assume
import java.io.File

fun MainActivityComposeRule.killAndRelaunch(waitForText: String? = null) {
    hardKillAndRelaunch(this)

    // Handle crash dialog if present (non-blocking semantics check)
    val crashDialogTitle = "Previous crash detected"
    val hasCrashDialog =
        try {
            onAllNodesWithText(crashDialogTitle).fetchSemanticsNodes().isNotEmpty()
        } catch (_: Exception) {
            false
        }

    if (hasCrashDialog) {
        try {
            onNodeWithText("Dismiss").performClick()
            waitForIdle()
        } catch (_: Exception) {
        }
    }

    // Wait for the UI to be ready
    val targetContext = activity.applicationContext
    val expected = waitForText ?: targetContext.getString(R.string.my_garage)
    if (waitForText == null) {
        repeat(4) {
            if (onAllNodesWithText(expected).fetchSemanticsNodes().isNotEmpty()) {
                return@repeat
            }
            activityRule.scenario.onActivity { activity ->
                activity.onBackPressedDispatcher.onBackPressed()
            }
            waitForIdle()
        }
    }
    awaitText(expected, timeoutMillis = 120_000)
}

fun MainActivityComposeRule.rotateMidEdit() {
    activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
    activityRule.scenario.recreate()
    waitForIdle()
    activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
    activityRule.scenario.recreate()
    waitForIdle()
}

fun MainActivityComposeRule.app(): YorvanaApplication = activity.application as YorvanaApplication

fun MainActivityComposeRule.seedVehicle(
    id: String = UuidGenerator.generate(),
    nickname: String,
    make: String = "",
    model: String = "",
    year: Int? = null,
    vin: String? = null,
    licensePlate: String? = null,
    odometerUnit: OdometerUnit = OdometerUnit.KM,
): Vehicle {
    val vehicle =
        Vehicle(
            id = id,
            nickname = nickname,
            make = make,
            model = model,
            year = year,
            vin = vin,
            licensePlate = licensePlate,
            odometerUnit = odometerUnit,
            createdAt = DateFormatter.nowIso(),
        )
    runBlocking { app().vehicleRepository.saveVehicle(vehicle) }
    waitForIdle()
    return vehicle
}

fun MainActivityComposeRule.seedRecord(
    vehicleId: String,
    id: String = UuidGenerator.generate(),
    date: String = "2025-01-01",
    odometer: Int = 12_345,
    category: String = "oil-change",
    performer: String? = "Self",
    cost: Double? = null,
    currency: String? = null,
    attachments: List<String> = emptyList(),
): ServiceRecord {
    val record =
        ServiceRecord(
            id = id,
            vehicleId = vehicleId,
            date = date,
            odometer = odometer,
            category = category,
            performer = performer,
            cost = cost,
            currency = currency,
            attachments = attachments,
            createdAt = DateFormatter.nowIso(),
            updatedAt = DateFormatter.nowIso(),
        )
    runBlocking { app().recordRepository.saveRecord(record) }
    waitForIdle()
    return record
}

fun MainActivityComposeRule.seedCustomCategory(label: String): CustomCategory {
    val category =
        CustomCategory(
            id = UuidGenerator.generate(),
            label = label,
            createdAt = DateFormatter.nowIso(),
        )
    runBlocking {
        val current = app().vaultStorage.readCategories().getOrElse { CategoriesFile() }
        app().vaultStorage.writeCategories(current.copy(custom = current.custom + category)).getOrThrow()
    }
    return category
}

fun MainActivityComposeRule.seedAttachmentFile(
    vehicleId: String,
    recordId: String,
    filename: String,
    contents: String = "fixture attachment",
) {
    seedAttachmentBytes(vehicleId, recordId, filename, contents.toByteArray())
}

fun MainActivityComposeRule.seedAttachmentBytes(
    vehicleId: String,
    recordId: String,
    filename: String,
    contents: ByteArray,
) {
    val source = File(activity.cacheDir, filename)
    source.writeBytes(contents)
    runBlocking {
        app().recordRepository.addAttachment(
            vehicleId = vehicleId,
            recordId = recordId,
            sourceUri = Uri.fromFile(source),
            filename = filename,
        )
        // Also update the record metadata to include the attachment
        val record = app().recordRepository.getRecord(vehicleId, recordId)
        if (record != null && !record.attachments.contains(filename)) {
            app().recordRepository.saveRecord(record.copy(attachments = record.attachments + filename))
        }
    }
}

fun MainActivityComposeRule.setBillingOverride(mode: DebugBillingOverrideMode) {
    runBlocking { app().debugBillingOverride?.setMode(mode) }
    waitForIdle()
}

fun MainActivityComposeRule.seedCachedPremium() {
    runBlocking {
        app().preferences.setIsPremiumCached(true)
        app().preferences.setDebugBillingOverrideMode(DebugBillingOverrideMode.NONE)
    }
    waitForIdle()
}

fun MainActivityComposeRule.vaultRoot(vaultName: String = "test_vault"): File = File(activity.filesDir, vaultName)

fun MainActivityComposeRule.vehicleDir(vehicleId: String): File = File(vaultRoot(), "vehicles/$vehicleId")

fun MainActivityComposeRule.recordFile(
    vehicleId: String,
    recordId: String,
): File = File(vehicleDir(vehicleId), "records/$recordId.json")

fun MainActivityComposeRule.attachmentDir(
    vehicleId: String,
    recordId: String,
): File = File(vehicleDir(vehicleId), "records/$recordId")

inline fun <reified T> File.decodeJson(): T =
    Json {
        ignoreUnknownKeys = true
    }.decodeFromString<T>(readText())

class NetworkState(
    private val device: UiDevice,
) {
    private var changed = false

    fun setOffline() {
        val wifi = runCatching { device.executeShellCommand("svc wifi disable") }
        val data = runCatching { device.executeShellCommand("svc data disable") }
        val commandsAccepted =
            wifi.getOrNull().isAcceptedShellOutput() &&
                data.getOrNull().isAcceptedShellOutput()
        Assume.assumeTrue(
            "device image does not permit `svc wifi disable` / `svc data disable` from instrumentation",
            commandsAccepted,
        )
        changed = true
    }

    fun restore() {
        if (!changed) return
        runCatching { device.executeShellCommand("svc wifi enable") }
        runCatching { device.executeShellCommand("svc data enable") }
        changed = false
    }

    private fun String?.isAcceptedShellOutput(): Boolean {
        val output = this?.lowercase().orEmpty()
        return output.isBlank() ||
            listOf("permission denial", "not found", "exception", "inaccessible", "error").none(output::contains)
    }
}

fun networkState(): NetworkState = NetworkState(UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()))
