use std::any::Any;
use std::future::Future;
use std::pin::Pin;
use std::sync::{
    Arc, Mutex as StdMutex,
    atomic::AtomicI32,
    atomic::{AtomicBool, Ordering},
};

use crate::block::entities::PropertyDelegate;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_data::data_component_impl::DataComponentImpl;
use pumpkin_data::item::Item;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_data::potion_brewing::{ITEM_RECIPES, POTION_RECIPES};
use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_data::tag::{self, Taggable};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::inventory::{Inventory, sync_read_items_from_nbt, sync_write_items_to_nbt};
use tokio::sync::RwLock;

pub struct BrewingStandBlockEntity {
    pub position: BlockPos,
    pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
    pub dirty: AtomicBool,
    pub brew_time: AtomicI32,
    pub fuel: AtomicI32,
    pub last_potion_count: StdMutex<Option<[bool; 3]>>,
    pub ingredient_item: StdMutex<Option<&'static pumpkin_data::item::Item>>,
}

impl BrewingStandBlockEntity {
    pub const INVENTORY_SIZE: usize = 5; // 3 potion slots + 1 ingredient + 1 fuel
    pub const ID: &'static str = "minecraft:brewing_stand";

    #[must_use]
    pub fn new(position: BlockPos) -> Self {
        use std::array::from_fn;
        Self {
            position,
            items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
            dirty: AtomicBool::new(false),
            brew_time: AtomicI32::new(0),
            fuel: AtomicI32::new(0),
            last_potion_count: StdMutex::new(None),
            ingredient_item: StdMutex::new(None),
        }
    }

    /// Check if the current ingredient matches the stored ingredient
    fn ingredient_matches(&self, ingredient: &ItemStack) -> bool {
        self.ingredient_item
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .is_some_and(|stored| !ingredient.is_empty() && ingredient.get_item().id == stored.id)
    }

    /// Check if any potion slot has a valid recipe with the ingredient
    async fn is_brewable(&self, ingredient: &ItemStack) -> bool {
        if ingredient.is_empty() {
            return false;
        }

        let ingredient_id = ingredient.get_item().id;

        // Check potion recipes (water bottle -> potions, potion upgrades, etc.)
        let items = self.items.read().await;
        for slot_idx in 0..3usize {
            let slot = &items[slot_idx];
            if slot.is_empty() {
                continue;
            }

            // Check item recipes first (potion -> splash potion, splash -> lingering)
            for recipe in &ITEM_RECIPES {
                if slot.get_item().id == recipe.from().id
                    && recipe.ingredient().iter().any(|i| i.id == ingredient_id)
                {
                    return true;
                }
            }

            // Check potion recipes (modify potion type)
            if let Some(pc) =
                slot.get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>()
                && let Some(potion_id) = pc.potion_id
            {
                for recipe in &POTION_RECIPES {
                    if recipe.from().id as i32 == potion_id
                        && recipe.ingredient().iter().any(|i| i.id == ingredient_id)
                    {
                        return true;
                    }
                }
            }
        }

        false
    }

