package pl.firmatpp.kierowca.ui

import java.time.LocalDate
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import pl.firmatpp.kierowca.data.model.DriverRouteDto
import pl.firmatpp.kierowca.data.model.DispatchSheetReminderDto
import pl.firmatpp.kierowca.data.model.RoutePhotoDto
import pl.firmatpp.kierowca.data.upload.PhotoUploadEntity
import pl.firmatpp.kierowca.data.upload.RouteActionEntity
import pl.firmatpp.kierowca.data.upload.RouteActionStatus
import pl.firmatpp.kierowca.data.upload.RouteActionType

class DriverUiRulesTest {
    private val today = LocalDate.parse("2026-06-30")

    @Test
    fun allowsPhotoActionsOnlyForTodayRouteDate() {
        assertTrue(canManageRoutePhotos("2026-06-30", today))
        assertFalse(canManageRoutePhotos("2026-06-29", today))
        assertFalse(canManageRoutePhotos("2026-07-01", today))
    }

    @Test
    fun blocksPhotoActionsWhenDateIsInvalid() {
        assertFalse(canManageRoutePhotos("", today))
        assertFalse(canManageRoutePhotos("not-a-date", today))
    }

    @Test
    fun allowsRouteCompletionOnlyForTodayPlannedOrInProgressRoutes() {
        assertTrue(canCompleteRouteFromDriverApp(route(status = "ZAPLANOWANA"), "2026-06-30", today))
        assertTrue(canCompleteRouteFromDriverApp(route(status = "W TRAKCIE"), "2026-06-30", today))
        assertFalse(canCompleteRouteFromDriverApp(route(status = "ZAKOŃCZONA"), "2026-06-30", today))
        assertFalse(canCompleteRouteFromDriverApp(route(status = "NIEROZPOZNANA"), "2026-06-30", today))
        assertFalse(canCompleteRouteFromDriverApp(route(status = "ZAPLANOWANA"), "2026-07-01", today))
        assertFalse(canCompleteRouteFromDriverApp(route(status = "ZAPLANOWANA"), "2026-06-29", today))
    }

    @Test
    fun showsLiveRouteDayUpdateOnlyForViewedDate() {
        assertTrue(shouldShowRouteDayLiveUpdate(viewedDate = "2026-03-12", hintDate = "2026-03-12"))
        assertTrue(shouldShowRouteDayLiveUpdate(viewedDate = "2026-03-12", hintDate = null))
        assertFalse(shouldShowRouteDayLiveUpdate(viewedDate = "2026-03-12", hintDate = "2026-03-13"))
        assertFalse(shouldShowRouteDayLiveUpdate(viewedDate = "", hintDate = "2026-03-12"))
    }

    @Test
    fun formatsRouteDateChipHumanFirstWithShortFallback() {
        assertEquals("dzisiaj · 30.06", routeDateChipLabel("2026-06-30", today))
        assertEquals("jutro · 01.07", routeDateChipLabel("2026-07-01", today))
        assertEquals("w poniedziałek · 06.07", routeDateChipLabel("2026-07-06", today))
        assertEquals("02.03", routeDateChipLabel("2027-03-02", today))
        assertEquals("—", routeDateChipLabel("", today))
    }

    @Test
    fun calculatesPhotoGridRowsForTwoColumnInlineGallery() {
        assertEquals(0, inlinePhotoGridRows(0))
        assertEquals(1, inlinePhotoGridRows(1))
        assertEquals(1, inlinePhotoGridRows(2))
        assertEquals(2, inlinePhotoGridRows(3))
        assertEquals(3, inlinePhotoGridRows(5))
    }

