use godot::{classes::RandomNumberGenerator, prelude::*};

use crate::{
    consts,
    core::event_bus::EventBus,
    data::hazard_def::{HazardDef, HazardType},
    systems::hazard_controller::HazardController,
};

#[derive(GodotClass)]
#[class(base=Node)]
pub struct HazardSystem {
    #[export]
    possible_hazards: Array<Gd<HazardDef>>,
    #[export]
    spawn_locations: Array<Gd<Node2D>>,
    #[export]
    hazard_prefab: Option<Gd<PackedScene>>,
    #[export]
    check_interval: f32,

    current_focus: f32,
    timer: f32,
    rng: Gd<RandomNumberGenerator>,
    event_bus: Option<Gd<EventBus>>,

    base: Base<Node>,
}

#[godot_api]
impl INode for HazardSystem {
    fn init(base: Base<Node>) -> Self {
        Self {
            possible_hazards: Array::new(),
            spawn_locations: Array::new(),
            hazard_prefab: None,
            check_interval: 1.0,
            timer: 0.0,
            current_focus: 0.0,
            rng: RandomNumberGenerator::new_gd(),
            event_bus: None,
            base,
        }
    }

    fn ready(&mut self) {
        let mut bus = self.base().get_node_as::<EventBus>(consts::EVENT_BUS_PATH);

        bus.connect(
            consts::FOCUS_CHANGED,
            &self.base().callable("on_focus_changed"),
        );

        bus.connect(
            consts::HAZARD_RESOLVED,
            &self.base().callable("on_hazard_resolved"),
        );

        bus.connect(consts::DAY_STARTED, &self.base().callable("on_day_started"));

        self.event_bus = Some(bus);
    }

    fn process(&mut self, delta: f64) {
        self.timer += delta as f32;
        if self.timer >= self.check_interval {
            self.timer = 0.0;
            self.try_spawn_hazard();
        }
    }
}

#[godot_api]
impl HazardSystem {
    #[func]
    pub fn clear_hazards(&mut self) {
        for location in self.spawn_locations.iter_shared() {
            for mut child in location.get_children().iter_shared() {
                child.queue_free();
            }
        }
        self.timer = 0.0;
    }

    #[func]
    pub fn set_available_hazards(&mut self, hazards: Array<Gd<HazardDef>>) {
        self.possible_hazards = hazards;
    }

    #[func]
    fn on_focus_changed(&mut self, focus: f32) {
        self.current_focus = focus;
    }

    #[func]
    fn on_hazard_resolved(&mut self, hazard_type: HazardType) {
        godot_print!("Hazard resolved: {:?}", hazard_type);
    }

    fn try_spawn_hazard(&mut self) {
        if self.current_focus < 0.2 {
            return;
        }

        let spawn_chance = self.current_focus * 0.5;
        if self.rng.randf() < spawn_chance {
            self.spawn_random_hazard();
        }
    }

    fn spawn_random_hazard(&mut self) {
        if self.possible_hazards.is_empty()
            || self.spawn_locations.is_empty()
            || self.hazard_prefab.is_none()
        {
            return;
        }

        let empty_locations: Vec<Gd<Node2D>> = self
            .spawn_locations
            .iter_shared()
            .filter(|loc| loc.get_child_count() == 0)
            .collect();

        if empty_locations.is_empty() {
            return;
        }

        let loc_idx = self.rng.randi() as usize % empty_locations.len();
        let mut target_loc = empty_locations[loc_idx].clone();

        let valid_hazards: Vec<Gd<HazardDef>> = self
            .possible_hazards
            .iter_shared()
            .filter(|h| h.bind().min_focus_to_spawn <= self.current_focus)
            .collect();

        if valid_hazards.is_empty() {
            return;
        }

        let haz_idx = self.rng.randi() as usize % valid_hazards.len();
        let selected_hazard = valid_hazards[haz_idx].clone();
        let hazard_type = selected_hazard.bind().hazard_type.clone();

        if let Some(prefab) = &self.hazard_prefab {
            if let Some(instance) = prefab.instantiate() {
                if let Ok(mut hazard_node) = instance.try_cast::<HazardController>() {
                    hazard_node
                        .clone()
                        .upcast::<Node2D>()
                        .set_position(Vector2::ZERO);

                    target_loc.add_child(&hazard_node.clone().upcast::<Node>());

                    hazard_node.bind_mut().initialize(selected_hazard);

                    if let Some(bus) = &self.event_bus {
                        bus.clone()
                            .bind_mut()
                            .publish_hazard_spawned(hazard_type.clone());
                    }

                    godot_print!(
                        "Spawned hazard {:?} at location {:?}",
                        hazard_type,
                        target_loc.get_name()
                    );
                } else {
                    godot_print!("Error: Hazard Prefab root is not a HazardController!");
                }
            }
        }
    }

    #[func]
    fn on_day_started(&mut self, _day_index: i32) {
        self.clear_hazards();
        self.timer = 0.0;
        self.current_focus = 0.0;
    }
}
