package pl.firmatpp.kierowca.ui

import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import java.io.IOException
import pl.firmatpp.kierowca.data.model.DriverLeaveRequestDto
import pl.firmatpp.kierowca.data.model.DriverRouteDto
import pl.firmatpp.kierowca.data.upload.RouteActionEntity
import pl.firmatpp.kierowca.data.upload.RouteActionStatus
import pl.firmatpp.kierowca.data.upload.RouteActionType
import pl.firmatpp.kierowca.ui.theme.AppThemeMode

class DriverUiStateTest {
    @Test
    fun startsOnInitializingScreenBeforeTokenCheckCompletes() {
        val state = DriverUiState()

        assertEquals(DriverScreen.Initializing, state.screen)
        assertTrue(state.loading)
    }

    @Test
    fun startsWithMaterial3ThemeMode() {
        val state = DriverUiState()

        assertEquals(AppThemeMode.Material3, state.themeMode)
    }

    @Test
    fun networkTimeoutDoesNotChangeValidatedNetworkState() {
        val state = DriverUiState(
            isOnline = true,
            serverConnectionState = ServerConnectionState.Reachable,
            lastServerSyncAtEpochMillis = 123L,
        )

        val failed = state.withApiError(IOException("timeout"))

        assertTrue(failed.isOnline)
        assertTrue(failed.isStale)
        assertEquals(ServerConnectionState.Degraded, failed.serverConnectionState)
    }

    @Test
    fun refreshingLeaveRequestsKeepsDetailScreenOpenAndUpdatesSelectedRequest() {
        val state = DriverUiState(
            screen = DriverScreen.LeaveRequestDetail,
            selectedLeaveRequest = leaveRequest(id = "leave-1", status = "pending"),
        )

        val refreshed = state.withLoadedLeaveRequests(
            requests = listOf(leaveRequest(id = "leave-1", status = "approved")),
            navigateToList = false,
        )

        assertEquals(DriverScreen.LeaveRequestDetail, refreshed.screen)
        assertEquals("approved", refreshed.selectedLeaveRequest?.status)
    }

    @Test
    fun openingLeaveRequestsNavigatesToList() {
        val state = DriverUiState(screen = DriverScreen.Routes)

        val refreshed = state.withLoadedLeaveRequests(
            requests = listOf(leaveRequest(id = "leave-1", status = "pending")),
            navigateToList = true,
        )

        assertEquals(DriverScreen.LeaveRequests, refreshed.screen)
        assertEquals(1, refreshed.leaveRequests.size)
    }

    @Test
    fun staleLeaveRequestRefreshDoesNotOverwriteNewerMutationResult() {
        val state = DriverUiState(
            screen = DriverScreen.LeaveRequestDetail,
            leaveRequests = listOf(leaveRequest(id = "leave-1", status = "cancel_requested", version = 3)),
            selectedLeaveRequest = leaveRequest(id = "leave-1", status = "cancel_requested", version = 3),
        )

        val refreshed = state.withLoadedLeaveRequests(
            requests = listOf(leaveRequest(id = "leave-1", status = "approved", version = 2)),
            navigateToList = false,
        )

        assertEquals("cancel_requested", refreshed.leaveRequests.single().status)
        assertEquals(3L, refreshed.selectedLeaveRequest?.version)
    }

    @Test
    fun displayRoutesAndSelectedRouteUseLocalRouteActionsProjection() {
        val route = route(status = "ZAPLANOWANA")
        val state = DriverUiState(
            routes = listOf(route),
            selectedRoute = route,
            visibleRouteActions = listOf(action(RouteActionType.Start, RouteActionStatus.Pending, weight = 12.5)),
        )

        assertEquals("W TRAKCIE", state.displayRoutes.single().status)
        assertEquals(12.5, state.displayRoutes.single().loadingWeight)
        assertEquals("W TRAKCIE", state.displaySelectedRoute?.status)
        assertEquals(12.5, state.displaySelectedRoute?.loadingWeight)
    }

