use crate::ScriptManifest;
use crate::generator::Generator;
use chrono::Utc;
use std::collections::HashMap;
use std::fmt::Write;

pub struct KotlinNativeGenerator;

impl Generator for KotlinNativeGenerator {
    fn generate(&self, manifest: &ScriptManifest) -> anyhow::Result<String> {
        let mut output = String::new();
        let local_time = Utc::now();
        writeln!(
            output,
            "// Auto-generated by dropbear-engine with the magna-carta parser on {} UTC",
            local_time.format("%Y-%m-%d %H:%M:%S")
        )?;
        writeln!(
            output,
            "@file:OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class)"
        )?;
        writeln!(output, "@file:Suppress(\"UNUSED_PARAMETER\", \"unused\")")?;
        writeln!(output)?;

        writeln!(output, "package com.dropbear.decl")?;
        writeln!(output)?;

        writeln!(
            output,
            r#"import com.dropbear.DropbearEngine
import com.dropbear.ecs.System
import com.dropbear.EntityId
import com.dropbear.ffi.NativeEngine
import com.dropbear.ffi.generated.DropbearContext
import com.dropbear.logging.Logger
import com.dropbear.math.Vector3d
import com.dropbear.physics.*
import kotlinx.cinterop.*
import kotlin.experimental.ExperimentalNativeApi"#
        )?;
        writeln!(output)?;

        let mut imported_classes = Vec::new();
        for item in manifest.items() {
            if let Some(last_dot) = item.fqcn().rfind('.') {
                let package = &item.fqcn()[..last_dot];
                let simple_name = &item.fqcn()[last_dot + 1..];
                writeln!(output, "import {}.{}", package, simple_name)?;
                imported_classes.push(simple_name.to_string());
            } else {
                imported_classes.push(item.simple_name().to_string());
            }
        }
        writeln!(output)?;

        let mut tag_map: HashMap<String, Vec<String>> = HashMap::new();
        for item in manifest.items() {
            let simple_name = item.simple_name();
            if item.tags().is_empty() {
                continue;
            }
            for tag in item.tags() {
                tag_map
                    .entry(tag.clone())
                    .or_default()
                    .push(simple_name.to_string());
            }
        }

        writeln!(
            output,
            r#"
object ScriptManager {{
    private var nativeEngine: NativeEngine? = null
    private var dropbearEngine: DropbearEngine? = null
    private val scriptsByTag: MutableMap<String, MutableList<System>> = mutableMapOf()

    fun init(dropbearContextPtr: CPointer<DropbearContext>?) : Int {{
        try {{
            val ctx = dropbearContextPtr?.pointed

            val engine = nativeEngine ?: NativeEngine().also {{ nativeEngine = it }}
            engine.init(ctx)

            if (dropbearEngine == null) {{
                dropbearEngine = DropbearEngine(engine)
            }}

            Logger.debug("Native ScriptManager initialised")
            return 0
        }} catch (e: Exception) {{
            dropbear_set_last_error("Native ScriptManager failed to initialise: ${{e.message}}")
            e.printStackTrace()
            return -1
        }}
    }}

    fun loadSystemsByTag(tag: String): Int {{
        val engine = dropbearEngine ?: return -2
        try {{
            if (scriptsByTag.containsKey(tag)) {{
                Logger.trace("Systems already loaded for tag: '$tag'")
                val instances = scriptsByTag[tag] ?: emptyList()
                for (instance in instances) {{
                    instance.attachEngine(engine)
                    instance.load(engine)
                }}
                return 0
            }}
            val factories = getScriptFactories(tag)
            val instances = factories.map {{ it() }}

            for (instance in instances) {{
                instance.attachEngine(engine)
                instance.load(engine)
            }}

            scriptsByTag.getOrPut(tag) {{ mutableListOf() }}.addAll(instances)
            Logger.debug("Loaded ${{instances.size}} script(s) for tag: '$tag'")
            return 0
        }} catch (e: Exception) {{
            dropbear_set_last_error("Error loading systems for tag '$tag': ${{e.message}}")
            e.printStackTrace()
            return -1
        }}
    }}

    fun updateAllSystems(dt: Double): Int {{
        val engine = dropbearEngine ?: return -2
        try {{
            for (instances in scriptsByTag.values) {{
                for (instance in instances) {{
                    instance.attachEngine(engine)
                    instance.clearCurrentEntity()
                    instance.update(engine, dt)
                }}
            }}
            return 0
        }} catch (e: Exception) {{
            dropbear_set_last_error("Error updating all systems: ${{e.message}}")
            e.printStackTrace()
            return -1
        }}
    }}

    fun updateSystemsByTag(tag: String, dt: Double): Int {{
        val engine = dropbearEngine ?: return -2
        try {{
            val instances = scriptsByTag[tag] ?: emptyList()
            for (instance in instances) {{
                instance.attachEngine(engine)
                instance.clearCurrentEntity()
                instance.update(engine, dt)
            }}
            return 0
        }} catch (e: Exception) {{
            dropbear_set_last_error("Error updating systems for tag '$tag': ${{e.message}}")
            e.printStackTrace()
            return -1
        }}
    }}

    fun physicsUpdateAllSystems(dt: Double): Int {{
        val engine = dropbearEngine ?: return -2
        try {{
            for (instances in scriptsByTag.values) {{
                for (instance in instances) {{
                    instance.attachEngine(engine)
                    instance.clearCurrentEntity()
                    instance.physicsUpdate(engine, dt)
                }}
            }}
            return 0
        }} catch (e: Exception) {{
            dropbear_set_last_error("Error physics updating all systems: ${{e.message}}")
            e.printStackTrace()
            return -1
        }}
    }}

    fun physicsUpdateSystemsByTag(tag: String, dt: Double): Int {{
        val engine = dropbearEngine ?: return -2
        try {{
            val instances = scriptsByTag[tag] ?: emptyList()
            for (instance in instances) {{
                instance.attachEngine(engine)
                instance.clearCurrentEntity()
                instance.physicsUpdate(engine, dt)
            }}
            return 0
        }} catch (e: Exception) {{
            dropbear_set_last_error("Error physics updating systems for tag '$tag': ${{e.message}}")
            e.printStackTrace()
            return -1
        }}
    }}

    fun updateSystemsForEntities(tag: String, entities: CPointer<ULongVar>, entityCount: Int, dt: Double): Int {{
        val engine = dropbearEngine ?: return -2
        try {{
            val instances = scriptsByTag[tag] ?: emptyList()

            val entityIds = LongArray(entityCount) {{ index ->
                entities[index].toLong()
            }}

            Logger.trace("Updating systems for tag: $tag with $entityCount entities")

            if (instances.isEmpty()) {{
                return 0
            }}

            if (entityIds.isEmpty()) {{
                for (instance in instances) {{
                    instance.update(engine, dt)
                }}
                return 0
            }}

            for (entityId in entityIds) {{
                for (instance in instances) {{
                    try {{
                        instance.attachEngine(engine)
                        instance.setCurrentEntity(entityId)
                        instance.update(engine, dt)
                    }} catch (ex: Exception) {{
                        Logger.error("Failed to update system $instance for entity $entityId: ${{ex.message}}")
                    }}
                }}
            }}

            for (instance in instances) {{
                instance.clearCurrentEntity()
            }}

            Logger.debug("Updated ${{instances.size}} system(s) for tag '$tag' with ${{entityCount}} entities")
            return 0
        }} catch (e: Exception) {{
            dropbear_set_last_error("Error updating systems for tag '$tag' with entities: ${{e.message}}")
            e.printStackTrace()
            return -1
        }}
    }}

    fun physicsUpdateSystemsForEntities(tag: String, entities: CPointer<ULongVar>, entityCount: Int, dt: Double): Int {{
        val engine = dropbearEngine ?: return -2
        try {{
            val instances = scriptsByTag[tag] ?: emptyList()

            val entityIds = LongArray(entityCount) {{ index ->
                entities[index].toLong()
            }}

            Logger.trace("Physics updating systems for tag: $tag with $entityCount entities")

            if (instances.isEmpty()) {{
                return 0
            }}

            if (entityIds.isEmpty()) {{
                for (instance in instances) {{
                    instance.physicsUpdate(engine, dt)
                }}
                return 0
            }}

            for (entityId in entityIds) {{
                for (instance in instances) {{
                    try {{
                        instance.attachEngine(engine)
                        instance.setCurrentEntity(entityId)
                        instance.physicsUpdate(engine, dt)
                    }} catch (ex: Exception) {{
                        Logger.error("Failed to physics update system $instance for entity $entityId: ${{ex.message}}")
                    }}
                }}
            }}

            for (instance in instances) {{
                instance.clearCurrentEntity()
            }}

            Logger.debug("Physics updated ${{instances.size}} system(s) for tag '$tag' with ${{entityCount}} entities")
            return 0
        }} catch (e: Exception) {{
            dropbear_set_last_error("Error physics updating systems for tag '$tag' with entities: ${{e.message}}")
            e.printStackTrace()
            return -1
        }}
    }}

    fun loadSystemsForEntities(tag: String, entities: CPointer<ULongVar>, entityCount: Int): Int {{
        val engine = dropbearEngine ?: return -2
        try {{
            if (!scriptsByTag.containsKey(tag)) {{
                val factories = getScriptFactories(tag)
                val created = factories.map {{ it() }}
                for (instance in created) {{
                    instance.attachEngine(engine)
                    instance.clearCurrentEntity()
                }}
                scriptsByTag.getOrPut(tag) {{ mutableListOf() }}.addAll(created)
                Logger.debug("Instantiated ${{created.size}} script(s) for tag: '$tag' (entity-scoped load)")
            }}

            val instances = scriptsByTag[tag] ?: emptyList()
            val entityIds = entities.toLongArray(entityCount)

            if (instances.isEmpty()) {{
                return 0
            }}

            if (entityIds.isEmpty()) {{
                for (instance in instances) {{
                    instance.attachEngine(engine)
                    instance.clearCurrentEntity()
                    instance.load(engine)
                }}
                return 0
            }}

            for (entityId in entityIds) {{
                for (instance in instances) {{
                    try {{
                        instance.attachEngine(engine)
                        instance.setCurrentEntity(entityId)
                        instance.load(engine)
                    }} catch (ex: Exception) {{
                        Logger.error("Failed to load system $instance for entity $entityId: ${{ex.message}}")
                    }} finally {{
                        try {{
                            instance.clearCurrentEntity()
                        }} catch (_: Exception) {{
                            // ignore
                        }}
                    }}
                }}
            }}

            return 0
        }} catch (e: Exception) {{
            dropbear_set_last_error("Error loading systems for tag '$tag' with entities: ${{e.message}}")
            e.printStackTrace()
            return -1
        }}
    }}

    fun collisionEvent(
        tag: String,
        currentEntityId: Long,
        eventType: Int,
        c1Index: Int,
        c1Generation: Int,
        c1EntityId: Long,
        c1Id: Int,
        c2Index: Int,
        c2Generation: Int,
        c2EntityId: Long,
        c2Id: Int,
        flags: Long
    ): Int {{
        val engine = dropbearEngine ?: return -2
        try {{
            val instances = scriptsByTag[tag] ?: emptyList()
            if (instances.isEmpty()) return 0

            val type = CollisionEventType.values().getOrNull(eventType) ?: CollisionEventType.Started

            val collider1 = Collider(
                Index(c1Index.toUInt(), c1Generation.toUInt()),
                EntityId(c1EntityId),
                c1Id.toUInt()
            )
            val collider2 = Collider(
                Index(c2Index.toUInt(), c2Generation.toUInt()),
                EntityId(c2EntityId),
                c2Id.toUInt()
            )

            val event = CollisionEvent(type, collider1, collider2, flags.toInt())

            for (instance in instances) {{
                try {{
                    instance.attachEngine(engine)
                    instance.setCurrentEntity(currentEntityId)
                    instance.collisionEvent(engine, event)
                }} catch (ex: Exception) {{
                    Logger.error("Failed to deliver collision event to $instance for entity $currentEntityId: ${{ex.message}}")
                }}
            }}

            for (instance in instances) {{
                instance.clearCurrentEntity()
            }}

            return 0
        }} catch (e: Exception) {{
            dropbear_set_last_error("Error delivering collision event for tag '$tag': ${{e.message}}")
            e.printStackTrace()
            return -1
        }}
    }}

    fun contactForceEvent(
        tag: String,
        currentEntityId: Long,
        c1Index: Int,
        c1Generation: Int,
        c1EntityId: Long,
        c1Id: Int,
        c2Index: Int,
        c2Generation: Int,
        c2EntityId: Long,
        c2Id: Int,
        totalFx: Double,
        totalFy: Double,
        totalFz: Double,
        totalForceMagnitude: Double,
        maxFx: Double,
        maxFy: Double,
        maxFz: Double,
        maxForceMagnitude: Double
    ): Int {{
        val engine = dropbearEngine ?: return -2
        try {{
            val instances = scriptsByTag[tag] ?: emptyList()
            if (instances.isEmpty()) return 0

            val collider1 = Collider(
                Index(c1Index.toUInt(), c1Generation.toUInt()),
                EntityId(c1EntityId),
                c1Id.toUInt()
            )
            val collider2 = Collider(
                Index(c2Index.toUInt(), c2Generation.toUInt()),
                EntityId(c2EntityId),
                c2Id.toUInt()
            )

            val totalForce = Vector3d(totalFx, totalFy, totalFz)
            val maxForceDir = Vector3d(maxFx, maxFy, maxFz)
            val event = ContactForceEvent(
                collider1,
                collider2,
                totalForce,
                totalForceMagnitude,
                maxForceDir,
                maxForceMagnitude
            )

            for (instance in instances) {{
                try {{
                    instance.attachEngine(engine)
                    instance.setCurrentEntity(currentEntityId)
                    instance.collisionForceEvent(engine, event)
                }} catch (ex: Exception) {{
                    Logger.error("Failed to deliver contact force event to $instance for entity $currentEntityId: ${{ex.message}}")
                }}
            }}

            for (instance in instances) {{
                instance.clearCurrentEntity()
            }}

            return 0
        }} catch (e: Exception) {{
            dropbear_set_last_error("Error delivering contact force event for tag '$tag': ${{e.message}}")
            e.printStackTrace()
            return -1
        }}
    }}

    fun destroyByTag(tag: String): Int {{
        try {{
            val engine = dropbearEngine ?: return -2
            val instances = scriptsByTag[tag] ?: emptyList()
            for (instance in instances) {{
                instance.destroy(engine)
            }}
            scriptsByTag.remove(tag)
            Logger.debug("Destroyed ${{instances.size}} script(s) for tag: '$tag'")
            return 0
        }} catch (e: Exception) {{
            dropbear_set_last_error("Error destroying systems for tag '$tag': ${{e.message}}")
            e.printStackTrace()
            return -1
        }}
    }}

    fun destroyInScopeByTag(tag: String): Int {{
        try {{
            val engine = dropbearEngine ?: return -2
            val instances = scriptsByTag[tag] ?: emptyList()
            for (instance in instances) {{
                instance.destroy(engine)
            }}
            Logger.debug("Destroyed (in-scope) ${{instances.size}} script(s) for tag: '$tag'")
            return 0
        }} catch (e: Exception) {{
            dropbear_set_last_error("Error destroying (in-scope) systems for tag '$tag': ${{e.message}}")
            e.printStackTrace()
            return -1
        }}
    }}

    fun destroyAll(): Int {{
        try {{
            val engine = dropbearEngine ?: return -2
            for (instances in scriptsByTag.values) {{
                for (instance in instances) {{
                    instance.destroy(engine)
                }}
            }}
            scriptsByTag.clear()
            dropbearEngine = null
            nativeEngine = null
            return 0
        }} catch (e: Exception) {{
            dropbear_set_last_error("Error destroying scripts: ${{e.message}}")
            e.printStackTrace()
            return -1
        }}
    }}
            "#
        )?;

        // getScriptFactories (generated)
        {
            writeln!(
                output,
                "\
    private fun getScriptFactories(tag: String): List<() -> System> {{"
            )?;
            writeln!(output, "       return when (tag) {{")?;

            for (tag, classes) in &tag_map {
                let factories: Vec<String> = classes
                    .iter()
                    .map(|cls| format!("{{ {}() }}", cls))
                    .collect();
                writeln!(
                    output,
                    "           \"{}\" -> listOf({})",
                    tag,
                    factories.join(", ")
                )?;
            }

            writeln!(output, "           else -> emptyList()")?;
            writeln!(output, "        }}")?;
            writeln!(output, "    }}")?;
            writeln!(output)?;
        }

        writeln!(output, "}}")?;

        writeln!(output)?;
        writeln!(output, "object ComponentManager {{")?;
        writeln!(
            output,
            "    private val instances: MutableMap<String, com.dropbear.ecs.NativeComponent> = mutableMapOf()"
        )?;
        writeln!(output)?;
        writeln!(
            output,
            "    private fun registerOne(component: com.dropbear.ecs.NativeComponent) {{"
        )?;
        writeln!(output, "        component.register()")?;
        writeln!(
            output,
            "        instances[component.fullyQualifiedTypeName] = component"
        )?;
        writeln!(output, "    }}")?;
        writeln!(output)?;
        writeln!(output, "    fun registerAll() {{")?;
        for component in manifest.components() {
            writeln!(
                output,
                "        registerOne({simple}())",
                simple = component.simple_name(),
            )?;
        }
        writeln!(output, "    }}")?;
        writeln!(output)?;
        writeln!(
            output,
            "    fun updateKotlinComponent(fqcn: String, entityId: Long, dt: Double) {{"
        )?;
        writeln!(output, "        val instance = instances[fqcn] ?: return")?;
        writeln!(
            output,
            "        val engine = com.dropbear.DropbearEngine(com.dropbear.DropbearEngine.native)"
        )?;
        writeln!(output, "        instance.setCurrentEntity(entityId)")?;
        writeln!(output, "        instance.updateComponent(engine, dt)")?;
        writeln!(output, "        instance.clearCurrentEntity()")?;
        writeln!(output, "    }}")?;
        writeln!(output)?;
        writeln!(output, "    fun inspectKotlinComponent(fqcn: String) {{")?;
        writeln!(output, "        val instance = instances[fqcn] ?: return")?;
        writeln!(
            output,
            "        val engine = com.dropbear.DropbearEngine(com.dropbear.DropbearEngine.native)"
        )?;
        writeln!(output, "        instance.inspect(engine)")?;
        writeln!(output, "    }}")?;
        writeln!(output, "}}")?;

        // ADD CNAME FUNCTIONS HERE
        writeln!(
            output,
            r#"
fun CPointer<ULongVar>.toLongArray(length: Int): LongArray {{
    require(length >= 0) {{ "Length must be non-negative" }}
    return LongArray(length) {{ index ->
        this[index].toLong()
    }}
}}

@CName("dropbear_init")
fun dropbear_native_init(dropbearContextPtr: CPointer<DropbearContext>?): Int {{
    ComponentManager.registerAll()
    return ScriptManager.init(dropbearContextPtr)
}}

@CName("dropbear_load_tagged")
fun dropbear_load_systems_for_tag(tag: String?): Int {{
    if (tag == null) return -1
    return ScriptManager.loadSystemsByTag(tag)
}}

@CName("dropbear_load_with_entities")
fun dropbear_load_systems_for_entities(tag: String?, entities: CPointer<ULongVar>?, entityCount: Int): Int {{
    if (tag == null || entities == null) return -1
    return ScriptManager.loadSystemsForEntities(tag, entities, entityCount)
}}

@CName("dropbear_update_all")
fun dropbear_update_all_systems(dt: Double): Int {{
    return ScriptManager.updateAllSystems(dt)
}}

@CName("dropbear_update_tagged")
fun dropbear_update_systems_for_tag(tag: String?, dt: Double): Int {{
    if (tag == null) return -1
    return ScriptManager.updateSystemsByTag(tag, dt)
}}

@CName("dropbear_update_with_entities")
fun dropbear_update_systems_for_entities(tag: String?, entities: CPointer<ULongVar>?, entityCount: Int, dt: Double): Int {{
    if (tag == null || entities == null) return -1
    return ScriptManager.updateSystemsForEntities(tag, entities, entityCount, dt)
}}

@CName("dropbear_physics_update_all")
fun dropbear_physics_update_all_systems(dt: Double): Int {{
    return ScriptManager.physicsUpdateAllSystems(dt)
}}

@CName("dropbear_physics_update_tagged")
fun dropbear_physics_update_systems_for_tag(tag: String?, dt: Double): Int {{
    if (tag == null) return -1
    return ScriptManager.physicsUpdateSystemsByTag(tag, dt)
}}

@CName("dropbear_physics_update_with_entities")
fun dropbear_physics_update_systems_for_entities(tag: String?, entities: CPointer<ULongVar>?, entityCount: Int, dt: Double): Int {{
    if (tag == null || entities == null) return -1
    return ScriptManager.physicsUpdateSystemsForEntities(tag, entities, entityCount, dt)
}}

@CName("dropbear_collision_event")
fun dropbear_collision_event(
    tag: String?,
    currentEntityId: ULong,
    eventType: Int,
    c1Index: Int,
    c1Generation: Int,
    c1EntityId: ULong,
    c1Id: Int,
    c2Index: Int,
    c2Generation: Int,
    c2EntityId: ULong,
    c2Id: Int,
    flags: ULong
): Int {{
    if (tag == null) return -1
    return ScriptManager.collisionEvent(
        tag,
        currentEntityId.toLong(),
        eventType,
        c1Index,
        c1Generation,
        c1EntityId.toLong(),
        c1Id,
        c2Index,
        c2Generation,
        c2EntityId.toLong(),
        c2Id,
        flags.toLong()
    )
}}

@CName("dropbear_contact_force_event")
fun dropbear_contact_force_event(
    tag: String?,
    currentEntityId: ULong,
    c1Index: Int,
    c1Generation: Int,
    c1EntityId: ULong,
    c1Id: Int,
    c2Index: Int,
    c2Generation: Int,
    c2EntityId: ULong,
    c2Id: Int,
    totalFx: Double,
    totalFy: Double,
    totalFz: Double,
    totalForceMagnitude: Double,
    maxFx: Double,
    maxFy: Double,
    maxFz: Double,
    maxForceMagnitude: Double
): Int {{
    if (tag == null) return -1
    return ScriptManager.contactForceEvent(
        tag,
        currentEntityId.toLong(),
        c1Index,
        c1Generation,
        c1EntityId.toLong(),
        c1Id,
        c2Index,
        c2Generation,
        c2EntityId.toLong(),
        c2Id,
        totalFx,
        totalFy,
        totalFz,
        totalForceMagnitude,
        maxFx,
        maxFy,
        maxFz,
        maxForceMagnitude
    )
}}

@CName("dropbear_destroy_tagged")
fun dropbear_destroy(tag: String?): Int {{
    if (tag == null) return -1
    return ScriptManager.destroyByTag(tag)
}}

@CName("dropbear_destroy_in_scope_tagged")
fun dropbear_destroy_in_scope(tag: String?): Int {{
    if (tag == null) return -1
    return ScriptManager.destroyInScopeByTag(tag)
}}

@CName("dropbear_destroy_all")
fun dropbear_destroy_all(): Int {{
    return ScriptManager.destroyAll()
}}

@CName("dropbear_update_kotlin_component")
fun dropbear_update_kotlin_component(fqcn: String?, entityId: ULong, dt: Double): Int {{
    if (fqcn == null) return -1
    ComponentManager.updateKotlinComponent(fqcn, entityId.toLong(), dt)
    return 0
}}

@CName("dropbear_inspect_kotlin_component")
fun dropbear_inspect_kotlin_component(fqcn: String?): Int {{
    if (fqcn == null) return -1
    ComponentManager.inspectKotlinComponent(fqcn)
    return 0
}}

@CName("dropbear_get_last_error")
fun dropbear_get_last_error(): String? {{
    return com.dropbear.lastErrorMessage
}}

@CName("dropbear_set_last_error")
fun dropbear_set_last_error(err: String?) {{
    com.dropbear.lastErrorMessage = err
}}
        "#
        )?;

        Ok(output)
    }
}
