// SPDX-License-Identifier: GPL-3.0-or-later

/// Schedule persistence — load/save schedule profiles from settings.
use anyhow::Result;
use std::fs;
use std::path::PathBuf;

use super::model::ScheduleProfile;

/// Load schedule profiles from the default config file.
pub fn load() -> Result<Vec<ScheduleProfile>> {
    let path = schedules_path()?;
    if !path.exists() {
        return Ok(Vec::new());
    }
    let content = fs::read_to_string(&path)?;
    let profiles: Vec<ScheduleProfile> = serde_json::from_str(&content)?;
    Ok(profiles)
}

/// Load schedule profiles from a specific path (for testing).
pub fn load_from(path: &std::path::Path) -> Result<Vec<ScheduleProfile>> {
    if !path.exists() {
        return Ok(Vec::new());
    }
    let content = fs::read_to_string(path)?;
    let profiles: Vec<ScheduleProfile> = serde_json::from_str(&content)?;
    Ok(profiles)
}

/// Save schedule profiles to the default config file.
pub fn save(profiles: &[ScheduleProfile]) -> Result<()> {
    let path = schedules_path()?;
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    let content = serde_json::to_string_pretty(profiles)?;
    fs::write(&path, content)?;
    Ok(())
}

/// Save schedule profiles to a specific path (for testing).
pub fn save_to(profiles: &[ScheduleProfile], path: &std::path::Path) -> Result<()> {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    let content = serde_json::to_string_pretty(profiles)?;
    fs::write(path, content)?;
    Ok(())
}

/// Get a profile by ID.
pub fn get_by_id(id: &str) -> Result<Option<ScheduleProfile>> {
    let profiles = load()?;
    Ok(profiles.into_iter().find(|p| p.id == id))
}

/// Add a new profile.
pub fn add(profile: ScheduleProfile) -> Result<()> {
    let mut profiles = load()?;
    profiles.push(profile);
    save(&profiles)
}

/// Update an existing profile by ID.
pub fn update(updated: ScheduleProfile) -> Result<bool> {
    let mut profiles = load()?;
    if let Some(idx) = profiles.iter().position(|p| p.id == updated.id) {
        profiles[idx] = updated;
        save(&profiles)?;
        Ok(true)
    } else {
        Ok(false)
    }
}

/// Delete a profile by ID.
pub fn delete(id: &str) -> Result<bool> {
    let mut profiles = load()?;
    let len_before = profiles.len();
    profiles.retain(|p| p.id != id);
    if profiles.len() < len_before {
        save(&profiles)?;
        Ok(true)
    } else {
        Ok(false)
    }
}

fn schedules_path() -> Result<PathBuf> {
    let config_dir = dirs::config_dir()
        .ok_or_else(|| anyhow::anyhow!("Could not find config directory"))?;
    Ok(config_dir.join("temperlux").join("schedules.json"))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::schedule::model::ScheduleTrigger;

    #[test]
    fn test_save_and_load() {
        let dir = tempfile::tempdir().unwrap();
        let path = dir.path().join("schedules.json");

        let mut p1 = crate::schedule::model::ScheduleProfile::new("Morning".into());
        p1.set_entry("ddc:1".into(), 50.0);
        p1.triggers.push(ScheduleTrigger::Absolute { time: "08:00".into() });

        let mut p2 = crate::schedule::model::ScheduleProfile::new("Evening".into());
        p2.set_entry("ddc:2".into(), 30.0);
        p2.triggers.push(ScheduleTrigger::Sunset { offset_minutes: 30 });

        save_to(&[p1.clone(), p2.clone()], &path).unwrap();
        let loaded = load_from(&path).unwrap();

        assert_eq!(loaded.len(), 2);
        assert_eq!(loaded[0].name, "Morning");
        assert_eq!(loaded[1].name, "Evening");
    }

    #[test]
    fn test_load_missing_file() {
        let dir = tempfile::tempdir().unwrap();
        let path = dir.path().join("nonexistent.json");
        let loaded = load_from(&path).unwrap();
        assert!(loaded.is_empty());
    }

    #[test]
    fn test_delete() {
        let dir = tempfile::tempdir().unwrap();
        let path = dir.path().join("schedules.json");

        let p1 = crate::schedule::model::ScheduleProfile::new("A".into());
        let p2 = crate::schedule::model::ScheduleProfile::new("B".into());
        let id1 = p1.id.clone();

        save_to(&[p1, p2], &path).unwrap();

        // Delete by modifying the file directly (testing the model)
        let mut profiles = load_from(&path).unwrap();
        profiles.retain(|p| p.id != id1);
        save_to(&profiles, &path).unwrap();

        let loaded = load_from(&path).unwrap();
        assert_eq!(loaded.len(), 1);
        assert_eq!(loaded[0].name, "B");
    }

    #[test]
    fn test_update_existing() {
        let dir = tempfile::tempdir().unwrap();
        let path = dir.path().join("schedules.json");

        let mut p1 = crate::schedule::model::ScheduleProfile::new("A".into());
        let id = p1.id.clone();
        save_to(&[p1.clone()], &path).unwrap();

        // Update name
        p1.name = "A-updated".into();
        let mut profiles = load_from(&path).unwrap();
        if let Some(p) = profiles.iter_mut().find(|p| p.id == id) {
            *p = p1;
        }
        save_to(&profiles, &path).unwrap();

        let loaded = load_from(&path).unwrap();
        assert_eq!(loaded[0].name, "A-updated");
    }

    #[test]
    fn test_roundtrip_multiple_triggers() {
        let dir = tempfile::tempdir().unwrap();
        let path = dir.path().join("schedules.json");

        let mut p = crate::schedule::model::ScheduleProfile::new("Full".into());
        p.set_entry("ddc:1".into(), 50.0);
        p.set_entry("ddc:2".into(), 80.0);
        p.triggers.push(ScheduleTrigger::Absolute { time: "08:00".into() });
        p.triggers.push(ScheduleTrigger::Sunrise { offset_minutes: -30 });
        p.triggers.push(ScheduleTrigger::Sunset { offset_minutes: 45 });
        p.enabled = false;

        save_to(&[p], &path).unwrap();
        let loaded = load_from(&path).unwrap();
        assert_eq!(loaded.len(), 1);
        assert_eq!(loaded[0].triggers.len(), 3);
        assert!(!loaded[0].enabled);
        assert_eq!(loaded[0].entries.len(), 2);
    }

    #[test]
    fn test_empty_file_returns_error() {
        let dir = tempfile::tempdir().unwrap();
        let path = dir.path().join("schedules.json");
        std::fs::write(&path, "").unwrap();
        let result = load_from(&path);
        // Empty file is not valid JSON array
        assert!(result.is_err());
    }
}
