// Copyright (C) 2026 Jason M. Schwefel. All Rights Reserved.
package com.coldboreballisticsllc.scopedope.data.session

import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test

class DopeSessionSerializerTest {

    private val serializer = DopeSessionSerializer()

    // ── Round-trip ────────────────────────────────────────────────────────────

    @Test fun `serialize then deserialize produces equal session`() {
        val session = DopeSession()
        val raw     = serializer.serialize(session)
        val result  = serializer.deserialize(raw)
        assertTrue("Deserialize must succeed", result.isSuccess)
        assertEquals(session, result.getOrThrow())
    }

    @Test fun `deserialize with unknown keys does not fail`() {
        val raw    = """{"version":3,"unknown_future_field":"ignored"}"""
        val result = serializer.deserialize(raw)
        assertTrue("Unknown keys must be ignored", result.isSuccess)
    }

    // ── V3 migration — distances ──────────────────────────────────────────────

    @Test fun `migrateToV3 converts output_distances_yards to metres`() {
        // 100 yd = 91.44 m, 200 yd = 182.88 m
        val v1Json = """
            {
              "version": 1,
              "output_distances_yards": [100, 200]
            }
        """.trimIndent()
        val result = serializer.deserialize(v1Json)
        assertTrue(result.isSuccess)
        val session = result.getOrThrow()
        assertEquals(91.44,  session.outputDistancesM[0], 0.001)
        assertEquals(182.88, session.outputDistancesM[1], 0.001)
    }

    // ── V3 migration — ammo conversions ──────────────────────────────────────

    @Test fun `migrateToV3 converts muzzle_vel_fps to mps`() {
        // 2650 fps × 0.3048 = 807.72 m/s
        val v1Json = """
            {
              "version": 1,
              "ammo": { "muzzle_vel_fps": 2650 }
            }
        """.trimIndent()
        val result = serializer.deserialize(v1Json)
        assertTrue(result.isSuccess)
        assertEquals(807.72, result.getOrThrow().ammo?.muzzleVelMps ?: 0.0, 0.1)
    }

    @Test fun `migrateToV3 converts bullet_weight_gr to grams`() {
        // 175 gr × 0.06479891 = 11.34 g
        val v1Json = """
            {
              "version": 1,
              "ammo": { "bullet_weight_gr": 175 }
            }
        """.trimIndent()
        val result = serializer.deserialize(v1Json)
        assertTrue(result.isSuccess)
        assertEquals(11.34, result.getOrThrow().ammo?.bulletWeightG ?: 0.0, 0.01)
    }

    @Test fun `migrateToV3 converts temperature_f to celsius`() {
        // 59°F = 15.0°C exactly
        val v1Json = """
            {
              "version": 1,
              "conditions": { "temperature_f": 59.0 }
            }
        """.trimIndent()
        val result = serializer.deserialize(v1Json)
        assertTrue(result.isSuccess)
        assertEquals(15.0, result.getOrThrow().conditions?.temperatureC ?: 0.0, 0.01)
    }

    @Test fun `migrateToV3 with malformed number does not crash`() {
        // toDoubleOrNull() must absorb malformed values rather than throwing
        val v1Json = """
            {
              "version": 1,
              "ammo": { "muzzle_vel_fps": "not_a_number" }
            }
        """.trimIndent()
        val result = serializer.deserialize(v1Json)
        assertTrue("Malformed migration value must not throw", result.isSuccess)
    }

    // ── V3 migration — pressure normalisation ─────────────────────────────────

    @Test fun `migrateToV3 normalises suspiciously low pressure_hpa to inHg`() {
        // A value < 100 hPa in a v2 file was actually stored in inHg; must be re-converted.
        // 29.92 inHg × 33.8639 ≈ 1013.25 hPa
        val v2Json = """
            {
              "version": 2,
              "conditions": { "pressure_hpa": 29.92 }
            }
        """.trimIndent()
        val result = serializer.deserialize(v2Json)
        assertTrue(result.isSuccess)
        val pressureHpa = result.getOrThrow().conditions?.pressureHpa ?: 0.0
        assertEquals(1013.25, pressureHpa, 1.0)
    }

    // ── V3 migration — dope entries ───────────────────────────────────────────

    @Test fun `migrateToV3 renames distance_yards to distance_m and adjustment_mil to elevation_mil`() {
        // 100 yd = 91.44 m; elevation_mil value must be preserved unchanged.
        val v1Json = """
            {
              "version": 1,
              "dope_entries": [
                { "distance_yards": 100, "adjustment_mil": 2.5 }
              ]
            }
        """.trimIndent()
        val result = serializer.deserialize(v1Json)
        assertTrue(result.isSuccess)
        val entry = result.getOrThrow().dopeEntries.firstOrNull()
        assertEquals(91.44, entry?.distanceM ?: 0.0, 0.001)
        assertEquals(2.5,   entry?.elevationMil ?: 0.0, 0.001)
    }

    // ── New field round-trips ─────────────────────────────────────────────────

    @Test fun `coriolis and label distance fields survive serialize-deserialize round-trip`() {
        val session = DopeSession(
            labelSelectedDistancesM = listOf(91.44, 182.88),
            conditions = SessionConditions(
                useCoriolis         = true,
                coriolisLatitudeDeg = 44.5,
            ),
        )
        val raw    = serializer.serialize(session)
        val result = serializer.deserialize(raw)
        assertTrue(result.isSuccess)
        val restored = result.getOrThrow()
        assertEquals(listOf(91.44, 182.88), restored.labelSelectedDistancesM)
        assertEquals(true,  restored.conditions?.useCoriolis)
        assertEquals(44.5,  restored.conditions?.coriolisLatitudeDeg ?: 0.0, 0.001)
    }

    // ── Version passthrough ───────────────────────────────────────────────────

    @Test fun `v3 document is not re-migrated`() {
        val v3Json = serializer.serialize(DopeSession())
        val result = serializer.deserialize(v3Json)
        assertTrue("v3 document must deserialize without error", result.isSuccess)
    }
}