    @Test
    fun keepsPhotoPreviewFeedbackVisibleUntilImageLoadsOrFails() {
        assertTrue(shouldShowPhotoPreviewLoadingState(hasPhoto = true, isLoading = true, isError = false))
        assertFalse(shouldShowPhotoPreviewLoadingState(hasPhoto = true, isLoading = false, isError = false))
        assertFalse(shouldShowPhotoPreviewLoadingState(hasPhoto = true, isLoading = true, isError = true))
        assertFalse(shouldShowPhotoPreviewLoadingState(hasPhoto = false, isLoading = true, isError = false))

        assertTrue(shouldShowPhotoPreviewErrorState(hasPhoto = true, isError = true))
        assertFalse(shouldShowPhotoPreviewErrorState(hasPhoto = true, isError = false))
        assertFalse(shouldShowPhotoPreviewErrorState(hasPhoto = false, isError = true))
    }

    @Test
    fun keepsPhotoTileFeedbackVisibleUntilThumbnailLoadsOrFails() {
        assertTrue(shouldShowPhotoTileLoadingState(hasPhoto = true, isLoading = true, isError = false))
        assertFalse(shouldShowPhotoTileLoadingState(hasPhoto = true, isLoading = false, isError = false))
        assertFalse(shouldShowPhotoTileLoadingState(hasPhoto = true, isLoading = true, isError = true))
        assertFalse(shouldShowPhotoTileLoadingState(hasPhoto = false, isLoading = true, isError = false))

        assertTrue(shouldShowPhotoTileErrorState(hasPhoto = true, isError = true))
        assertFalse(shouldShowPhotoTileErrorState(hasPhoto = true, isError = false))
        assertFalse(shouldShowPhotoTileErrorState(hasPhoto = false, isError = true))
    }

    @Test
    fun usesShorterChromeOnCompactScreens() {
        assertEquals(118, authBrandBannerHeightDp(600))
        assertEquals(150, authBrandBannerHeightDp(690))
        assertEquals(190, authBrandBannerHeightDp(780))

        assertEquals(92, appBrandHeaderHeightDp(600))
        assertEquals(108, appBrandHeaderHeightDp(690))
        assertEquals(124, appBrandHeaderHeightDp(780))

        assertEquals(72, bottomNavigationHeightDp(600))
        assertEquals(78, bottomNavigationHeightDp(690))
        assertEquals(82, bottomNavigationHeightDp(780))
    }

    @Test
    fun reducesHorizontalSpaceOnNarrowScreens() {
        assertEquals(14, screenHorizontalPaddingDp(340))
        assertEquals(16, screenHorizontalPaddingDp(390))
        assertEquals(20, screenHorizontalPaddingDp(440))

        assertEquals(70, phonePrefixWidthDp(340))
        assertEquals(82, phonePrefixWidthDp(390))
        assertEquals(94, phonePrefixWidthDp(440))
    }

    @Test
    fun stacksDenseRouteFactsOnNarrowScreens() {
        assertEquals(1, routeFactColumns(340))
        assertEquals(2, routeFactColumns(390))
    }

    @Test
    fun countsServerPhotosAndLocalUploadsAsVisibleAttachments() {
        assertEquals(0, visiblePhotoAttachmentCount(serverPhotoCount = 0, localUploadCount = 0))
        assertEquals(1, visiblePhotoAttachmentCount(serverPhotoCount = 0, localUploadCount = 1))
        assertEquals(3, visiblePhotoAttachmentCount(serverPhotoCount = 1, localUploadCount = 2))
    }

    @Test
    fun countsOnlyUnconfirmedNonCancelledUploadsInQueue() {
        assertEquals(0, queuedPhotoUploadCount(emptyList()))
        assertEquals(
            4,
            queuedPhotoUploadCount(
                listOf(
                    "PENDING",
                    "UPLOADING",
                    "VERIFYING",
                    "FAILED_RETRYABLE",
                    "CONFIRMED",
                    "CANCELLED",
                ),
            ),
        )
        assertEquals(1, queuedPhotoUploadCount(listOf("FAILED_PERMANENT")))
    }

