use std::time::Duration;

use calloop::timer::{TimeoutAction, Timer};
use calloop::LoopHandle;

use crate::forge::Forge;

const POLL_INTERVAL_MS: u64 = 2000;
const TRANSITION_STEP: f64 = 0.05;

/// Auto-brightness buckets mapping lux ranges to brightness levels.
const BRIGHTNESS_BUCKETS: [(f64, f64); 7] = [
    (0.0, 0.05),     // Very dark → minimum
    (10.0, 0.15),    // Dark room
    (50.0, 0.30),    // Dim room
    (200.0, 0.50),   // Indoor
    (500.0, 0.70),   // Bright indoor
    (2000.0, 0.85),  // Cloudy outdoor
    (10000.0, 1.0),  // Bright sunlight → maximum
];

pub struct AutoBrightness {
    pub enabled: bool,
    pub offset: f64, // User offset from auto value (-1.0 to 1.0)
    current_lux: f64,
    target_brightness: f64,
    current_brightness: f64,
    sensor_path: Option<String>,
}

impl AutoBrightness {
    pub fn new() -> Self {
        let sensor_path = find_als_sensor();

        Self {
            enabled: sensor_path.is_some(),
            offset: 0.0,
            current_lux: 200.0,
            target_brightness: 0.5,
            current_brightness: 0.5,
            sensor_path,
        }
    }

    /// Start auto-brightness polling timer.
    pub fn start_polling(event_loop: &LoopHandle<'static, Forge>) {
        let timer = Timer::from_duration(Duration::from_millis(POLL_INTERVAL_MS));
        event_loop
            .insert_source(timer, |_, _, forge| {
                // Skip brightness polling when display is off — saves CPU wakeups
                if !forge.display_on {
                    return TimeoutAction::ToDuration(Duration::from_millis(POLL_INTERVAL_MS));
                }

                if forge.auto_brightness.enabled {
                    forge.auto_brightness.poll_and_adjust();
                }
                TimeoutAction::ToDuration(Duration::from_millis(POLL_INTERVAL_MS))
            })
            .expect("auto-brightness timer");
    }

    fn poll_and_adjust(&mut self) {
        if let Some(lux) = self.read_lux() {
            self.current_lux = lux;
            self.target_brightness = lux_to_brightness(lux);

            // Apply user offset
            let target = (self.target_brightness + self.offset).clamp(0.05, 1.0);

            // Gradual transition
            if (self.current_brightness - target).abs() > 0.01 {
                if self.current_brightness < target {
                    self.current_brightness = (self.current_brightness + TRANSITION_STEP).min(target);
                } else {
                    self.current_brightness = (self.current_brightness - TRANSITION_STEP).max(target);
                }

                crate::backlight::set(self.current_brightness);
            }
        }
    }

    fn read_lux(&self) -> Option<f64> {
        let path = self.sensor_path.as_ref()?;
        std::fs::read_to_string(path)
            .ok()
            .and_then(|s| s.trim().parse::<f64>().ok())
    }

    /// Set brightness to a specific percentage (0-100).
    pub fn set_brightness(&mut self, percent: u8) {
        crate::backlight::set(percent as f64 / 100.0);
        self.current_brightness = percent as f64 / 100.0;
    }
}

fn lux_to_brightness(lux: f64) -> f64 {
    // Interpolate between brightness buckets
    for i in 1..BRIGHTNESS_BUCKETS.len() {
        let (lux_low, bright_low) = BRIGHTNESS_BUCKETS[i - 1];
        let (lux_high, bright_high) = BRIGHTNESS_BUCKETS[i];

        if lux <= lux_high {
            let t = if lux_high > lux_low {
                (lux - lux_low) / (lux_high - lux_low)
            } else {
                0.0
            };
            return bright_low + t * (bright_high - bright_low);
        }
    }

    1.0 // Maximum brightness for very high lux
}

fn find_als_sensor() -> Option<String> {
    // Search for ambient light sensor in IIO subsystem
    let iio_base = "/sys/bus/iio/devices";

    if let Ok(entries) = std::fs::read_dir(iio_base) {
        for entry in entries.flatten() {
            let path = entry.path();
            let name_path = path.join("name");

            if let Ok(name) = std::fs::read_to_string(&name_path) {
                let name = name.trim();
                // Common ALS sensor names
                if name.contains("als") || name.contains("light") || name.contains("opt3001")
                    || name.contains("tsl2563") || name.contains("apds9960")
                    || name.contains("veml6070") || name.contains("bh1750")
                {
                    // Check for illuminance channel
                    let lux_path = path.join("in_illuminance_raw");
                    if lux_path.exists() {
                        return Some(lux_path.to_string_lossy().to_string());
                    }

                    let lux_path = path.join("in_illuminance_input");
                    if lux_path.exists() {
                        return Some(lux_path.to_string_lossy().to_string());
                    }
                }
            }
        }
    }

    None
}
