use crate::core::model::{Category, ColorTag, State, SubTask, Todo};
use crate::core::store::Store;
use anyhow::{bail, Result};
use chrono::Local;

pub fn add_todo(store: &dyn Store, category: Category, title: String, notes: Option<String>, created_at: Option<i64>, updated_at: Option<i64>) -> Result<u64> {
    let mut state = store.load()?;
    let id = state.next_id;
    state.next_id += 1;
    
    let now = Local::now().timestamp();
    let todo = Todo {
        id,
        title,
        notes,
        created_at: created_at.unwrap_or(now),
        updated_at: updated_at.unwrap_or(now),
        color: ColorTag::None,
        subtasks: Vec::new(),
    };
    
    state.get_category_mut(category).push(todo);
    store.save(&state)?;
    Ok(id)
}

pub fn edit_todo(store: &dyn Store, id: u64, title: Option<String>, notes: Option<String>, created_at: Option<i64>, updated_at: Option<i64>) -> Result<()> {
    let mut state = store.load()?;
    let now = Local::now().timestamp();
    
    for cat in State::all_categories() {
        let vec = state.get_category_mut(cat);
        if let Some(todo) = vec.iter_mut().find(|t| t.id == id) {
            if let Some(t) = title { todo.title = t; }
            if let Some(n) = notes { todo.notes = Some(n); }
            if let Some(c) = created_at { todo.created_at = c; }
            if let Some(u) = updated_at { todo.updated_at = u; }
            else { todo.updated_at = now; }
            store.save(&state)?;
            return Ok(());
        }
    }
    bail!("Todo not found: {}", id)
}

pub fn move_todo(store: &dyn Store, id: u64, target_cat: Category) -> Result<()> {
    let mut state = store.load()?;
    let now = Local::now().timestamp();
    
    let mut found_todo: Option<Todo> = None;
    
    // Remove from old location
    for cat in State::all_categories() {
        let vec = state.get_category_mut(cat);
        if let Some(idx) = vec.iter().position(|t| t.id == id) {
            found_todo = Some(vec.remove(idx));
            break;
        }
    }
    
    if let Some(mut todo) = found_todo {
        todo.updated_at = now;
        state.get_category_mut(target_cat).push(todo);
        store.save(&state)?;
        Ok(())
    } else {
        bail!("Todo not found: {}", id)
    }
}

pub fn remove_todo(store: &dyn Store, id: u64) -> Result<()> {
    let mut state = store.load()?;
    
    for cat in State::all_categories() {
        let vec = state.get_category_mut(cat);
        if let Some(idx) = vec.iter().position(|t| t.id == id) {
            vec.remove(idx);
            store.save(&state)?;
            return Ok(());
        }
    }
    bail!("Todo not found: {}", id)
}

pub fn toggle_done(store: &dyn Store, id: u64) -> Result<()> {
    let state = store.load()?;
    let is_completed = state.completed.iter().any(|t| t.id == id);
    
    if is_completed {
        move_todo(store, id, Category::Short)
    } else {
        move_todo(store, id, Category::Completed)
    }
}

// Helper to find parent vec of an item and swap
pub fn reorder_item(store: &dyn Store, id: u64, move_up: bool) -> Result<()> {
    let mut state = store.load()?;
    let now = Local::now().timestamp();
    let mut modified = false;

    // Check Categories (Level 1 items)
    for cat in State::all_categories() {
        let vec = state.get_category_mut(cat);
        if let Some(idx) = vec.iter().position(|t| t.id == id) {
            // Found in root
            if move_up {
                if idx > 0 {
                    vec.swap(idx, idx - 1);
                    vec[idx].updated_at = now;
                    vec[idx-1].updated_at = now;
                    modified = true;
                }
            } else if idx < vec.len() - 1 {
                vec.swap(idx, idx + 1);
                vec[idx].updated_at = now;
                vec[idx+1].updated_at = now;
                modified = true;
            }
            if modified { break; }
        }
        
        // Recursive check for subtasks
        for todo in vec.iter_mut() {
            if reorder_in_subs(&mut todo.subtasks, id, move_up, now) {
                todo.updated_at = now;
                modified = true;
                break;
            }
        }
        if modified { break; }
    }
    
    if modified {
         store.save(&state)?;
         Ok(())
    } else {
         // Either not found or boundary reached (cannot move further)
         // We return Ok even if no move happened? Or bail?
         // TUI expects simple return. 
         Ok(())
    }
}