    /// Perform brewing on all valid potion slots
    async fn do_brew(&self, world: &Arc<crate::world::World>, ingredient: &ItemStack) {
        let ingredient_id = ingredient.get_item().id;

        // Apply recipes to each slot
        for slot_idx in 0..3usize {
            let items = self.items.read().await;
            let slot = &items[slot_idx];
            if slot.is_empty() {
                continue;
            }

            let mut new_stack_opt: Option<ItemStack> = None;

            // Try item recipes first (potion -> splash/lingering)
            for recipe in &ITEM_RECIPES {
                if slot.get_item().id == recipe.from().id
                    && recipe.ingredient().iter().any(|i| i.id == ingredient_id)
                {
                    let new_item = recipe.to();
                    let potion_comp = slot.get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>().cloned();
                    let new_stack = potion_comp.map_or_else(
                        || ItemStack::new(slot.item_count, new_item),
                        |pc| {
                            ItemStack::new_with_component(
                                slot.item_count,
                                new_item,
                                vec![(
                                    pumpkin_data::data_component::DataComponent::PotionContents,
                                    Some(pc.to_dyn()),
                                )],
                            )
                        },
                    );
                    new_stack_opt = Some(new_stack);
                    break;
                }
            }

            // Try potion recipes (modify potion type) if item recipe didn't apply
            if new_stack_opt.is_none()
                && let Some(pc) = slot
                    .get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>()
                && let Some(potion_id) = pc.potion_id
            {
                for recipe in &POTION_RECIPES {
                    if recipe.from().id as i32 == potion_id
                        && recipe.ingredient().iter().any(|i| i.id == ingredient_id)
                    {
                        let new_pc = pumpkin_data::data_component_impl::PotionContentsImpl {
                            potion_id: Some(recipe.to().id as i32),
                            custom_color: pc.custom_color,
                            custom_effects: pc.custom_effects.clone(),
                            custom_name: pc.custom_name.clone(),
                        };
                        let new_stack = ItemStack::new_with_component(
                            slot.item_count,
                            slot.get_item(),
                            vec![(
                                pumpkin_data::data_component::DataComponent::PotionContents,
                                Some(new_pc.to_dyn()),
                            )],
                        );
                        new_stack_opt = Some(new_stack);
                        break;
                    }
                }
            }

            drop(items);

            // Update the slot using set_stack if a recipe was applied
            if let Some(new_stack) = new_stack_opt {
                self.set_stack(slot_idx, new_stack).await;
            }
        }

        // Consume ingredient
        let mut items = self.items.write().await;
        items[3].decrement(1);
        self.mark_dirty();
        drop(items);

        // Play sound at the center of the block
        let pos = Vector3::new(
            self.position.0.x as f64 + 0.5,
            self.position.0.y as f64 + 0.5,
            self.position.0.z as f64 + 0.5,
        );
        world.play_sound(Sound::BlockBrewingStandBrew, SoundCategory::Blocks, &pos);

        // Mark dirty to trigger update
        self.mark_dirty();

        // Handle crafting remainder (like glass bottles from honey bottles)
        // TODO: Implement remainder handling when item lookup by ID is available
    }
}

impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity {
    fn size(&self) -> usize {
        Self::INVENTORY_SIZE
    }

    fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> {
        Box::pin(async move {
            let items = self.items.read().await;
            for slot in items.iter() {
                if !slot.is_empty() {
                    return false;
                }
            }
            true
        })
    }

    fn get_stack(&self, slot: usize) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
        Box::pin(async move {
            let items = self.items.read().await;
            items[slot].clone()
        })
    }

    fn remove_stack(
        &self,
        slot: usize,
    ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
        Box::pin(async move {
            let mut items = self.items.write().await;
            let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
            self.mark_dirty();
            removed
        })
    }

    fn remove_stack_specific(
        &self,
        slot: usize,
        amount: u8,
    ) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
        Box::pin(async move {
            let mut items = self.items.write().await;
            let taken = if items[slot].item_count <= amount {
                std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone())
            } else {
                let mut taken = items[slot].clone();
                taken.item_count = amount;
                items[slot].item_count -= amount;
                taken
            };
            self.mark_dirty();
            taken
        })
    }

    fn set_stack(
        &self,
        slot: usize,
        stack: ItemStack,
    ) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
        Box::pin(async move {
            let mut items = self.items.write().await;
            items[slot] = stack;
            self.mark_dirty();
        })
    }

    fn on_open(&self) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
        Box::pin(async move {})
    }

    fn on_close(&self) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
        Box::pin(async move {})
    }

    fn mark_dirty(&self) {
        self.dirty.store(true, Ordering::Relaxed);
    }

    fn as_any(&self) -> &dyn Any {
        self
    }

    fn is_valid_slot_for(&self, slot: usize, stack: &ItemStack) -> bool {
        if stack.is_empty() {
            return true;
        }

        match slot {
            // Slots 0-2 - potion bottles
            0..=2 => stack
                .get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>()
                .is_some(),
            // Slot 3 - ingredient (must be tagged as brewable)
            3 => {
                // Check if item is a valid brewing ingredient
                if stack.get_item().has_tag(&tag::Item::MINECRAFT_BREWING_FUEL) {
                    return false; // Fuel should not go in ingredient slot
                }
                // Allow any item that's not fuel (ingredient validation happens during brewing)
                true
            }
            // Slot 4 - fuel
            4 => stack.get_item().has_tag(&tag::Item::MINECRAFT_BREWING_FUEL),
            _ => false,
        }
    }
}

