//! Local filesystem implementation.
//!
//! Stores each entity type in a separate JSON file under `base_path`.
//! Uses Tokio's `spawn_blocking` to avoid blocking async runtime during file IO.

use anyhow::{Context, Result};
use async_trait::async_trait;
use std::path::{Path, PathBuf};
use tokio::task;

use sepora_core::{Calendar, Diary, Todo};

use crate::storage::Storage;

fn ensure_dir(path: &Path) -> Result<()> {
    if !path.exists() {
        std::fs::create_dir_all(path)?;
    }
    Ok(())
}

fn read_json<T: for<'de> serde::Deserialize<'de>>(path: &Path) -> Result<Vec<T>> {
    if !path.exists() {
        return Ok(vec![]);
    }
    let s = std::fs::read_to_string(path)?;
    let v = serde_json::from_str(&s)?;
    Ok(v)
}

fn write_json<T: serde::Serialize>(path: &Path, v: &Vec<T>) -> Result<()> {
    let s = serde_json::to_string_pretty(v)?;
    std::fs::write(path, s)?;
    Ok(())
}

/// LocalStorage backed by JSON files.
pub struct LocalStorage {
    base_path: PathBuf,
}

impl LocalStorage {
    /// Create a new LocalStorage with given base directory.
    /// Data files will be `base_path/todos.json`, `base_path/diaries.json`, `base_path/calendars.json`.
    pub fn new<P: Into<PathBuf>>(base_path: P) -> Self {
        let pb = base_path.into();
        // create dir if not exists (best-effort)
        let _ = ensure_dir(&pb);
        Self { base_path: pb }
    }

    fn file_path(&self, name: &str) -> PathBuf {
        self.base_path.join(name)
    }
}

#[async_trait]
impl Storage for LocalStorage {
    // Todos
    async fn list_todos(&self) -> Result<Vec<Todo>> {
        let path = self.file_path("todos.json");
        let path2 = path.clone();
        task::spawn_blocking(move || read_json(&path2))
            .await?
            .context("read todos")
    }

    async fn create_todo(&self, todo: Todo) -> Result<()> {
        let path = self.file_path("todos.json");
        let todo_cloned = todo.clone();
        task::spawn_blocking(move || -> Result<()> {
            let mut v: Vec<Todo> = read_json(&path)?;
            v.push(todo_cloned);
            write_json(&path, &v)?;
            Ok(())
        })
        .await?
    }

    async fn update_todo(&self, todo: Todo) -> Result<()> {
        let path = self.file_path("todos.json");
        let todo_cloned = todo.clone();
        task::spawn_blocking(move || -> Result<()> {
            let mut v: Vec<Todo> = read_json(&path)?;
            if let Some(pos) = v.iter().position(|t| t.id == todo_cloned.id) {
                v[pos] = todo_cloned;
            }
            write_json(&path, &v)?;
            Ok(())
        })
        .await?
    }

    async fn delete_todo(&self, id: &str) -> Result<()> {
        let id_owned = id.to_string();
        let path = self.file_path("todos.json");
        task::spawn_blocking(move || -> Result<()> {
            let mut v: Vec<Todo> = read_json(&path)?;
            v.retain(|t| t.id != id_owned);
            write_json(&path, &v)?;
            Ok(())
        })
        .await?
    }

    // Diaries
    async fn list_diaries(&self) -> Result<Vec<Diary>> {
        let path = self.file_path("diaries.json");
        let path2 = path.clone();
        task::spawn_blocking(move || read_json(&path2))
            .await?
            .context("read diaries")
    }

    async fn create_diary(&self, diary: Diary) -> Result<()> {
        let path = self.file_path("diaries.json");
        let diary_cloned = diary.clone();
        task::spawn_blocking(move || -> Result<()> {
            let mut v: Vec<Diary> = read_json(&path)?;
            v.push(diary_cloned);
            write_json(&path, &v)?;
            Ok(())
        })
        .await?
    }

    async fn update_diary(&self, diary: Diary) -> Result<()> {
        let path = self.file_path("diaries.json");
        let diary_cloned = diary.clone();
        task::spawn_blocking(move || -> Result<()> {
            let mut v: Vec<Diary> = read_json(&path)?;
            if let Some(pos) = v.iter().position(|d| d.id == diary_cloned.id) {
                v[pos] = diary_cloned;
            }
            write_json(&path, &v)?;
            Ok(())
        })
        .await?
    }

    async fn delete_diary(&self, id: &str) -> Result<()> {
        let id_owned = id.to_string();
        let path = self.file_path("diaries.json");
        task::spawn_blocking(move || -> Result<()> {
            let mut v: Vec<Diary> = read_json(&path)?;
            v.retain(|d| d.id != id_owned);
            write_json(&path, &v)?;
            Ok(())
        })
        .await?
    }

    // Calendars
    async fn list_calendars(&self) -> Result<Vec<Calendar>> {
        let path = self.file_path("calendars.json");
        let path2 = path.clone();
        task::spawn_blocking(move || read_json(&path2))
            .await?
            .context("read calendars")
    }

    async fn create_calendar(&self, cal: Calendar) -> Result<()> {
        let path = self.file_path("calendars.json");
        let cal_cloned = cal.clone();
        task::spawn_blocking(move || -> Result<()> {
            let mut v: Vec<Calendar> = read_json(&path)?;
            v.push(cal_cloned);
            write_json(&path, &v)?;
            Ok(())
        })
        .await?
    }

    async fn update_calendar(&self, cal: Calendar) -> Result<()> {
        let path = self.file_path("calendars.json");
        let cal_cloned = cal.clone();
        task::spawn_blocking(move || -> Result<()> {
            let mut v: Vec<Calendar> = read_json(&path)?;
            if let Some(pos) = v.iter().position(|c| c.id == cal_cloned.id) {
                v[pos] = cal_cloned;
            }
            write_json(&path, &v)?;
            Ok(())
        })
        .await?
    }

    async fn delete_calendar(&self, id: &str) -> Result<()> {
        let id_owned = id.to_string();
        let path = self.file_path("calendars.json");
        task::spawn_blocking(move || -> Result<()> {
            let mut v: Vec<Calendar> = read_json(&path)?;
            v.retain(|c| c.id != id_owned);
            write_json(&path, &v)?;
            Ok(())
        })
        .await?
    }
}

impl std::fmt::Debug for LocalStorage {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "LocalStorage({})", self.base_path.display())
    }
}