    @Test
    fun usesCreatedAtBeforeTakenAtForNewestFirstPhotoSorting() {
        val fallback = 100L

        assertEquals(
            1_782_892_800_000L,
            routePhotoSortEpochMillis(
                createdAt = "2026-07-01T10:00:00+02:00",
                takenAt = "2026-07-01T08:00:00+02:00",
                fallback = fallback,
            ),
        )
        assertEquals(
            1_782_885_600_000L,
            routePhotoSortEpochMillis(
                createdAt = null,
                takenAt = "2026-07-01T08:00:00+02:00",
                fallback = fallback,
            ),
        )
        assertEquals(fallback, routePhotoSortEpochMillis(createdAt = null, takenAt = null, fallback = fallback))
    }

    @Test
    fun allowsDeleteIconForConfirmedLocalUploadWithServerPhotoId() {
        assertEquals("42", confirmedUploadServerPhotoId("CONFIRMED", "42"))
        assertEquals(null, confirmedUploadServerPhotoId("CONFIRMED", ""))
        assertEquals(null, confirmedUploadServerPhotoId("UPLOADING", "42"))
        assertEquals(null, confirmedUploadServerPhotoId("FAILED_RETRYABLE", "42"))
    }

    @Test
    fun allowsRetryActionOnlyForRetryableFailedUploads() {
        assertTrue(canRetryPhotoUpload("FAILED_RETRYABLE"))
        assertFalse(canRetryPhotoUpload("PENDING"))
        assertFalse(canRetryPhotoUpload("UPLOADING"))
        assertFalse(canRetryPhotoUpload("VERIFYING"))
        assertFalse(canRetryPhotoUpload("CONFIRMED"))
        assertFalse(canRetryPhotoUpload("FAILED_PERMANENT"))
        assertFalse(canRetryPhotoUpload("CANCELLED"))
    }

    @Test
    fun showsFailureDetailsOnlyForFailedUploadsWithErrorMessage() {
        assertTrue(canShowPhotoUploadFailureDetails("FAILED_RETRYABLE", "Typ: błąd serwera"))
        assertTrue(canShowPhotoUploadFailureDetails("FAILED_PERMANENT", "Typ: błąd aplikacji"))
        assertFalse(canShowPhotoUploadFailureDetails("FAILED_RETRYABLE", null))
        assertFalse(canShowPhotoUploadFailureDetails("FAILED_RETRYABLE", ""))
        assertFalse(canShowPhotoUploadFailureDetails("UPLOADING", "Typ: błąd serwera"))
        assertFalse(canShowPhotoUploadFailureDetails("CONFIRMED", "Typ: błąd serwera"))
    }

    @Test
    fun createsFullScreenPreviewForLocalUploadBeforeRouteRefresh() {
        val photo = localUploadPreviewPhoto(upload(status = "CONFIRMED", serverPhotoId = "55"))

        requireNotNull(photo)
        assertEquals("local-upload-123", photo.id)
        assertEquals("10", photo.routeId)
        assertEquals("file:/tmp/photo.jpg", photo.url)
        assertEquals("2026-07-01T08:00:00Z", photo.takenAt)
        assertEquals(50.1, photo.latitude ?: 0.0, 0.0)
        assertEquals(true, photo.canDelete)
    }

    @Test
    fun preciseLocationRequirementAllowsCameraOnlyWithFineLocation() {
        assertTrue(canLaunchCameraWithLocationPolicy(requirePreciseLocation = false, hasFineLocation = false))
        assertTrue(canLaunchCameraWithLocationPolicy(requirePreciseLocation = true, hasFineLocation = true))
        assertFalse(canLaunchCameraWithLocationPolicy(requirePreciseLocation = true, hasFineLocation = false))
    }

    @Test
    fun showsDispatchSheetCardOnlyWhenReminderIsDueToday() {
        assertTrue(shouldShowDispatchSheetReminderCard(dispatchReminder(status = "missing", dueToday = true)))
        assertTrue(shouldShowDispatchSheetReminderCard(dispatchReminder(status = "uploaded", dueToday = true)))
        assertFalse(shouldShowDispatchSheetReminderCard(dispatchReminder(status = "not_required", dueToday = false)))
        assertFalse(shouldShowDispatchSheetReminderCard(null))
    }

