package com.bytelyst.platform

import kotlinx.coroutines.test.runTest
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.Json
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

/**
 * SmartAuth v2 tests for Kotlin BLAuthClient.
 * Uses MockWebServer to verify social login, MFA, and type serialization.
 */
class BLAuthClientSmartAuthTest {

    private lateinit var server: MockWebServer
    private lateinit var config: BLPlatformConfig
    private val json = Json { ignoreUnknownKeys = true; encodeDefaults = true }

    @BeforeEach
    fun setUp() {
        server = MockWebServer()
        server.start()
        config = BLPlatformConfig(
            productId = "testapp",
            baseUrl = server.url("/api").toString().trimEnd('/'),
            platform = "android",
            channel = "test",
            applicationId = "com.test.smartauth",
        )
    }

    @AfterEach
    fun tearDown() {
        server.shutdown()
    }

    // ── Test: Social Login (Google) calls correct endpoint ───

    @Test
    fun `loginWithGoogle sends idToken to correct endpoint`() = runTest {
        // Arrange: mock token response
        val tokenResponse = """
            {
                "accessToken": "at_google_123",
                "refreshToken": "rt_google_456",
                "user": {
                    "id": "usr_g1",
                    "email": "google@test.com",
                    "displayName": "Google User",
                    "plan": "free",
                    "role": "user"
                }
            }
        """.trimIndent()
        server.enqueue(MockResponse().setBody(tokenResponse).setResponseCode(200))

        // Act
        val client = BLPlatformClient(config) { null }
        // We can't easily use BLAuthClient directly (needs BLSecureStore with Context),
        // so we test the HTTP layer directly via BLPlatformClient
        val body = json.encodeToString(
            MapSerializer(String.serializer(), String.serializer()),
            mapOf("idToken" to "mock_google_id_token"),
        )
        val response = client.request("POST", "/api/auth/oauth/google", body, skipAuth = true)

        // Assert: correct endpoint called
        val request = server.takeRequest()
        assertEquals("POST", request.method)
        assertTrue(request.path!!.contains("/api/auth/oauth/google"))

        // Assert: request body contains idToken
        val requestBody = request.body.readUtf8()
        assertTrue(requestBody.contains("mock_google_id_token"))

        // Assert: response parses correctly
        val result = json.decodeFromString<BLAuthClient.TokenResponse>(response)
        assertEquals("at_google_123", result.accessToken)
        assertEquals("usr_g1", result.user.id)
        assertEquals("google@test.com", result.user.email)
    }

    // ── Test: MFA challenge detection ────────────────────────

    @Test
    fun `social login detects MFA challenge response`() = runTest {
        // Arrange: mock MFA challenge response
        val mfaResponse = """
            {
                "mfaRequired": true,
                "mfaChallenge": "ch_abc123",
                "methods": ["totp", "recovery"]
            }
        """.trimIndent()
        server.enqueue(MockResponse().setBody(mfaResponse).setResponseCode(200))

        // Act: call the endpoint
        val client = BLPlatformClient(config) { null }
        val body = json.encodeToString(
            MapSerializer(String.serializer(), String.serializer()),
            mapOf("idToken" to "mock_token"),
        )
        val response = client.request("POST", "/api/auth/oauth/google", body, skipAuth = true)

        // Assert: response is parseable as MfaChallenge
        val challenge = json.decodeFromString<BLAuthClient.MfaChallenge>(response)
        assertTrue(challenge.mfaRequired)
        assertEquals("ch_abc123", challenge.mfaChallenge)
        assertEquals(listOf("totp", "recovery"), challenge.methods)
    }

    // ── Test: SmartAuth types serialize/deserialize ──────────

    @Test
    fun `SmartAuth types are correctly serializable`() {
        // MfaStatus
        val statusJson = """{"mfaEnabled":true,"methods":["totp"],"recoveryCodesRemaining":6}"""
        val status = json.decodeFromString<BLAuthClient.MfaStatus>(statusJson)
        assertEquals(true, status.mfaEnabled)
        assertEquals(6, status.recoveryCodesRemaining)

        // AuthProvider
        val providerJson = """{"provider":"google","email":"test@test.com","linkedAt":"2026-01-01","lastUsedAt":null}"""
        val provider = json.decodeFromString<BLAuthClient.AuthProvider>(providerJson)
        assertEquals("google", provider.provider)
        assertNull(provider.lastUsedAt)

        // Device
        val deviceJson = """{"fingerprint":"fp_d1","trustLevel":"trusted","trustExpiresAt":"2026-06-01","createdAt":"2026-01-01","lastSeenAt":"2026-03-01","isTrusted":true}"""
        val device = json.decodeFromString<BLAuthClient.Device>(deviceJson)
        assertEquals("trusted", device.trustLevel)

        // LoginEvent
        val eventJson = """{"id":"e1","eventType":"login_success","method":"google","ip":"1.2.3.4","geo":{"country":"US","city":"SF"},"riskScore":15,"createdAt":"2026-03-01"}"""
        val event = json.decodeFromString<BLAuthClient.LoginEvent>(eventJson)
        assertEquals(15, event.riskScore)
        assertEquals("SF", event.geo?.city)

        // TotpSetup
        val totpJson = """{"otpauthUri":"otpauth://totp/test","qrCode":"data:image/png;base64,abc","recoveryCodes":["code1","code2"]}"""
        val totp = json.decodeFromString<BLAuthClient.TotpSetup>(totpJson)
        assertEquals(2, totp.recoveryCodes.size)

        // StepUpResponse
        val stepUpJson = """{"stepUpToken":"su_abc123"}"""
        val stepUp = json.decodeFromString<BLAuthClient.StepUpResponse>(stepUpJson)
        assertEquals("su_abc123", stepUp.stepUpToken)
    }

    @Test
    fun `MfaRequiredException contains challenge data`() {
        val challenge = BLAuthClient.MfaChallenge(
            mfaRequired = true,
            mfaChallenge = "ch_test",
            methods = listOf("totp"),
        )
        val exception = BLAuthClient.MfaRequiredException(challenge)
        assertEquals("MFA required", exception.message)
        assertEquals("ch_test", exception.challenge.mfaChallenge)
    }

    @Test
    fun `AuthState MfaRequired holds challenge`() {
        val challenge = BLAuthClient.MfaChallenge(
            mfaRequired = true,
            mfaChallenge = "ch_xyz",
            methods = listOf("totp", "recovery"),
        )
        val state = BLAuthClient.AuthState.MfaRequired(challenge)
        assertTrue(state is BLAuthClient.AuthState.MfaRequired)
        assertEquals("ch_xyz", (state as BLAuthClient.AuthState.MfaRequired).challenge.mfaChallenge)
    }
}
