package dev.adriankuta.flights.model.repository.usecases

import dev.adriankuta.flights.domain.types.Airport
import dev.adriankuta.flights.domain.types.MacCity
import dev.adriankuta.model.data.api.RoutesService
import dev.adriankuta.model.data.api.entities.RouteResponse
import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test

class GetConnectionsForAirportUseCaseImplTest {

    private lateinit var routesService: RoutesService
    private lateinit var useCase: GetConnectionsForAirportUseCaseImpl

    @Before
    fun setup() {
        routesService = mockk()
        useCase = GetConnectionsForAirportUseCaseImpl(routesService)
    }

    @Test
    fun `invoke returns mapped airports when service returns valid response`() = runTest {
        // Given
        val departureAirport = Airport.Departure(
            code = "WAW",
            name = "Warsaw",
            macCity = MacCity(macCode = "WAW"),
        )

        val routeResponse = RouteResponse(
            departureAirport = RouteResponse.Airport.Departure(
                code = "WAW",
                name = "Warsaw",
                macCity = RouteResponse.Airport.MacCity(macCode = "WAW"),
            ),
            arrivalAirport = RouteResponse.Airport.Arrival(
                code = "LDN",
                name = "London",
                macCity = RouteResponse.Airport.MacCity(macCode = "LDN"),
            ),
            connectingAirport = null,
        )

        coEvery {
            routesService.getRoutes(
                languageCode = "pl",
                departureAirportCode = "WAW",
            )
        } returns listOf(routeResponse)

        // When
        val result = useCase.invoke(departureAirport)

        // Then
        val expectedAirports = setOf(
            Airport.Departure(
                code = "WAW",
                name = "Warsaw",
                macCity = MacCity(macCode = "WAW"),
            ),
            Airport.Arrival(
                code = "LDN",
                name = "London",
                macCity = MacCity(macCode = "LDN"),
            ),
        )

        assertEquals(expectedAirports, result)
    }

    @Test
    fun `invoke returns empty set when service returns empty list`() = runTest {
        // Given
        val departureAirport = Airport.Departure(
            code = "WAW",
            name = "Warsaw",
            macCity = MacCity(macCode = "WAW"),
        )

        coEvery {
            routesService.getRoutes(
                languageCode = "pl",
                departureAirportCode = "WAW",
            )
        } returns emptyList()

        // When
        val result = useCase.invoke(departureAirport)

        // Then
        assertEquals(emptySet<Airport>(), result)
    }

    @Test
    fun `invoke filters out airports with null code or name`() = runTest {
        // Given
        val departureAirport = Airport.Departure(
            code = "WAW",
            name = "Warsaw",
            macCity = MacCity(macCode = "WAW"),
        )

        val routeResponse = RouteResponse(
            departureAirport = RouteResponse.Airport.Departure(
                code = null, // This should be filtered out
                name = "Warsaw",
                macCity = RouteResponse.Airport.MacCity(macCode = "WAW"),
            ),
            arrivalAirport = RouteResponse.Airport.Arrival(
                code = "LDN",
                name = "London",
                macCity = RouteResponse.Airport.MacCity(macCode = "LDN"),
            ),
            connectingAirport = RouteResponse.Airport.Connecting(
                code = "BER",
                name = null, // This should be filtered out
                macCity = RouteResponse.Airport.MacCity(macCode = "BER"),
            ),
        )

        coEvery {
            routesService.getRoutes(
                languageCode = "pl",
                departureAirportCode = "WAW",
            )
        } returns listOf(routeResponse)

        // When
        val result = useCase.invoke(departureAirport)

        // Then
        val expectedAirports = setOf(
            Airport.Arrival(
                code = "LDN",
                name = "London",
                macCity = MacCity(macCode = "LDN"),
            ),
        )

        assertEquals(expectedAirports, result)
    }

    @Test
    fun `invoke handles multiple route responses correctly`() = runTest {
        // Given
        val departureAirport = Airport.Departure(
            code = "WAW",
            name = "Warsaw",
            macCity = MacCity(macCode = "WAW"),
        )

        val routeResponse1 = RouteResponse(
            departureAirport = RouteResponse.Airport.Departure(
                code = "WAW",
                name = "Warsaw",
                macCity = RouteResponse.Airport.MacCity(macCode = "WAW"),
            ),
            arrivalAirport = RouteResponse.Airport.Arrival(
                code = "LDN",
                name = "London",
                macCity = RouteResponse.Airport.MacCity(macCode = "LDN"),
            ),
            connectingAirport = null,
        )

        val routeResponse2 = RouteResponse(
            departureAirport = RouteResponse.Airport.Departure(
                code = "WAW",
                name = "Warsaw",
                macCity = RouteResponse.Airport.MacCity(macCode = "WAW"),
            ),
            arrivalAirport = RouteResponse.Airport.Arrival(
                code = "PAR",
                name = "Paris",
                macCity = RouteResponse.Airport.MacCity(macCode = "PAR"),
            ),
            connectingAirport = null,
        )

        coEvery {
            routesService.getRoutes(
                languageCode = "pl",
                departureAirportCode = "WAW",
            )
        } returns listOf(routeResponse1, routeResponse2)

        // When
        val result = useCase.invoke(departureAirport)

        // Then
        val expectedAirports = setOf(
            Airport.Departure(
                code = "WAW",
                name = "Warsaw",
                macCity = MacCity(macCode = "WAW"),
            ),
            Airport.Arrival(
                code = "LDN",
                name = "London",
                macCity = MacCity(macCode = "LDN"),
            ),
            Airport.Arrival(
                code = "PAR",
                name = "Paris",
                macCity = MacCity(macCode = "PAR"),
            ),
        )

        assertEquals(expectedAirports, result)
    }
}