    @Test
    fun explainsOfflineStateWithoutCachedSync() {
        assertEquals(
            "Brak połączenia z serwerem. Sprawdź internet i spróbuj ponownie.",
            offlineStaleBannerMessage(isOnline = false, isStale = false, syncLabel = "brak zapisanej synchronizacji"),
        )
    }

    @Test
    fun explainsStaleCachedDataWhenOnline() {
        assertEquals(
            "Dane mogą być nieaktualne. Ostatnia synchronizacja: 12:30.",
            offlineStaleBannerMessage(isOnline = true, isStale = true, syncLabel = "12:30"),
        )
    }

    @Test
    fun labelsDispatchSheetActionByStatusAndLocalUploadQueue() {
        assertEquals("Zrób zdjęcie", dispatchSheetPrimaryActionLabel(dispatchReminder(status = "missing"), hasQueuedUpload = false))
        assertEquals("Wysyłam zdjęcie...", dispatchSheetPrimaryActionLabel(dispatchReminder(status = "missing"), hasQueuedUpload = true))
        assertEquals("Popraw", dispatchSheetPrimaryActionLabel(dispatchReminder(status = "uploaded"), hasQueuedUpload = false))
    }

    @Test
    fun routeStageFormRequiresPositiveWeightAndAtLeastOnePhotoOrQueuedUpload() {
        assertTrue(canSubmitRouteStageForm(weightText = "12,5", serverPhotoCount = 1, localUploadCount = 0))
        assertTrue(canSubmitRouteStageForm(weightText = "12.5", serverPhotoCount = 0, localUploadCount = 1))
        assertFalse(canSubmitRouteStageForm(weightText = "", serverPhotoCount = 1, localUploadCount = 0))
        assertFalse(canSubmitRouteStageForm(weightText = "0", serverPhotoCount = 1, localUploadCount = 0))
        assertFalse(canSubmitRouteStageForm(weightText = "12.5", serverPhotoCount = 0, localUploadCount = 0))
    }

    @Test
    fun filtersRoutePhotosByStageWithOtherFallback() {
        val photos = listOf(
            photo("1", stage = "loading"),
            photo("2", stage = "unloading"),
            photo("3", stage = null),
        )

        assertEquals(listOf("1"), routePhotosForStage(photos, "loading").map { it.id })
        assertEquals(listOf("2"), routePhotosForStage(photos, "unloading").map { it.id })
        assertEquals(listOf("3"), routePhotosForStage(photos, "other").map { it.id })
    }

    @Test
    fun choosesAdaptiveGpsUploadIntervalForActiveRoute() {
        assertEquals(45, activeRouteUploadIntervalSeconds(isMoving = false))
        assertEquals(20, activeRouteUploadIntervalSeconds(isMoving = true))
        assertEquals(5, activeRouteUploadIntervalSeconds(isMoving = true, isUrbanOrTurning = true))
        assertEquals(5, activeRouteUploadIntervalSeconds(isMoving = true, isNearDestination = true))
        assertEquals(20, activeRouteUploadIntervalSeconds(isMoving = true, isHighwayStable = true))
        assertEquals(3, activeRouteUploadIntervalSeconds(isMoving = true, isLikelyOffRoute = true))
    }

    @Test
    fun notificationTapTargetsFinishScreenOnlyAfterArrival() {
        assertEquals(DriverScreen.Detail, activeRouteNotificationTarget(arrived = false))
        assertEquals(DriverScreen.FinishRoute, activeRouteNotificationTarget(arrived = true))
    }