fn reorder_in_subs(subs: &mut [SubTask], id: u64, move_up: bool, now: i64) -> bool {
    // Check direct children
    if let Some(idx) = subs.iter().position(|s| s.id == id) {
        if move_up {
            if idx > 0 {
                subs.swap(idx, idx - 1);
                subs[idx].updated_at = now;
                subs[idx-1].updated_at = now;
                return true;
            }
        } else if idx < subs.len() - 1 {
            subs.swap(idx, idx + 1);
            subs[idx].updated_at = now;
            subs[idx+1].updated_at = now;
            return true;
        }
        return false; // Found but couldn't move
    }
    
    // Recurse
    for sub in subs.iter_mut() {
        if reorder_in_subs(&mut sub.subtasks, id, move_up, now) {
            sub.updated_at = now;
            return true;
        }
    }
    false
}

// Recursive add subtask
pub fn add_subtask(store: &dyn Store, _parent_id: u64, title: String, notes: Option<String>, created_at: Option<i64>, updated_at: Option<i64>) -> Result<u64> {
    let mut state = store.load()?;
    let id = state.next_id;
    state.next_id += 1;
    let now = Local::now().timestamp();
    
    let sub = SubTask {
        id,
        title,
        done: false,
        created_at: created_at.unwrap_or(now),
        updated_at: updated_at.unwrap_or(now),
        color: ColorTag::None,
        notes,
        subtasks: Vec::new(),
    };
    
    let mut added = false;
    for cat in State::all_categories() {
        let vec = state.get_category_mut(cat);
        for todo in vec.iter_mut() {
            if todo.id == _parent_id {
                todo.subtasks.push(sub.clone());
                todo.updated_at = now;
                added = true;
                break;
            }
            // Recurse
            if add_to_sub(&mut todo.subtasks, _parent_id, &sub, now) {
                todo.updated_at = now;
                added = true;
                break;
            }
        }
        if added { break; }
    }
    
    if added {
        store.save(&state)?;
        Ok(id)
    } else {
        bail!("Parent not found: {}", _parent_id)
    }
}

fn add_to_sub(subs: &mut [SubTask], parent_id: u64, new_sub: &SubTask, now: i64) -> bool {
    for sub in subs.iter_mut() {
        if sub.id == parent_id {
            sub.subtasks.push(new_sub.clone());
            sub.updated_at = now;
            return true;
        }
        if add_to_sub(&mut sub.subtasks, parent_id, new_sub, now) {
            sub.updated_at = now;
            return true;
        }
    }
    false
}

pub fn edit_subtask(store: &dyn Store, _parent_id: u64, sub_id: u64, title: Option<String>, done: Option<bool>, notes: Option<String>, created_at: Option<i64>, updated_at: Option<i64>) -> Result<()> {
    let mut state = store.load()?;
    let now = Local::now().timestamp();
    
    let mut found = false;
    for cat in State::all_categories() {
        let vec = state.get_category_mut(cat);
        for todo in vec.iter_mut() {
             if edit_in_subs(&mut todo.subtasks, sub_id, &title, done, &notes, created_at, updated_at, now) {
                 todo.updated_at = now;
                 found = true;
                 break;
             }
        }
        if found { break; }
    }
    
    if found {
        store.save(&state)?;
        Ok(())
    } else {
        bail!("Subtask not found: {}", sub_id)
    }
}

fn edit_in_subs(subs: &mut [SubTask], sub_id: u64, title: &Option<String>, done: Option<bool>, notes: &Option<String>, created_at: Option<i64>, updated_at: Option<i64>, now: i64) -> bool {
    for sub in subs.iter_mut() {
        if sub.id == sub_id {
            if let Some(t) = title { sub.title = t.clone(); }
            if let Some(d) = done { sub.done = d; }
            if let Some(n) = notes { sub.notes = Some(n.clone()); }
            if let Some(c) = created_at { sub.created_at = c; }
            if let Some(u) = updated_at { sub.updated_at = u; }
            else { sub.updated_at = now; }
            return true;
        }
        if edit_in_subs(&mut sub.subtasks, sub_id, title, done, notes, created_at, updated_at, now) {
            sub.updated_at = now;
            return true;
        }
    }
    false
}

