use std::sync::Arc;

use pumpkin_data::sound::SoundCategory;
use pumpkin_data::translation;
use pumpkin_util::text::TextComponent;
use rand::{RngExt, rng};

use crate::entity::EntityBase;
use crate::{
    command::{
        CommandExecutor, CommandResult, CommandSender,
        args::{
            Arg, ConsumedArgs, FindArg, bounded_num::BoundedNumArgumentConsumer,
            players::PlayersArgumentConsumer, position_3d::Position3DArgumentConsumer,
            sound::SoundArgumentConsumer, sound_category::SoundCategoryArgumentConsumer,
        },
        dispatcher::CommandError,
        tree::{CommandTree, builder::argument},
    },
    entity::player::Player,
};

/// Command: playsound <sound> [<source>] [<targets>] [<pos>] [<volume>] [<pitch>] [<minVolume>]
///
/// Plays a sound at specified position for target players.
/// - sound: The sound resource location to play
/// - source: Sound category (master, music, record, etc.)
/// - targets: Players who will hear the sound
/// - pos: Position to play the sound from
/// - volume: Sound volume (>=0, default: 1.0)
/// - pitch: Sound pitch (0.5-2.0, default: 1.0)
/// - minVolume: Minimum volume for distant players (0.0-1.0, default: 0.0)
const NAMES: [&str; 1] = ["playsound"];
const DESCRIPTION: &str = "Plays a sound at a position.";
const ARG_SOUND: &str = "sound";
const ARG_SOURCE: &str = "source";
const ARG_TARGETS: &str = "targets";
const ARG_POS: &str = "pos";
const ARG_VOLUME: &str = "volume";
const ARG_PITCH: &str = "pitch";
const ARG_MIN_VOLUME: &str = "minVolume";

// Volume must be >= 0, no upper limit
const fn volume_consumer() -> BoundedNumArgumentConsumer<f32> {
    BoundedNumArgumentConsumer::new().name(ARG_VOLUME).min(0.0)
}

// Pitch must be between 0.0 and 2.0
// Values below 0.5 are treated as 0.5
const fn pitch_consumer() -> BoundedNumArgumentConsumer<f32> {
    BoundedNumArgumentConsumer::new()
        .name(ARG_PITCH)
        .min(0.0)
        .max(2.0)
}

// Minimum volume must be between 0.0 and 1.0
// Controls the volume for players outside normal hearing range
const fn min_volume_consumer() -> BoundedNumArgumentConsumer<f32> {
    BoundedNumArgumentConsumer::new()
        .name(ARG_MIN_VOLUME)
        .min(0.0)
        .max(1.0)
}

struct Executor;

impl CommandExecutor for Executor {
    fn execute<'a>(
        &'a self,
        sender: &'a CommandSender,
        _server: &'a crate::server::Server,
        args: &'a ConsumedArgs<'a>,
    ) -> CommandResult<'a> {
        Box::pin(async move {
            // Get required sound argument
            let sound = SoundArgumentConsumer::find_arg(args, ARG_SOUND)?;

            // Get optional sound category, defaults to Master
            let source = args
                .get(ARG_SOURCE)
                .map_or(SoundCategory::Master, |arg| match arg {
                    Arg::SoundCategory(category) => *category,
                    _ => SoundCategory::Master,
                });

            // Get target players, defaults to sender if not specified
            let targets: &[Arc<Player>] = match (
                PlayersArgumentConsumer::find_arg(args, ARG_TARGETS),
                sender.as_player(),
            ) {
                (Ok(players), _) => players,
                (_, Some(player)) => &[player],
                (_, _) => &[],
            };

            // Get optional position, defaults to target's position
            let position = Position3DArgumentConsumer::find_arg(args, ARG_POS).ok();

            // Get optional volume parameter
            let volume = match BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_VOLUME) {
                Ok(Ok(v)) => v,
                _ => 1.0, // Default volume
            };

            // Get optional pitch parameter
            let pitch = match BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_PITCH) {
                Ok(Ok(p)) => p.max(0.5), // Values below 0.5 are clamped
                _ => 1.0,                // Default pitch
            };

            // Get optional minimum volume (currently unused in implementation)
            let min_volume = match BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_MIN_VOLUME)
            {
                Ok(Ok(v)) => v,
                _ => 0.0, // Default minimum volume
            };

            // Use same random seed for all targets to ensure sound synchronization
            let seed = rng().random::<f64>();

            // Track how many players actually received the sound
            let mut players_who_heard = 0;

            // Play sound for each target player
            for target in targets {
                let pos = position.unwrap_or(target.living_entity.entity.pos.load());

                // Check if player can hear the sound based on volume and distance
                let player_pos = target.living_entity.entity.pos.load();
                let distance = player_pos.squared_distance_to_vec(&pos);
                let max_distance: f64 = (16.0 * volume).into(); // 16 blocks is base distance at volume 1.0

                if distance <= max_distance || min_volume > 0.0 {
                    target
                        .play_sound(sound as u16, source, &pos, volume, pitch, seed)
                        .await;
                    players_who_heard += 1;
                }
            }

            // Send appropriate message based on results
            if players_who_heard == 0 {
                Err(CommandError::CommandFailed(TextComponent::translate_cross(
                    translation::java::COMMANDS_PLAYSOUND_FAILED,
                    translation::java::COMMANDS_PLAYSOUND_FAILED,
                    [],
                )))
            } else {
                let sound_name = sound.to_name();
                if players_who_heard == 1 {
                    sender
                        .send_message(TextComponent::translate_cross(
                            translation::java::COMMANDS_PLAYSOUND_SUCCESS_SINGLE,
                            translation::bedrock::COMMANDS_PLAYSOUND_SUCCESS,
                            [
                                TextComponent::text(sound_name),
                                targets[0].get_display_name().await,
                            ],
                        ))
                        .await;
                } else {
                    sender
                        .send_message(TextComponent::translate_cross(
                            translation::java::COMMANDS_PLAYSOUND_SUCCESS_MULTIPLE,
                            translation::bedrock::COMMANDS_PLAYSOUND_SUCCESS,
                            [
                                TextComponent::text(sound_name),
                                TextComponent::text(players_who_heard.to_string()),
                            ],
                        ))
                        .await;
                }

                Ok(players_who_heard)
            }
        })
    }
}

pub fn init_command_tree() -> CommandTree {
    CommandTree::new(NAMES, DESCRIPTION).then(
        argument(ARG_SOUND, SoundArgumentConsumer)
            .then(
                argument(ARG_SOURCE, SoundCategoryArgumentConsumer)
                    .then(
                        argument(ARG_TARGETS, PlayersArgumentConsumer)
                            .then(
                                argument(ARG_POS, Position3DArgumentConsumer)
                                    .then(
                                        argument(ARG_VOLUME, volume_consumer())
                                            .then(
                                                argument(ARG_PITCH, pitch_consumer())
                                                    .then(
                                                        argument(
                                                            ARG_MIN_VOLUME,
                                                            min_volume_consumer(),
                                                        )
                                                        .execute(Executor),
                                                    )
                                                    .execute(Executor),
                                            )
                                            .execute(Executor),
                                    )
                                    .execute(Executor),
                            )
                            .execute(Executor),
                    )
                    .execute(Executor),
            )
            .execute(Executor),
    )
}
