use std::sync::Arc;

use pumpkin_data::{
    Block,
    BlockDirection::{East, North, South, West},
    BlockStateId,
    block_properties::{BlockProperties, FarmlandLikeProperties, WheatLikeProperties},
};
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use pumpkin_world::world::{BlockAccessor, BlockFlags};
use rand::RngExt;

use crate::{
    block::blocks::plant::PlantBlockBase, plugin::api::events::block::block_grow::BlockGrowEvent,
    world::World,
};

type CropProperties = WheatLikeProperties;
type FarmlandProperties = FarmlandLikeProperties;

pub mod beetroot;
pub mod carrot;
pub mod gourds;
pub mod nether_wart;
pub mod potatoes;
pub mod sweet_berry_bush;
pub mod torch_flower;
pub mod wheat;

trait CropBlockBase: PlantBlockBase {
    fn can_plant_on_top(&self, block_accessor: &dyn BlockAccessor, pos: &BlockPos) -> bool {
        let block = block_accessor.get_block(pos);
        block == &Block::FARMLAND
    }

    fn max_age(&self) -> i32 {
        7
    }

    fn get_age(&self, state: BlockStateId, block: &Block) -> i32 {
        let props = CropProperties::from_state_id(state, block);
        i32::from(props.age)
    }

    fn state_with_age(&self, block: &Block, state: BlockStateId, age: i32) -> BlockStateId {
        let mut props = CropProperties::from_state_id(state, block);
        props.age = age as u8;
        props.to_state_id(block)
    }

    async fn random_tick(&self, world: &Arc<World>, pos: &BlockPos) {
        let (block, state) = world.get_block_and_state_id(pos);
        let age = self.get_age(state, block);
        if age < self.max_age() {
            let f = get_available_moisture(world, pos, block).await;
            if rand::rng().random_range(0..=(25.0 / f).floor() as i64) == 0 {
                let mut new_state_id = self.state_with_age(block, state, age + 1);
                if let Some(server) = world.server.upgrade() {
                    let mut event = BlockGrowEvent::new(
                        world.clone(),
                        block,
                        state,
                        Block::from_state_id(new_state_id),
                        new_state_id,
                        *pos,
                    );
                    server.plugin_manager.fire(&server, &mut event).await;
                    if event.cancelled {
                        return;
                    }
                    new_state_id = event.new_state_id;
                }
                world
                    .set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS)
                    .await;
            }
        }
    }

    //TODO add impl for light level
}

pub async fn get_available_moisture(world: &Arc<World>, pos: &BlockPos, block: &Block) -> f32 {
    let mut moisture = 1.0;
    let down_pos = pos.down();

    for dx in -1..=1 {
        for dz in -1..=1 {
            let mut local_moisture = 0.0;

            let (block, block_state) =
                world.get_block_and_state_id(&down_pos.offset(Vector3 { x: dx, y: 0, z: dz }));
            if block == &Block::FARMLAND {
                local_moisture = 1.0;
                let props = FarmlandProperties::from_state_id(block_state, block);
                if props.moisture != 0 {
                    local_moisture = 3.0;
                }
            }

            if dx != 0 || dz != 0 {
                local_moisture /= 4.0;
            }

            moisture += local_moisture;
        }
    }

    let north = pos.offset(North.to_offset());
    let south = pos.offset(South.to_offset());
    let west = pos.offset(West.to_offset());
    let east = pos.offset(East.to_offset());
    let horizontal = world.get_block(&west) == block || world.get_block(&east) == block;
    let vertical = world.get_block(&north) == block || world.get_block(&south) == block;
    if (horizontal && vertical)
        || world.get_block(&west.offset(North.to_offset())) == block
        || world.get_block(&east.offset(North.to_offset())) == block
        || world.get_block(&east.offset(South.to_offset())) == block
        || world.get_block(&west.offset(South.to_offset())) == block
    {
        moisture /= 2.0;
    }

    moisture
}