    @Test
    fun plannedRouteShowsLoadingAsNextStepAndStartAction() {
        val route = route(status = "ZAPLANOWANA")

        val steps = routeFlowSteps(route, emptyList())
        val action = routeLifecyclePrimaryAction(route, "2026-06-30", today)

        assertEquals(listOf("Zdjęcia załadunku", "Waga załadunku", "W trasie", "Rozładunek"), steps.map { it.title })
        assertEquals(RouteFlowStepState.Todo, steps[0].state)
        assertEquals(RouteFlowStepState.Todo, steps[1].state)
        assertEquals(RouteFlowStepState.Todo, steps[2].state)
        assertEquals(RouteFlowStepState.Todo, steps[3].state)
        assertEquals("Rozpocznij kurs", action?.label)
        assertEquals(RouteLifecycleAction.Start, action?.action)
    }

    @Test
    fun plannedRouteWithLoadingPhotoShowsOnlyPhotoStepComplete() {
        val route = route(status = "ZAPLANOWANA", photos = listOf(photo("loading-1", stage = "loading")))

        val steps = routeFlowSteps(route, emptyList())

        assertEquals(RouteFlowStepState.LocalComplete, steps[0].state)
        assertEquals(RouteFlowStepState.Todo, steps[1].state)
        assertEquals(RouteFlowStepState.Todo, steps[2].state)
        assertEquals(RouteFlowStepState.Todo, steps[3].state)
    }

    @Test
    fun activeRouteShowsTransitStateAndFinishAction() {
        val route = route(status = "W TRAKCIE", loadingWeight = 12.5)

        val steps = routeFlowSteps(route, emptyList())
        val action = routeLifecyclePrimaryAction(route, "2026-06-30", today)

        assertEquals(RouteFlowStepState.Confirmed, steps[0].state)
        assertEquals(RouteFlowStepState.Confirmed, steps[1].state)
        assertEquals(RouteFlowStepState.LocalComplete, steps[2].state)
        assertEquals(RouteFlowStepState.Todo, steps[3].state)
        assertEquals("Zakończ kurs", action?.label)
        assertEquals(RouteLifecycleAction.Finish, action?.action)
    }

    @Test
    fun finishedRouteShowsCompletedStagesAndNoPrimaryAction() {
        val route = route(status = "ZAKOŃCZONA", loadingWeight = 12.5, unloadingWeight = 11.8)

        val steps = routeFlowSteps(route, emptyList())
        val action = routeLifecyclePrimaryAction(route, "2026-06-30", today)

        assertEquals(RouteFlowStepState.Confirmed, steps[0].state)
        assertEquals(RouteFlowStepState.Confirmed, steps[1].state)
        assertEquals(RouteFlowStepState.Confirmed, steps[2].state)
        assertEquals(RouteFlowStepState.Confirmed, steps[3].state)
        assertEquals(null, action)
    }

    @Test
    fun pendingLocalRouteActionShowsSyncingCallout() {
        val actions = listOf(action(RouteActionType.Start, RouteActionStatus.WaitingForPhotos))

        val steps = routeFlowSteps(route(status = "W TRAKCIE", loadingWeight = 12.5), actions)
        val callout = routeSyncCallout(actions)

        assertEquals(RouteFlowStepState.Syncing, steps[1].state)
        assertEquals("Czekam na wysłanie zdjęć etapu, potem wyślę zmianę statusu.", callout?.text)
        assertFalse(callout?.isConflict ?: true)
    }

    @Test
    fun failedConflictRouteActionRequiresAttention() {
        val actions = listOf(action(RouteActionType.Finish, RouteActionStatus.FailedConflict, "Kurs został przepisany."))

        val steps = routeFlowSteps(route(status = "W TRAKCIE", loadingWeight = 12.5), actions)
        val callout = routeSyncCallout(actions)

        assertEquals(RouteFlowStepState.NeedsAttention, steps[3].state)
        assertEquals("Kurs wymaga obsługi. Dane wpisane w telefonie zostały zachowane.", callout?.text)
        assertTrue(callout?.isConflict ?: false)
    }

