package com.yorvana.ui.vehicles

import android.util.Log
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import com.yorvana.YorvanaApplication
import com.yorvana.data.model.OdometerUnit
import com.yorvana.data.model.Vehicle
import com.yorvana.data.preferences.AppPreferences
import com.yorvana.data.preferences.AppPreferencesStore
import com.yorvana.data.repository.VehicleRepository
import com.yorvana.data.storage.VaultStorage
import com.yorvana.domain.AppGate
import com.yorvana.ui.util.ViewModelDependencies
import com.yorvana.ui.util.testInstance
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkAll
import io.mockk.unmockkStatic
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Test

@OptIn(ExperimentalCoroutinesApi::class)
class AddEditVehicleViewModelTest {
    private lateinit var repository: VehicleRepository
    private lateinit var preferences: AppPreferencesStore
    private lateinit var vaultStorage: VaultStorage
    private lateinit var application: YorvanaApplication
    private lateinit var viewModel: AddEditVehicleViewModel
    private lateinit var appGate: AppGate
    private lateinit var isReadOnlyFlow: MutableStateFlow<Boolean>
    private val testDispatcher = UnconfinedTestDispatcher()

    @Before
    fun setUp() {
        Dispatchers.setMain(testDispatcher)
        mockkStatic(Log::class)
        every { Log.w(any<String>(), any<String>()) } returns 0
        repository = mockk()
        preferences = mockk()
        vaultStorage = mockk()
        application = mockk(relaxed = true)
        every { application.getString(any()) } returns "Premium required for additional vehicles"
        appGate = mockk(relaxed = true)
        isReadOnlyFlow = MutableStateFlow(false)

        every { preferences.preferences } returns flowOf(AppPreferences(defaultOdometerUnit = OdometerUnit.MI))
        every { appGate.isReadOnly } returns isReadOnlyFlow
        every { appGate.isPremium } returns MutableStateFlow(false)
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
        unmockkStatic(Log::class)
        unmockkAll()
    }

    private fun createViewModel(vehicleId: String? = null) =
        AddEditVehicleViewModel(
            vehicleId,
            dependencies =
                ViewModelDependencies.testInstance(
                    app = application,
                    appGate = appGate,
                    vehicleRepository = repository,
                    vaultStorage = vaultStorage,
                    preferences = preferences,
                ),
        )

    @Test
    fun `init should load vehicle data when vehicleId is provided`() =
        runTest {
            val vehicle = Vehicle(id = "v1", nickname = "My Car", make = "Make", model = "Model", createdAt = "2023-01-01")
            coEvery { repository.getVehicle("v1") } returns vehicle

            viewModel = createViewModel("v1")

            assertThat(viewModel.state.value.nickname).isEqualTo("My Car")
            assertThat(viewModel.state.value.isEditMode).isTrue()
        }

    @Test
    fun `init should load default odometer unit when vehicleId is null`() =
        runTest {
            viewModel = createViewModel()

            assertThat(viewModel.state.value.odometerUnit).isEqualTo(OdometerUnit.MI)
            assertThat(viewModel.state.value.isEditMode).isFalse()
        }

    @Test
    fun `onEvent NicknameChanged should update state`() =
        runTest {
            viewModel = createViewModel()

            viewModel.onEvent(AddEditVehicleEvent.NicknameChanged("New Name"))
            assertThat(viewModel.state.value.nickname).isEqualTo("New Name")
        }

    @Test
    fun `save should show error when nickname is blank`() =
        runTest {
            viewModel = createViewModel()

            viewModel.onEvent(AddEditVehicleEvent.Save)
            assertThat(viewModel.state.value.nicknameError).isEqualTo("required")
        }

    @Test
    fun `save should call repository and emit NavigateBack on success`() =
        runTest {
            coEvery { vaultStorage.listVehicleIds() } returns Result.success(emptyList())
            coEvery { repository.saveVehicle(any()) } returns Unit

            viewModel = createViewModel()
            viewModel.onEvent(AddEditVehicleEvent.NicknameChanged("Car"))

            viewModel.effects.test {
                viewModel.onEvent(AddEditVehicleEvent.Save)
                assertThat(awaitItem()).isEqualTo(AddEditVehicleEffect.NavigateBack)
                cancelAndIgnoreRemainingEvents()
            }

            coVerify { repository.saveVehicle(match { it.nickname == "Car" }) }
        }