    @Test
    fun displayRoutesMovesCompletedRoutesBelowActiveDispatcherOrder() {
        val state = DriverUiState(
            routes = listOf(
                route(id = "completed", status = "ZAKOŃCZONA"),
                route(id = "planned", status = "ZAPLANOWANA"),
                route(id = "active", status = "W TRAKCIE"),
            ),
        )

        assertEquals(listOf("planned", "active", "completed"), state.displayRoutes.map { it.id })
    }

    @Test
    fun displayRoutesUsesTheSelectedDayOrderingForMultiDayRoutes() {
        val state = DriverUiState(
            selectedDate = "2026-07-02",
            routes = listOf(
                route(id = "span", status = "ZAPLANOWANA", ordering = 0, dayOrderings = mapOf("2026-07-02" to 2)),
                route(id = "above-first", status = "ZAPLANOWANA", ordering = 0, dayOrderings = mapOf("2026-07-02" to 0)),
                route(id = "above-second", status = "ZAPLANOWANA", ordering = 1, dayOrderings = mapOf("2026-07-02" to 1)),
            ),
        )

        assertEquals(listOf("above-first", "above-second", "span"), state.displayRoutes.map { it.id })
    }

    @Test
    fun freshGpsPointsDoNotShowSynchronizationBannerWhenOnlineIsHealthy() {
        val state = DriverUiState(
            isOnline = true,
            serverConnectionState = ServerConnectionState.Reachable,
            pendingGpsPoints = 48,
            oldestPendingGpsAtEpochMillis = System.currentTimeMillis() - 30_000L,
        )

        assertEquals(0, state.pendingOperationalItems)
        assertFalse(state.operationalQueueNeedsAttention)
    }

    @Test
    fun staleGpsBacklogRequiresAttentionWhenOnline() {
        val state = DriverUiState(
            isOnline = true,
            serverConnectionState = ServerConnectionState.Reachable,
            pendingGpsPoints = 48,
            oldestPendingGpsAtEpochMillis = System.currentTimeMillis() - 3 * 60_000L,
        )

        assertEquals(48, state.pendingOperationalItems)
        assertTrue(state.operationalQueueNeedsAttention)
    }

    @Test
    fun offlineGpsPointsAreShownAsSavedLocallyNotAsFailure() {
        val state = DriverUiState(
            isOnline = false,
            pendingGpsPoints = 48,
            oldestPendingGpsAtEpochMillis = System.currentTimeMillis() - 3 * 60_000L,
        )

        assertEquals(48, state.pendingOperationalItems)
        assertFalse(state.operationalQueueNeedsAttention)
    }

    private fun leaveRequest(id: String, status: String, version: Long = 1): DriverLeaveRequestDto =
        DriverLeaveRequestDto(
            id = id,
            dateFrom = "2026-07-10",
            dateTo = "2026-07-12",
            type = "URLOP",
            status = status,
            version = version,
        )

    private fun route(
        id: String = "1",
        status: String,
        ordering: Int? = null,
        dayOrderings: Map<String, Int> = emptyMap(),
    ): DriverRouteDto =
        DriverRouteDto(
            id = id,
            routeDate = "2026-06-30",
            ordering = ordering,
            dayOrderings = dayOrderings,
            startsAt = "",
            originName = "Baza",
            destinationName = "Instalacja",
            contractorName = "TPP",
            contractName = "Kontrakt",
            contractCode = "TPP-1",
            relationLabel = "Baza -> Instalacja",
            status = status,
            distanceKm = 1.0,
            notes = null,
            truck = null,
        )

    private fun action(type: String, status: String, weight: Double): RouteActionEntity =
        RouteActionEntity(
            clientActionId = "action-$type-$status",
            routeId = "1",
            action = type,
            weight = weight,
            occurredAt = "2026-06-30T07:40:00Z",
            photoClientRequestIdsJson = "[]",
            status = status,
        )
}
