use std::sync::Arc;
use std::sync::atomic::AtomicBool;

use crate::plugin::player::egg_throw::PlayerEggThrowEvent;
use crate::{
    entity::{
        Entity, EntityBase, EntityBaseFuture, NBTStorage, projectile::ThrownItemEntity,
        r#type::from_type,
    },
    server::Server,
};
use pumpkin_data::entity::{EntityStatus, EntityType};
use pumpkin_data::item::Item;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_data::meta_data_type::MetaDataType;
use pumpkin_data::tracked_data::TrackedData;
use pumpkin_protocol::bedrock::server::actor_event::ActorEventType;
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
use pumpkin_protocol::java::client::play::Metadata;
use pumpkin_util::math::vector3::Vector3;
use tokio::sync::RwLock;
use uuid::Uuid;

const MAX_EGG_HATCH_EVENT_SPAWNS: usize = 16;
const GRAVITY: f64 = 0.03;

pub struct EggEntity {
    pub thrown: ThrownItemEntity,
    pub item_stack: RwLock<ItemStack>,
}

impl EggEntity {
    pub fn new(entity: Entity) -> Self {
        // Default velocity slightly upward for thrown egg
        entity.set_velocity(Vector3::new(0.0, 0.1, 0.0));
        let thrown = ThrownItemEntity {
            entity,
            owner_id: None,
            collides_with_projectiles: false,
            has_hit: AtomicBool::new(false),
            gravity: GRAVITY,
        };

        Self {
            thrown,
            item_stack: RwLock::new(ItemStack::new(1, &Item::EGG)),
        }
    }

    pub fn new_shot(entity: Entity, shooter: &Entity) -> Self {
        let thrown = ThrownItemEntity::new(entity, shooter, GRAVITY);
        // Default slight upward velocity
        thrown.entity.set_velocity(Vector3::new(0.0, 0.1, 0.0));

        Self {
            thrown,
            item_stack: RwLock::new(ItemStack::new(1, &Item::EGG)),
        }
    }

    /// Set the item stack shown by this thrown egg
    pub async fn set_item_stack(&self, item_stack: ItemStack) {
        let mut write = self.item_stack.write().await;
        *write = item_stack;
    }
}

impl NBTStorage for EggEntity {}

impl EntityBase for EggEntity {
    fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
        Box::pin(async move {
            let entity = self.get_entity();
            let stack = self.item_stack.read().await;

            // Sync the item stack so the client renders the correct color/variant
            entity.send_meta_data(
                &[Metadata::new(
                    TrackedData::ITEM_STACK,
                    MetaDataType::ITEM_STACK,
                    &ItemStackSerializer::from(stack.clone()),
                )],
                None,
            );
        })
    }

    fn tick<'a>(
        &'a self,
        caller: &'a Arc<dyn EntityBase>,
        server: &'a Server,
    ) -> EntityBaseFuture<'a, ()> {
        Box::pin(async move { self.thrown.process_tick(caller, server).await })
    }

    fn get_entity(&self) -> &Entity {
        self.thrown.get_entity()
    }

    fn get_living_entity(&self) -> Option<&crate::entity::living::LivingEntity> {
        None
    }

    fn as_nbt_storage(&self) -> &dyn NBTStorage {
        self
    }

    fn on_hit(&self, hit: crate::entity::projectile::ProjectileHit) -> EntityBaseFuture<'_, ()> {
        Box::pin(async move {
            let world = self.get_entity().world.load();
            let hit_pos = hit.hit_pos();
            let normal = hit.normal();

            // Chicken spawn position offset slightly from hit position
            let spawn_pos = hit_pos.add(&normal.multiply(0.5, 0.5, 0.5));

            // Play egg break particles
            world.send_entity_status(
                self.get_entity(),
                EntityStatus::Death,
                Some(ActorEventType::Death),
            );

            // Decide spawn count per probabilities:
            // r == 0 -> spawn 4 (1/256)
            // r in 1..31 -> spawn 1 (31/256)
            // else -> 0
            let r: u8 = rand::random(); // 0..=255
            let mut to_spawn = if r == 0 { 4usize } else { usize::from(r < 32) };
            let mut hatching = to_spawn > 0;
            let mut hatching_type: &'static EntityType = &EntityType::CHICKEN;

            if let Some(owner_id) = self.thrown.owner_id
                && let Some(player) = world.get_player_by_id(owner_id)
                && let Some(server) = world.server.upgrade()
            {
                let mut event = PlayerEggThrowEvent::new(
                    player,
                    self.get_entity().entity_uuid,
                    hatching,
                    to_spawn as u8,
                    hatching_type,
                );
                server.plugin_manager.fire(&server, &mut event).await;
                if event.cancelled {
                    hatching = false;
                } else {
                    hatching = event.hatching;
                    to_spawn = (event.num_hatches as usize).min(MAX_EGG_HATCH_EVENT_SPAWNS);
                    hatching_type = event.hatching_type;
                }
            }

            // Spawn chickens in a separate task to prevent stack overflow
            if hatching && to_spawn > 0 {
                let world_clone = world.clone();
                let spawn_pos_clone = spawn_pos;

                let variant_name = {
                    let stack = self.item_stack.read().await;
                    stack.get_data_component::<pumpkin_data::data_component_impl::ChickenVariantImpl>()
                        .map(|comp| comp.value.clone())
                };

                tokio::spawn(async move {
                    for _ in 0..to_spawn {
                        let mob =
                            from_type(hatching_type, spawn_pos_clone, &world_clone, Uuid::new_v4());

                        let yaw = rand::random::<f32>() * 360.0;
                        let new_entity = mob.get_entity();
                        new_entity.set_rotation(yaw, 0.0);
                        new_entity.set_age(-24000);
                        if let Some(name) = &variant_name {
                            mob.set_variant_name(name);
                        }

                        world_clone.spawn_entity(mob).await;
                    }
                });
            }
        })
    }

    fn cast_any(&self) -> &dyn std::any::Any {
        self
    }
}