    @Test
    fun `save should be blocked when isReadOnly`() =
        runTest {
            isReadOnlyFlow.value = true
            coEvery { vaultStorage.listVehicleIds() } returns Result.success(emptyList())
            coEvery { repository.saveVehicle(any()) } returns Unit

            viewModel = createViewModel()
            viewModel.onEvent(AddEditVehicleEvent.NicknameChanged("Car"))

            viewModel.effects.test {
                viewModel.onEvent(AddEditVehicleEvent.Save)
                // No effect should be emitted
                cancelAndIgnoreRemainingEvents()
            }

            coVerify(exactly = 0) { repository.saveVehicle(any()) }
        }

    @Test
    fun `onEvent MakeChanged should update state`() =
        runTest {
            viewModel = createViewModel()
            viewModel.onEvent(AddEditVehicleEvent.MakeChanged("Toyota"))
            assertThat(viewModel.state.value.make).isEqualTo("Toyota")
        }

    @Test
    fun `onEvent ModelChanged should update state`() =
        runTest {
            viewModel = createViewModel()
            viewModel.onEvent(AddEditVehicleEvent.ModelChanged("Corolla"))
            assertThat(viewModel.state.value.model).isEqualTo("Corolla")
        }

    @Test
    fun `onEvent YearChanged should update state`() =
        runTest {
            viewModel = createViewModel()
            viewModel.onEvent(AddEditVehicleEvent.YearChanged("2020"))
            assertThat(viewModel.state.value.year).isEqualTo("2020")
        }

    @Test
    fun `onEvent VinChanged should update state`() =
        runTest {
            viewModel = createViewModel()
            viewModel.onEvent(AddEditVehicleEvent.VinChanged("VIN123"))
            assertThat(viewModel.state.value.vin).isEqualTo("VIN123")
        }

    @Test
    fun `onEvent LicensePlateChanged should update state`() =
        runTest {
            viewModel = createViewModel()
            viewModel.onEvent(AddEditVehicleEvent.LicensePlateChanged("ABC-123"))
            assertThat(viewModel.state.value.licensePlate).isEqualTo("ABC-123")
        }

    @Test
    fun `onEvent OdometerUnitChanged should update state`() =
        runTest {
            viewModel = createViewModel()
            viewModel.onEvent(AddEditVehicleEvent.OdometerUnitChanged(OdometerUnit.KM))
            assertThat(viewModel.state.value.odometerUnit).isEqualTo(OdometerUnit.KM)
        }

    @Test
    fun `onEvent NicknameChanged should truncate to MAX_TEXT_LENGTH`() =
        runTest {
            viewModel = createViewModel()
            val longString = "a".repeat(AddEditVehicleViewModel.MAX_TEXT_LENGTH + 10)
            viewModel.onEvent(AddEditVehicleEvent.NicknameChanged(longString))
            assertThat(viewModel.state.value.nickname.length).isEqualTo(AddEditVehicleViewModel.MAX_TEXT_LENGTH)
        }

    @Test
    fun `onEvent MakeChanged should truncate to MAX_TEXT_LENGTH`() =
        runTest {
            viewModel = createViewModel()
            val longString = "a".repeat(AddEditVehicleViewModel.MAX_TEXT_LENGTH + 10)
            viewModel.onEvent(AddEditVehicleEvent.MakeChanged(longString))
            assertThat(viewModel.state.value.make.length).isEqualTo(AddEditVehicleViewModel.MAX_TEXT_LENGTH)
        }

    @Test
    fun `onEvent ModelChanged should truncate to MAX_TEXT_LENGTH`() =
        runTest {
            viewModel = createViewModel()
            val longString = "a".repeat(AddEditVehicleViewModel.MAX_TEXT_LENGTH + 10)
            viewModel.onEvent(AddEditVehicleEvent.ModelChanged(longString))
            assertThat(viewModel.state.value.model.length).isEqualTo(AddEditVehicleViewModel.MAX_TEXT_LENGTH)
        }

