use anyhow::{bail, Context, Result};
use serde::Serialize;
use std::fmt;
use ulid::Ulid;

/// Current UTC time in ISO 8601 format.
pub fn now_utc() -> String {
    chrono::Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string()
}

/// Lifecycle state for a task.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Status {
    Open,
    InProgress,
    Closed,
}

impl Status {
    pub fn as_str(self) -> &'static str {
        match self {
            Status::Open => "open",
            Status::InProgress => "in_progress",
            Status::Closed => "closed",
        }
    }

    pub fn parse(raw: &str) -> Result<Self> {
        match raw {
            "open" => Ok(Self::Open),
            "in_progress" => Ok(Self::InProgress),
            "closed" => Ok(Self::Closed),
            _ => bail!("invalid status '{raw}'"),
        }
    }
}

/// Priority for task ordering.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Priority {
    High,
    Medium,
    Low,
}

impl Priority {
    pub fn as_str(self) -> &'static str {
        match self {
            Priority::High => "high",
            Priority::Medium => "medium",
            Priority::Low => "low",
        }
    }

    pub fn parse(raw: &str) -> Result<Self> {
        match raw {
            "high" => Ok(Self::High),
            "medium" => Ok(Self::Medium),
            "low" => Ok(Self::Low),
            _ => bail!("invalid priority '{raw}'"),
        }
    }

    pub fn score(self) -> i32 {
        match self {
            Priority::High => 1,
            Priority::Medium => 2,
            Priority::Low => 3,
        }
    }
}

/// Estimated effort for a task.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Effort {
    Low,
    Medium,
    High,
}

impl Effort {
    pub fn as_str(self) -> &'static str {
        match self {
            Effort::Low => "low",
            Effort::Medium => "medium",
            Effort::High => "high",
        }
    }

    pub fn parse(raw: &str) -> Result<Self> {
        match raw {
            "low" => Ok(Self::Low),
            "medium" => Ok(Self::Medium),
            "high" => Ok(Self::High),
            _ => bail!("invalid effort '{raw}'"),
        }
    }

    pub fn score(self) -> i32 {
        match self {
            Effort::Low => 1,
            Effort::Medium => 2,
            Effort::High => 3,
        }
    }
}

/// A stable task identifier backed by a ULID.
///
/// Serializes as the short display form (`td-XXXXXXX`) for user-facing
/// JSON. Use [`TaskId::as_str`] when the full ULID is needed (e.g.
/// for CRDT keys or export round-tripping).
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TaskId(String);

impl Serialize for TaskId {
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_str(&self.short())
    }
}

impl TaskId {
    pub fn new(id: Ulid) -> Self {
        Self(id.to_string())
    }

    pub fn parse(raw: &str) -> Result<Self> {
        let id = Ulid::from_string(raw).with_context(|| format!("invalid task id '{raw}'"))?;
        Ok(Self::new(id))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }

    pub fn short(&self) -> String {
        format!("td-{}", &self.0[self.0.len() - 7..])
    }

    /// Return a display-friendly short ID from a raw ULID string.
    pub fn display_id(raw: &str) -> String {
        let n = raw.len();
        if n > 7 {
            format!("td-{}", &raw[n - 7..])
        } else {
            format!("td-{raw}")
        }
    }
}

impl fmt::Display for TaskId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.short())
    }
}

/// A task log entry embedded in a task record.
#[derive(Debug, Clone, Serialize)]
pub struct LogEntry {
    pub id: TaskId,
    pub timestamp: String,
    pub message: String,
}

/// Hydrated task data from the CRDT document.
#[derive(Debug, Clone, Serialize)]
pub struct Task {
    pub id: TaskId,
    pub title: String,
    pub description: String,
    #[serde(rename = "type")]
    pub task_type: String,
    pub priority: Priority,
    pub status: Status,
    pub effort: Effort,
    pub parent: Option<TaskId>,
    pub created_at: String,
    pub updated_at: String,
    pub deleted_at: Option<String>,
    pub labels: Vec<String>,
    pub blockers: Vec<TaskId>,
    pub logs: Vec<LogEntry>,
}

impl Task {
    /// Serialize this task with full ULIDs instead of short display IDs.
    ///
    /// Used by `export` so that `import` can round-trip data losslessly —
    /// `import` needs the full ULID to recreate exact CRDT keys.
    pub fn to_export_value(&self) -> serde_json::Value {
        serde_json::json!({
            "id": self.id.as_str(),
            "title": self.title,
            "description": self.description,
            "type": self.task_type,
            "priority": self.priority,
            "status": self.status,
            "effort": self.effort,
            "parent": self.parent.as_ref().map(|p| p.as_str()),
            "created_at": self.created_at,
            "updated_at": self.updated_at,
            "deleted_at": self.deleted_at,
            "labels": self.labels,
            "blockers": self.blockers.iter().map(|b| b.as_str()).collect::<Vec<_>>(),
            "logs": self
                .logs
                .iter()
                .map(|l| {
                    serde_json::json!({
                        "id": l.id.as_str(),
                        "timestamp": l.timestamp,
                        "message": l.message,
                    })
                })
                .collect::<Vec<_>>(),
        })
    }
}

/// Result type for partitioning blockers by task state.
#[derive(Debug, Default, Clone, Serialize)]
pub struct BlockerPartition {
    pub open: Vec<TaskId>,
    pub resolved: Vec<TaskId>,
}

/// Generate a new task ULID.
pub fn gen_id() -> TaskId {
    TaskId::new(Ulid::new())
}
