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

import android.content.Context
import android.net.Uri
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.intOrNull
import kotlinx.serialization.json.jsonPrimitive
import java.io.IOException
import javax.inject.Inject
import javax.inject.Singleton

private val json = Json {
    prettyPrint        = true
    ignoreUnknownKeys  = true   // forward-compatible across app versions
    encodeDefaults     = true
}

@Singleton
class DopeSessionSerializer @Inject constructor() {

    fun serialize(session: DopeSession): String =
        json.encodeToString(DopeSession.serializer(), session)

    fun deserialize(raw: String): Result<DopeSession> = runCatching {
        val element = json.parseToJsonElement(raw)
        val migrated = if (element is JsonObject) migrateIfNeeded(element) else element
        json.decodeFromJsonElement(DopeSession.serializer(), migrated)
    }

    /**
     * Write session to a URI obtained from [ACTION_CREATE_DOCUMENT].
     */
    fun saveTo(context: Context, uri: Uri, session: DopeSession): Result<Unit> =
        runCatching {
            context.contentResolver.openOutputStream(uri)
                ?.use { it.write(serialize(session).toByteArray()) }
                ?: throw IOException("Could not open output stream for $uri")
        }

    /**
     * Read session from a URI obtained from [ACTION_OPEN_DOCUMENT].
     */
    fun loadFrom(context: Context, uri: Uri): Result<DopeSession> =
        runCatching {
            val raw = context.contentResolver.openInputStream(uri)
                ?.use { it.readBytes().toString(Charsets.UTF_8) }
                ?: throw IOException("Could not open input stream for $uri")
            deserialize(raw).getOrThrow()
        }

    private fun migrateIfNeeded(obj: JsonObject): JsonObject {
        val version = obj["version"]?.jsonPrimitive?.intOrNull ?: 1
        return if (version < 3) migrateToV3(obj) else obj
    }

    private fun migrateToV3(obj: JsonObject): JsonObject {
        val map = obj.toMutableMap()

        // output_distances_yards → output_distances_m
        map.remove("output_distances_yards")?.let { arr ->
            if (arr is JsonArray) {
                map["output_distances_m"] = JsonArray(arr.map { el ->
                    JsonPrimitive((el.jsonPrimitive.content.toDoubleOrNull() ?: 0.0) * 0.9144)
                })
            }
        }

        // ammo field renames + conversions
        (map["ammo"] as? JsonObject)?.let { ammo ->
            val a = ammo.toMutableMap()
            a.remove("muzzle_vel_fps")?.let { v ->
                a["muzzle_vel_mps"] = JsonPrimitive((v.jsonPrimitive.content.toDoubleOrNull() ?: 0.0) * 0.3048)
            }
            a.remove("bullet_weight_gr")?.let { v ->
                a["bullet_weight_g"] = JsonPrimitive((v.jsonPrimitive.content.toDoubleOrNull() ?: 0.0) * 0.06479891)
            }
            a.remove("mv_sensitivity_fps_per_f")?.let { v ->
                a["mv_sensitivity_mps_per_c"] = JsonPrimitive((v.jsonPrimitive.content.toDoubleOrNull() ?: 0.0) * 0.54864)
            }
            a.remove("mv_reference_temp_f")?.let { v ->
                a["mv_reference_temp_c"] = JsonPrimitive(((v.jsonPrimitive.content.toDoubleOrNull() ?: 0.0) - 32.0) * 5.0 / 9.0)
            }
            map["ammo"] = JsonObject(a)
        }

        // rifle field renames + conversions
        (map["rifle"] as? JsonObject)?.let { rifle ->
            val r = rifle.toMutableMap()
            r.remove("zero_yards")?.let { v ->
                r["zero_m"] = JsonPrimitive((v.jsonPrimitive.content.toDoubleOrNull() ?: 0.0) * 0.9144)
            }
            r.remove("sight_height_in")?.let { v ->
                r["sight_height_mm"] = JsonPrimitive((v.jsonPrimitive.content.toDoubleOrNull() ?: 0.0) * 25.4)
            }
            r.remove("twist_rate_in")?.let { v ->
                r["twist_rate_mm"] = JsonPrimitive((v.jsonPrimitive.content.toDoubleOrNull() ?: 0.0) * 25.4)
            }
            map["rifle"] = JsonObject(r)
        }

        // conditions field renames + conversions
        (map["conditions"] as? JsonObject)?.let { cond ->
            val c = cond.toMutableMap()
            c.remove("altitude_ft")?.let { v ->
                c["altitude_m"] = JsonPrimitive((v.jsonPrimitive.content.toDoubleOrNull() ?: 0.0) * 0.3048)
            }
            c.remove("temperature_f")?.let { v ->
                c["temperature_c"] = JsonPrimitive(((v.jsonPrimitive.content.toDoubleOrNull() ?: 0.0) - 32.0) * 5.0 / 9.0)
            }
            c.remove("wind_speed_mph")?.let { v ->
                c["wind_speed_mps"] = JsonPrimitive((v.jsonPrimitive.content.toDoubleOrNull() ?: 0.0) * 0.44704)
            }
            // pressure_inhg → pressure_hpa; also catch v2 files that stored inHg in the hPa field
            c.remove("pressure_inhg")?.let { v ->
                c["pressure_hpa"] = JsonPrimitive((v.jsonPrimitive.content.toDoubleOrNull() ?: 0.0) * 33.8639)
            }
            // v2 files stored pressure in inHg even in the "pressure_hpa" field — a schema defect.
            // Normal atmospheric hPa is always > 900; any value < 100 must be inHg and needs conversion.
            c["pressure_hpa"]?.let { v ->
                val hpa = v.jsonPrimitive.content.toDoubleOrNull() ?: 0.0
                if (hpa < 100.0) c["pressure_hpa"] = JsonPrimitive(hpa * 33.8639)
            }
            map["conditions"] = JsonObject(c)
        }

        // dope_entries field renames + conversions
        (map["dope_entries"] as? JsonArray)?.let { entries ->
            map["dope_entries"] = JsonArray(entries.map { entry ->
                if (entry is JsonObject) {
                    val e = entry.toMutableMap()
                    e.remove("distance_yards")?.let { v ->
                        e["distance_m"] = JsonPrimitive((v.jsonPrimitive.content.toDoubleOrNull() ?: 0.0) * 0.9144)
                    }
                    // adjustment_mil → elevation_mil (rename only, value unchanged)
                    e.remove("adjustment_mil")?.let { v ->
                        e["elevation_mil"] = v
                    }
                    JsonObject(e)
                } else entry
            })
        }

        map["version"] = JsonPrimitive(3)
        return JsonObject(map)
    }
}