    @Test
    fun `onEvent VinChanged should truncate to MAX_TEXT_LENGTH`() =
        runTest {
            viewModel = createViewModel()
            val longString = "a".repeat(AddEditVehicleViewModel.MAX_TEXT_LENGTH + 10)
            viewModel.onEvent(AddEditVehicleEvent.VinChanged(longString))
            assertThat(viewModel.state.value.vin.length).isEqualTo(AddEditVehicleViewModel.MAX_TEXT_LENGTH)
        }

    @Test
    fun `onEvent LicensePlateChanged should truncate to MAX_TEXT_LENGTH`() =
        runTest {
            viewModel = createViewModel()
            val longString = "a".repeat(AddEditVehicleViewModel.MAX_TEXT_LENGTH + 10)
            viewModel.onEvent(AddEditVehicleEvent.LicensePlateChanged(longString))
            assertThat(viewModel.state.value.licensePlate.length).isEqualTo(AddEditVehicleViewModel.MAX_TEXT_LENGTH)
        }

    @Test
    fun `save should fail if year is invalid`() =
        runTest {
            viewModel = createViewModel()
            viewModel.onEvent(AddEditVehicleEvent.NicknameChanged("Car"))

            // Test lower boundary
            viewModel.onEvent(AddEditVehicleEvent.YearChanged((AddEditVehicleViewModel.MIN_YEAR - 1).toString()))
            viewModel.onEvent(AddEditVehicleEvent.Save)
            assertThat(viewModel.state.value.yearError).isEqualTo("invalid_year")

            // Test upper boundary
            viewModel.onEvent(AddEditVehicleEvent.YearChanged((AddEditVehicleViewModel.MAX_YEAR + 1).toString()))
            viewModel.onEvent(AddEditVehicleEvent.Save)
            assertThat(viewModel.state.value.yearError).isEqualTo("invalid_year")
        }

    @Test
    fun `save should fail if VIN is duplicate`() =
        runTest {
            // Make user premium so we can reach the VIN check even with 1 existing vehicle
            every { appGate.isPremium } returns MutableStateFlow(true)
            coEvery { vaultStorage.listVehicleIds() } returns Result.success(listOf("other-v"))
            coEvery { vaultStorage.readVehicle("other-v") } returns
                Result.success(
                    Vehicle(id = "other-v", nickname = "Other", vin = "DUPE-VIN", createdAt = "now"),
                )

            viewModel = createViewModel()
            viewModel.onEvent(AddEditVehicleEvent.NicknameChanged("Car"))
            viewModel.onEvent(AddEditVehicleEvent.VinChanged("DUPE-VIN"))

            viewModel.onEvent(AddEditVehicleEvent.Save)

            assertThat(viewModel.state.value.vinError).isEqualTo("duplicate_vin")
            assertThat(viewModel.state.value.isSaving).isFalse()
        }

    @Test
    fun `save should show error on repository failure`() =
        runTest {
            val errorMessage = "IO Error"
            coEvery { vaultStorage.listVehicleIds() } returns Result.success(emptyList())
            coEvery { repository.saveVehicle(any()) } throws Exception(errorMessage)

            viewModel = createViewModel()
            viewModel.onEvent(AddEditVehicleEvent.NicknameChanged("Car"))

            viewModel.onEvent(AddEditVehicleEvent.Save)

            assertThat(viewModel.state.value.saveError).isEqualTo(errorMessage)
            assertThat(viewModel.state.value.isSaving).isFalse()
        }

    @Test
    fun `save should block second-vehicle creation for non-premium even when isReadOnly is false`() =
        runTest {
            // isReadOnly is false (default)
            // premium is false (default)
            // count is 1
            coEvery { vaultStorage.listVehicleIds() } returns Result.success(listOf("v1"))

            viewModel = createViewModel() // vehicleId = null, creating NEW
            viewModel.onEvent(AddEditVehicleEvent.NicknameChanged("Car 2"))

            viewModel.onEvent(AddEditVehicleEvent.Save)

            assertThat(viewModel.state.value.saveError).contains("Premium required")
            coVerify(exactly = 0) { repository.saveVehicle(any()) }
        }
}
