use std::sync::Arc;

use crate::{
    block::{
        BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetComparatorOutputArgs,
        GetStateForNeighborUpdateArgs, NormalUseArgs, OnPlaceArgs, OnScheduledTickArgs,
        UseWithItemArgs, blocks::candle_cakes::cake_from_candle, registry::BlockActionResult,
    },
    entity::player::Player,
    world::World,
};
use pumpkin_data::item::Item;
use pumpkin_data::{
    Block, BlockStateId,
    block_properties::{BlockProperties, CakeLikeProperties},
    sound::{Sound, SoundCategory},
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::{GameMode, math::position::BlockPos};
use pumpkin_world::{
    tick::TickPriority,
    world::{BlockAccessor, BlockFlags},
};
use rand::{RngExt, rng};

#[pumpkin_block("minecraft:cake")]
pub struct CakeBlock;

impl CakeBlock {
    pub async fn consume_if_hungry(
        world: &Arc<World>,
        player: &Player,
        block: &Block,
        location: &BlockPos,
        state_id: BlockStateId,
    ) -> BlockActionResult {
        match player.gamemode.load() {
            GameMode::Survival | GameMode::Adventure => {
                let hunger_level = player.hunger_manager.level.load();
                if hunger_level >= 20 {
                    return BlockActionResult::Pass;
                }
                player.hunger_manager.level.store(20.min(hunger_level + 2));
                player
                    .hunger_manager
                    .saturation
                    .store(player.hunger_manager.saturation.load() + 0.4);
                player.send_health().await;
            }
            GameMode::Creative | GameMode::Spectator => {}
        }

        let mut properties = CakeLikeProperties::from_state_id(state_id, block);
        match properties.bites {
            0..=5 => {
                player
                    .increment_stat(
                        pumpkin_data::statistic::StatisticCategory::Custom,
                        pumpkin_data::statistic::CustomStatistic::EatCakeSlice as i32,
                        1,
                    )
                    .await;
                properties.bites += 1;
                world
                    .set_block_state(
                        location,
                        properties.to_state_id(block),
                        BlockFlags::NOTIFY_ALL,
                    )
                    .await;
                BlockActionResult::Consume
            }
            6 => {
                player
                    .increment_stat(
                        pumpkin_data::statistic::StatisticCategory::Custom,
                        pumpkin_data::statistic::CustomStatistic::EatCakeSlice as i32,
                        1,
                    )
                    .await;
                world
                    .set_block_state(
                        location,
                        Block::AIR.default_state.id,
                        BlockFlags::NOTIFY_ALL,
                    )
                    .await;
                BlockActionResult::Consume
            }
            _ => BlockActionResult::Pass,
        }
    }
}

impl BlockBehaviour for CakeBlock {
    fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
        Box::pin(async move {
            if !can_place_at(args.world, args.position) {
                return Block::AIR.default_state.id;
            }
            Block::CAKE.default_state.id
        })
    }

    fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
        can_place_at(args.block_accessor, args.position)
    }

    fn use_with_item<'a>(
        &'a self,
        args: UseWithItemArgs<'a>,
    ) -> BlockFuture<'a, BlockActionResult> {
        Box::pin(async move {
            let state_id = args.world.get_block_state_id(args.position);
            let properties = CakeLikeProperties::from_state_id(state_id, args.block);
            let item = args.item_stack.item;
            match item.id {
                id if (Item::CANDLE.id..=Item::BLACK_CANDLE.id).contains(&id) => {
                    if properties.bites != 0 {
                        return Self::consume_if_hungry(
                            args.world,
                            args.player,
                            args.block,
                            args.position,
                            state_id,
                        )
                        .await;
                    }

                    if args.player.gamemode.load() != GameMode::Creative {
                        args.item_stack.decrement(1);
                    }
                    args.world
                        .set_block_state(
                            args.position,
                            cake_from_candle(item).default_state.id,
                            BlockFlags::NOTIFY_ALL,
                        )
                        .await;
                    let seed: f64 = rng().random();
                    args.player
                        .play_sound(
                            Sound::BlockCakeAddCandle as u16,
                            SoundCategory::Blocks,
                            &args.position.to_f64(),
                            1.0,
                            1.0,
                            seed,
                        )
                        .await;
                    BlockActionResult::Consume
                }
                _ => {
                    return Self::consume_if_hungry(
                        args.world,
                        args.player,
                        args.block,
                        args.position,
                        state_id,
                    )
                    .await;
                }
            }
        })
    }

    fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
        Box::pin(async move {
            let state_id = args.world.get_block_state_id(args.position);
            Self::consume_if_hungry(args.world, args.player, args.block, args.position, state_id)
                .await
        })
    }

    fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
        Box::pin(async move {
            if !can_place_at(args.world.as_ref(), args.position) {
                args.world
                    .break_block(args.position, None, BlockFlags::empty())
                    .await;
            }
        })
    }

    fn get_state_for_neighbor_update<'a>(
        &'a self,
        args: GetStateForNeighborUpdateArgs<'a>,
    ) -> BlockFuture<'a, BlockStateId> {
        Box::pin(async move {
            if !can_place_at(args.world, args.position) {
                args.world
                    .schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
            }
            args.state_id
        })
    }

    fn get_comparator_output<'a>(
        &'a self,
        args: GetComparatorOutputArgs<'a>,
    ) -> BlockFuture<'a, Option<u8>> {
        Box::pin(async move {
            let state_id = args.world.get_block_state_id(args.position);
            let properties = CakeLikeProperties::from_state_id(state_id, args.block);
            if properties.bites <= 6 {
                Some((7 - properties.bites) * 2)
            } else {
                Some(0)
            }
        })
    }
}

fn can_place_at(world: &dyn BlockAccessor, position: &BlockPos) -> bool {
    let state = world.get_block_state(&position.down());
    state.is_solid()
}