impl pumpkin_world::inventory::Clearable for BrewingStandBlockEntity {
    fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
        Box::pin(async move {
            let mut items = self.items.write().await;
            items.fill_with(|| ItemStack::EMPTY.clone());
            self.mark_dirty();
        })
    }
}

impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
    fn resource_location(&self) -> &'static str {
        Self::ID
    }

    fn get_position(&self) -> BlockPos {
        self.position
    }

    fn from_nbt(nbt: &NbtCompound, position: BlockPos) -> Self
    where
        Self: Sized,
    {
        let mut entity = Self::new(position);

        // Load brew time / fuel if present in NBT
        if let Some(bt) = nbt.get_int("BrewTime") {
            entity.brew_time.store(bt, Ordering::Relaxed);
        }
        if let Some(f) = nbt.get_int("Fuel") {
            entity.fuel.store(f, Ordering::Relaxed);
        }

        // Load inventory items from NBT
        let items = entity.items.get_mut();
        sync_read_items_from_nbt(nbt, items);

        // If there's an ingredient in slot 3, remember its base item for matching
        let ingredient_item = (!items[3].is_empty()).then(|| items[3].get_item());

        // Recompute last_potion_count so visuals are correct after load
        let mut current: [bool; 3] = [false; 3];
        for (i, slot) in items.iter().take(3).enumerate() {
            current[i] = !slot.is_empty()
                && (slot
                    .get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>()
                    .is_some()
                    || slot.get_item().id == pumpkin_data::item::Item::GLASS_BOTTLE.id);
        }

        if let Some(item) = ingredient_item {
            *entity
                .ingredient_item
                .lock()
                .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(item);
        }

        *entity
            .last_potion_count
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(current);

        entity
    }

    fn write_nbt<'a>(
        &'a self,
        nbt: &'a mut NbtCompound,
    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
        Box::pin(async move {
            // Persist brew state
            nbt.put_int("BrewTime", self.brew_time.load(Ordering::Relaxed));
            nbt.put_int("Fuel", self.fuel.load(Ordering::Relaxed));

            // Save inventory contents to NBT
            self.write_inventory_nbt(nbt, true).await;
        })
    }

    fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn Inventory>> {
        Some(self)
    }

    fn chunk_data_nbt(&self) -> Option<NbtCompound> {
        let mut nbt = NbtCompound::new();
        nbt.put_int("BrewTime", self.brew_time.load(Ordering::Relaxed));
        nbt.put_int("Fuel", self.fuel.load(Ordering::Relaxed));
        sync_write_items_to_nbt(&*futures::executor::block_on(self.items.read()), &mut nbt);
        Some(nbt)
    }

    fn is_dirty(&self) -> bool {
        self.dirty.load(Ordering::Relaxed)
    }

    fn clear_dirty(&self) {
        self.dirty.store(false, Ordering::Relaxed);
    }

    fn as_any(&self) -> &dyn Any {
        self
    }

    fn tick<'a>(
        &'a self,
        world: &'a Arc<crate::world::World>,
    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
        Box::pin(async move {
            // Refill fuel counter from fuel item if needed
            let fuel_refilled = if self.fuel.load(Ordering::Relaxed) <= 0 {
                let mut items = self.items.write().await;
                if !items[4].is_empty()
                    && items[4]
                        .get_item()
                        .has_tag(&tag::Item::MINECRAFT_BREWING_FUEL)
                {
                    self.fuel.store(20, Ordering::Relaxed);
                    items[4].decrement(1);
                    true
                } else {
                    false
                }
            } else {
                false
            };

            // Get current ingredient and check brewing state
            let ingredient = self.items.read().await[3].clone();
            let brewable = self.is_brewable(&ingredient).await;
            let is_brewing = self.brew_time.load(Ordering::Relaxed) > 0;

            // Handle brewing state machine
            if is_brewing {
                // Decrement brew time
                let new_brew_time = self.brew_time.fetch_sub(1, Ordering::Relaxed) - 1;
                let is_done_brewing = new_brew_time == 0;

                if is_done_brewing && brewable {
                    // Brewing complete
                    self.do_brew(world, &ingredient).await;
                } else if !brewable || !self.ingredient_matches(&ingredient) {
                    // Cancel brewing
                    self.brew_time.store(0, Ordering::Relaxed);
                    self.mark_dirty();
                } else {
                    // Continue brewing
                    self.mark_dirty();
                }
            } else if brewable && self.fuel.load(Ordering::Relaxed) > 0 {
                // Start new brewing cycle
                self.fuel.fetch_sub(1, Ordering::Relaxed);
                self.brew_time.store(400, Ordering::Relaxed);
                *self
                    .ingredient_item
                    .lock()
                    .unwrap_or_else(std::sync::PoisonError::into_inner) =
                    Some(ingredient.get_item());
                self.mark_dirty();
            } else if fuel_refilled {
                // Mark dirty if fuel was refilled to update fuel indicator
                self.mark_dirty();
            }

            // Ensure clients are notified when potion slot contents (and their data) change.
            // Compute current presence bits for the three bottle slots
            let mut current: [bool; 3] = [false; 3];
            let items_guard = self.items.read().await;
            for (i, slot) in items_guard.iter().take(3).enumerate() {
                // Consider a potion slot "present" when it has an item and a PotionContents component or is a glass bottle
                current[i] = !slot.is_empty() && (slot.get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>().is_some() || slot.get_item().id == Item::GLASS_BOTTLE.id);
            }
            drop(items_guard);

            // If potion presence changed, update last_potion_count and update block state so clients
            let mut needs_update = false;
            {
                let mut last_guard = self
                    .last_potion_count
                    .lock()
                    .unwrap_or_else(std::sync::PoisonError::into_inner);
                if last_guard.as_ref() != Some(&current) {
                    *last_guard = Some(current);
                    needs_update = true;
                }
            }

            if needs_update {
                // Update the block state properties for the brewing stand to reflect bottle presence
                let world = world.clone();
                let (block, state) = world.get_block_and_state(&self.position);
                // Use generated block properties helper to produce a new state id with the bits set
                let mut props =
                    pumpkin_data::block_properties::BrewingStandLikeProperties::from_state_id(
                        state.id, block,
                    );
                // Generated field names use raw identifiers for clarity
                props.r#has_bottle_0 = current[0];
                props.r#has_bottle_1 = current[1];
                props.r#has_bottle_2 = current[2];

                world
                    .set_block_state(
                        &self.position,
                        props.to_state_id(block),
                        crate::world::BlockFlags::NOTIFY_ALL,
                    )
                    .await;

                // Also mark dirty so inventory/container updates are sent to open screens
                self.mark_dirty();
            }
        })
    }

    fn to_property_delegate(self: Arc<Self>) -> Option<Arc<dyn PropertyDelegate>> {
        Some(self as Arc<dyn PropertyDelegate>)
    }
}

impl PropertyDelegate for BrewingStandBlockEntity {
    fn get_property(&self, index: i32) -> i32 {
        match index {
            0 => self.brew_time.load(Ordering::Relaxed),
            1 => self.fuel.load(Ordering::Relaxed),
            _ => 0,
        }
    }

    fn set_property(&self, _index: i32, _value: i32) {}

    fn get_properties_size(&self) -> i32 {
        2
    }
}