pub fn remove_subtask(store: &dyn Store, _parent_id: u64, sub_id: u64) -> Result<()> {
    // Ignore parent_id, search by sub_id
    let mut state = store.load()?;
    let now = Local::now().timestamp();
    
    let mut found = false;
    for cat in State::all_categories() {
        let vec = state.get_category_mut(cat);
        for todo in vec.iter_mut() {
            if remove_from_subs(&mut todo.subtasks, sub_id, now) {
                todo.updated_at = now;
                found = true;
                break;
            }
        }
        if found { break; }
    }
    
    if found {
        store.save(&state)?;
        Ok(())
    } else {
        bail!("Subtask not found: {}", sub_id)
    }
}

#[allow(clippy::ptr_arg)]
fn remove_from_subs(subs: &mut Vec<SubTask>, sub_id: u64, now: i64) -> bool {
    if let Some(idx) = subs.iter().position(|s| s.id == sub_id) {
        subs.remove(idx);
        return true;
    }
    
    for sub in subs.iter_mut() {
        if remove_from_subs(&mut sub.subtasks, sub_id, now) {
            sub.updated_at = now;
            return true;
        }
    }
    false
}

pub fn set_color(store: &dyn Store, id: u64, color: ColorTag) -> Result<()> {
    let mut state = store.load()?;
    let now = Local::now().timestamp();
    
    let mut found = false;
    for cat in State::all_categories() {
        let vec = state.get_category_mut(cat);
        // Check Todos
        if let Some(todo) = vec.iter_mut().find(|t| t.id == id) {
            todo.color = color;
            todo.updated_at = now;
            found = true;
        } else {
             // Check Subtasks recursively
             for todo in vec.iter_mut() {
                 if set_color_in_subs(&mut todo.subtasks, id, color, now) {
                     todo.updated_at = now;
                     found = true;
                     break;
                 }
             }
        }
        if found { break; }
    }
    
    if found {
        store.save(&state)?;
        Ok(())
    } else {
        bail!("Item not found: {}", id)
    }
}

fn set_color_in_subs(subs: &mut [SubTask], id: u64, color: ColorTag, now: i64) -> bool {
    for sub in subs.iter_mut() {
        if sub.id == id {
            sub.color = color;
            sub.updated_at = now;
            return true;
        }
        if set_color_in_subs(&mut sub.subtasks, id, color, now) {
            sub.updated_at = now;
            return true;
        }
    }
    false
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::core::store::Store;
    use std::sync::Mutex;

    struct MockStore {
        state: Mutex<State>,
    }

    impl MockStore {
        fn new(state: State) -> Self {
            Self { state: Mutex::new(state) }
        }
    }

    impl Store for MockStore {
        fn load(&self) -> Result<State> {
            Ok(self.state.lock().unwrap().clone())
        }
        fn save(&self, state: &State) -> Result<()> {
            *self.state.lock().unwrap() = state.clone();
            Ok(())
        }
    }

    #[test]
    fn test_add_and_edit_todo() {
        let store = MockStore::new(State::new());
        
        let id = add_todo(&store, Category::Short, "Buy milk".to_string(), None).unwrap();
        assert_eq!(id, 1);
        
        let state = store.load().unwrap();
        assert_eq!(state.short.len(), 1);
        assert_eq!(state.short[0].title, "Buy milk");
        
        edit_todo(&store, id, Some("Buy eggs".to_string()), Some("Organic".to_string())).unwrap();
        let state = store.load().unwrap();
        assert_eq!(state.short[0].title, "Buy eggs");
        assert_eq!(state.short[0].notes, Some("Organic".to_string()));
    }

    #[test]
    fn test_toggle_done() {
        let store = MockStore::new(State::new());
        let id = add_todo(&store, Category::Short, "Task".to_string(), None).unwrap();
        
        toggle_done(&store, id).unwrap();
        let state = store.load().unwrap();
        assert_eq!(state.short.len(), 0);
        assert_eq!(state.completed.len(), 1);
        assert_eq!(state.completed[0].title, "Task");
        
        toggle_done(&store, id).unwrap();
        let state = store.load().unwrap();
        assert_eq!(state.completed.len(), 0);
        assert_eq!(state.short.len(), 1);
    }
}

