use std::collections::BTreeMap;
use std::time::{Duration, Instant};

use crate::bus::{CanBus, CanFrame};
use crate::error::{Error, Result};
use crate::motor::MotorConfig;
use crate::protocol::{
    DirectCommand, MitCommand, MitHelperCommand, MotorFeedback, direct_command_frame,
    mit_command_frame, mit_helper_frame,
};

#[derive(Debug)]
pub struct MotorManager<B> {
    bus: B,
    motors: BTreeMap<u8, MotorConfig>,
    labels: BTreeMap<String, u8>,
    last_feedback: BTreeMap<u8, MotorFeedback>,
}

impl<B: CanBus> MotorManager<B> {
    pub fn new(bus: B, configs: impl IntoIterator<Item = MotorConfig>) -> Result<Self> {
        let mut motors = BTreeMap::new();
        for config in configs {
            config.refresh_period()?;
            let motor_id = config.motor_id;
            if motors.insert(motor_id, config).is_some() {
                return Err(Error::DuplicateMotorId(motor_id));
            }
        }

        Ok(Self {
            bus,
            motors,
            labels: BTreeMap::new(),
            last_feedback: BTreeMap::new(),
        })
    }

    pub fn with_labels(mut self, labels: impl IntoIterator<Item = (String, u8)>) -> Result<Self> {
        for (label, motor_id) in labels {
            if !self.motors.contains_key(&motor_id) {
                return Err(Error::UnknownMotorId(motor_id));
            }
            self.labels.insert(label, motor_id);
        }
        Ok(self)
    }

    pub fn bus(&self) -> &B {
        &self.bus
    }

    pub fn bus_mut(&mut self) -> &mut B {
        &mut self.bus
    }

    pub fn into_inner(self) -> B {
        self.bus
    }

    pub fn motor_ids(&self) -> impl Iterator<Item = u8> + '_ {
        self.motors.keys().copied()
    }

    pub fn config(&self, motor_id: u8) -> Result<&MotorConfig> {
        self.motors
            .get(&motor_id)
            .ok_or(Error::UnknownMotorId(motor_id))
    }

    pub fn id_for_label(&self, label: &str) -> Result<u8> {
        self.labels
            .get(label)
            .copied()
            .ok_or_else(|| Error::UnknownMotorLabel(label.to_owned()))
    }

    pub fn last_feedback(&self, motor_id: u8) -> Option<&MotorFeedback> {
        self.last_feedback.get(&motor_id)
    }

    pub fn send_mit_command(&mut self, motor_id: u8, command: MitCommand) -> Result<()> {
        let config = self.config(motor_id)?;
        let frame = mit_command_frame(config.model, config.motor_id, command)?;
        self.bus.send(&frame)
    }

    pub fn send_direct_command(&mut self, motor_id: u8, command: DirectCommand) -> Result<()> {
        let config = self.config(motor_id)?;
        let frame = direct_command_frame(config.motor_id, command)?;
        self.bus.send(&frame)
    }

    pub fn send_helper(&mut self, motor_id: u8, helper: MitHelperCommand) -> Result<()> {
        let config = self.config(motor_id)?;
        let frame = mit_helper_frame(config.model, config.motor_id, helper)?;
        self.bus.send(&frame)
    }

    pub fn send_keepalive_all(&mut self) -> Result<()> {
        let frames = self
            .motors
            .values()
            .map(|config| mit_command_frame(config.model, config.motor_id, MitCommand::neutral()))
            .collect::<Result<Vec<_>>>()?;

        for frame in frames {
            self.bus.send(&frame)?;
        }

        Ok(())
    }

    pub fn stop_all(&mut self) -> Result<()> {
        let frames = self
            .motors
            .values()
            .flat_map(|config| {
                [
                    mit_command_frame(config.model, config.motor_id, MitCommand::neutral()),
                    mit_helper_frame(config.model, config.motor_id, MitHelperCommand::Disable),
                ]
            })
            .collect::<Result<Vec<_>>>()?;

        for frame in frames {
            self.bus.send(&frame)?;
        }

        Ok(())
    }

    pub fn receive_feedback(&mut self, timeout: Duration) -> Result<Option<(u8, MotorFeedback)>> {
        let deadline = Instant::now() + timeout;

        loop {
            let now = Instant::now();
            if now >= deadline {
                return Ok(None);
            }

            let Some(frame) = self.bus.receive(deadline.saturating_duration_since(now))? else {
                return Ok(None);
            };

            if let Some(parsed) = self.parse_feedback_frame(&frame)? {
                self.last_feedback.insert(parsed.0, parsed.1.clone());
                return Ok(Some(parsed));
            }
        }
    }

    fn parse_feedback_frame(&self, frame: &CanFrame) -> Result<Option<(u8, MotorFeedback)>> {
        for (motor_id, config) in &self.motors {
            if config.feedback_filter().matches(frame) {
                return Ok(Some((
                    *motor_id,
                    config.model.parse_feedback(frame.data())?,
                )));
            }
        }
        Ok(None)
    }
}