    @Test
    fun routeStageSubmitBlockerExplainsMissingRequirements() {
        assertEquals("Podaj wagę.", routeStageSubmitBlocker("", serverPhotoCount = 1, localUploadCount = 0))
        assertEquals("Waga musi być większa od zera.", routeStageSubmitBlocker("0", serverPhotoCount = 1, localUploadCount = 0))
        assertEquals("Dodaj co najmniej jedno zdjęcie etapu.", routeStageSubmitBlocker("12,5", serverPhotoCount = 0, localUploadCount = 0))
        assertEquals(null, routeStageSubmitBlocker("12,5", serverPhotoCount = 0, localUploadCount = 1))
    }

    @Test
    fun loadingPhotosStepRequiresAtLeastOneLoadingPhotoBeforeWeightStep() {
        assertEquals(
            "Dodaj co najmniej jedno zdjęcie załadunku.",
            loadingPhotosSubmitBlocker(serverPhotoCount = 0, localUploadCount = 0),
        )
        assertEquals(null, loadingPhotosSubmitBlocker(serverPhotoCount = 1, localUploadCount = 0))
        assertEquals(null, loadingPhotosSubmitBlocker(serverPhotoCount = 0, localUploadCount = 1))
    }

    @Test
    fun loadingWeightStepRequiresPositiveWeightAndPreviousLoadingPhoto() {
        assertEquals("Podaj wagę.", loadingWeightSubmitBlocker("", serverPhotoCount = 1, localUploadCount = 0))
        assertEquals("Podaj poprawną wagę.", loadingWeightSubmitBlocker("abc", serverPhotoCount = 1, localUploadCount = 0))
        assertEquals("Waga musi być większa od zera.", loadingWeightSubmitBlocker("0", serverPhotoCount = 1, localUploadCount = 0))
        assertEquals(
            "Dodaj co najmniej jedno zdjęcie załadunku.",
            loadingWeightSubmitBlocker("12,5", serverPhotoCount = 0, localUploadCount = 0),
        )
        assertEquals(null, loadingWeightSubmitBlocker("12,5", serverPhotoCount = 0, localUploadCount = 1))
    }

    private fun route(
        status: String,
        loadingWeight: Double? = null,
        unloadingWeight: Double? = null,
        photos: List<RoutePhotoDto> = emptyList(),
    ): DriverRouteDto =
        DriverRouteDto(
            id = "1",
            startsAt = "",
            originName = "Baza",
            destinationName = "Instalacja",
            contractorName = "TPP",
            contractName = "Kontrakt",
            contractCode = "TPP-1",
            relationLabel = "Baza -> Instalacja",
            status = status,
            loadingWeight = loadingWeight,
            unloadingWeight = unloadingWeight,
            distanceKm = 1.0,
            notes = null,
            truck = null,
            photos = photos,
        )

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

    private fun upload(status: String, serverPhotoId: String? = null): PhotoUploadEntity =
        PhotoUploadEntity(
            clientRequestId = "local-upload-123",
            routeId = "10",
            localPath = "/tmp/photo.jpg",
            source = "camera",
            takenAt = "2026-07-01T08:00:00Z",
            latitude = 50.1,
            longitude = 19.9,
            locationAccuracyMeters = 8.0,
            mimeType = "image/jpeg",
            size = 1200,
            contentSha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
            status = status,
            serverPhotoId = serverPhotoId,
        )

    private fun photo(id: String, stage: String?): RoutePhotoDto =
        RoutePhotoDto(
            id = id,
            routeId = "10",
            clientRequestId = null,
            contentSha256 = null,
            source = "camera",
            stage = stage ?: "other",
            mimeType = "image/jpeg",
            size = 1200,
            url = "https://example.test/$id.jpg",
            takenAt = null,
            latitude = null,
            longitude = null,
            locationAccuracyMeters = null,
            createdAt = null,
        )

    private fun dispatchReminder(status: String, dueToday: Boolean = true): DispatchSheetReminderDto =
        DispatchSheetReminderDto(
            enabled = dueToday,
            dueToday = dueToday,
            workDate = "2026-06-30",
            reason = if (dueToday) "friday" else null,
            availableUntil = "2026-06-30T23:59:59+02:00",
            status = status,
            photo = null,
            canUpload = dueToday,
        )
}
