use std::sync::Arc;

use dashmap::DashMap;
use serde::{Deserialize, Serialize};
use sqlx::Row;
use tokio::sync::broadcast;

use crate::storage::{Storage, StorageError};

const MAX_OBJECTIVE_CHARS: usize = 2_000;
const MAX_ITEM_TITLE_CHARS: usize = 500;
const MAX_INITIAL_ITEMS: usize = 50;
const MAX_ITEMS: usize = 100;
const MAX_SUMMARY_CHARS: usize = 4_000;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TaskItem {
    pub id: String,
    pub ordinal: i64,
    pub title: String,
    pub status: String,
    pub executor_kind: Option<String>,
    pub execution_id: Option<String>,
    pub result_summary: Option<String>,
    pub error: Option<String>,
    pub version: i64,
    pub updated_at: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TaskPlan {
    pub id: String,
    pub session_id: String,
    pub objective: String,
    pub status: String,
    pub version: i64,
    pub created_at: i64,
    pub updated_at: i64,
    pub closed_at: Option<i64>,
    pub items: Vec<TaskItem>,
}

impl TaskPlan {
    pub fn compact_context(&self) -> String {
        let completed = self
            .items
            .iter()
            .filter(|item| item.status == "completed")
            .count();
        let mut lines = vec![
            "## 当前任务计划".to_string(),
            format!("- 目标：{}", self.objective),
            format!("- 进度：{completed}/{}", self.items.len()),
        ];
        let running: Vec<_> = self
            .items
            .iter()
            .filter(|item| item.status == "in_progress")
            .take(8)
            .map(|item| {
                let executor = item.executor_kind.as_deref().unwrap_or("main_agent");
                format!("  - {} {}（{}）", item.id, item.title, executor)
            })
            .collect();
        if !running.is_empty() {
            lines.push("- 正在执行：".to_string());
            lines.extend(running);
        }
        let blocked: Vec<_> = self
            .items
            .iter()
            .filter(|item| item.status == "blocked")
            .take(8)
            .map(|item| {
                format!(
                    "  - {} {}：{}",
                    item.id,
                    item.title,
                    item.error.as_deref().unwrap_or("原因未知")
                )
            })
            .collect();
        if !blocked.is_empty() {
            lines.push("- 阻塞：".to_string());
            lines.extend(blocked);
        }
        if let Some(next) = self.items.iter().find(|item| item.status == "pending") {
            lines.push(format!("- 下一待处理：{} {}", next.id, next.title));
        }
        lines.push(format!("- 计划版本：{}", self.version));
        lines.join("\n")
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlanChanged {
    pub session_id: String,
    pub reason: String,
    pub changed_item_ids: Vec<String>,
    pub plan: Option<TaskPlan>,
}

#[derive(Clone)]
pub struct WorkManager {
    storage: Arc<Storage>,
    events: broadcast::Sender<PlanChanged>,
    active_cache: Arc<DashMap<String, Option<TaskPlan>>>,
}

impl WorkManager {
    pub fn new(storage: Arc<Storage>) -> Self {
        let (events, _) = broadcast::channel(128);
        Self {
            storage,
            events,
            active_cache: Arc::new(DashMap::new()),
        }
    }

    pub fn subscribe(&self) -> broadcast::Receiver<PlanChanged> {
        self.events.subscribe()
    }

    pub async fn active_plan(&self, session_id: &str) -> Result<Option<TaskPlan>, StorageError> {
        if let Some(cached) = self.active_cache.get(session_id) {
            return Ok(cached.clone());
        }
        let row = sqlx::query(
            "SELECT id, session_id, objective, status, version, created_at, updated_at, closed_at \
             FROM task_plans WHERE session_id = ? AND status = 'active' LIMIT 1",
        )
        .bind(session_id)
        .fetch_optional(self.storage.pool())
        .await?;
        let Some(row) = row else {
            self.active_cache.insert(session_id.to_string(), None);
            return Ok(None);
        };
        let plan = self.plan_from_row(row).await?;
        self.cache_active_plan(&plan);
        Ok(Some(plan))
    }

    pub async fn plan_for_session(
        &self,
        session_id: &str,
    ) -> Result<Option<TaskPlan>, StorageError> {
        self.active_plan(session_id).await
    }

    async fn plan_from_row(&self, row: sqlx::sqlite::SqliteRow) -> Result<TaskPlan, StorageError> {
        let plan_id: String = row.get("id");
        let item_rows = sqlx::query(
            "SELECT id, ordinal, title, status, executor_kind, execution_id, result_summary, \
                    error, version, updated_at FROM task_items WHERE plan_id = ? ORDER BY ordinal",
        )
        .bind(&plan_id)
        .fetch_all(self.storage.pool())
        .await?;
        Ok(TaskPlan {
            id: plan_id,
            session_id: row.get("session_id"),
            objective: row.get("objective"),
            status: row.get("status"),
            version: row.get("version"),
            created_at: row.get("created_at"),
            updated_at: row.get("updated_at"),
            closed_at: row.get("closed_at"),
            items: item_rows.into_iter().map(item_from_row).collect(),
        })
    }

    async fn plan_by_id(&self, plan_id: &str) -> Result<TaskPlan, StorageError> {
        let row = sqlx::query(
            "SELECT id, session_id, objective, status, version, created_at, updated_at, closed_at \
             FROM task_plans WHERE id = ?",
        )
        .bind(plan_id)
        .fetch_optional(self.storage.pool())
        .await?
        .ok_or_else(|| StorageError::NotFound(plan_id.to_string()))?;
        self.plan_from_row(row).await
    }

    pub async fn create_plan(
        &self,
        session_id: &str,
        objective: &str,
        items: &[String],
    ) -> Result<TaskPlan, StorageError> {
        if objective.trim().is_empty() || items.is_empty() {
            return Err(StorageError::Conflict(
                "计划目标不能为空，且至少需要一个子项".to_string(),
            ));
        }
        validate_length("计划目标", objective, MAX_OBJECTIVE_CHARS)?;
        if items.len() > MAX_INITIAL_ITEMS {
            return Err(StorageError::Conflict(format!(
                "初始计划最多包含 {MAX_INITIAL_ITEMS} 个子项"
            )));
        }
        if self.active_plan(session_id).await?.is_some() {
            return Err(StorageError::Conflict(
                "当前 session 已有 active plan".to_string(),
            ));
        }
        let now = chrono::Utc::now().timestamp_millis();
        let plan_id = format!("plan-{}", crate::util::short_id());
        let mut tx = self.storage.pool().begin().await?;
        sqlx::query(
            "INSERT INTO task_plans (id, session_id, objective, status, version, created_at, updated_at) \
             VALUES (?, ?, ?, 'active', 1, ?, ?)",
        )
        .bind(&plan_id)
        .bind(session_id)
        .bind(objective.trim())
        .bind(now)
        .bind(now)
        .execute(&mut *tx)
        .await?;
        for (index, title) in items.iter().enumerate() {
            let title = title.trim();
            if title.is_empty() {
                return Err(StorageError::Conflict("计划子项不能为空".to_string()));
            }
            validate_length("计划子项", title, MAX_ITEM_TITLE_CHARS)?;
            sqlx::query(
                "INSERT INTO task_items (id, plan_id, ordinal, title, status, version, created_at, updated_at) \
                 VALUES (?, ?, ?, ?, 'pending', 1, ?, ?)",
            )
            .bind(format!("T{}", index + 1))
            .bind(&plan_id)
            .bind(index as i64 + 1)
            .bind(title)
            .bind(now)
            .bind(now)
            .execute(&mut *tx)
            .await?;
        }
        tx.commit().await?;
        let plan = self.plan_by_id(&plan_id).await?;
        self.cache_active_plan(&plan);
        self.emit("plan_created", vec![], Some(plan.clone()));
        Ok(plan)
    }

    pub async fn append_item(
        &self,
        session_id: &str,
        title: &str,
        expected_version: Option<i64>,
    ) -> Result<TaskPlan, StorageError> {
        let plan = self.require_active(session_id, expected_version).await?;
        if title.trim().is_empty() {
            return Err(StorageError::Conflict("计划子项不能为空".to_string()));
        }
        if plan.items.len() >= MAX_ITEMS {
            return Err(StorageError::Conflict(format!(
                "计划最多包含 {MAX_ITEMS} 个子项"
            )));
        }
        validate_length("计划子项", title, MAX_ITEM_TITLE_CHARS)?;
        let now = chrono::Utc::now().timestamp_millis();
        let ordinal = plan.items.last().map_or(1, |item| item.ordinal + 1);
        let item_id = format!("T{ordinal}");
        let mut tx = self.storage.pool().begin().await?;
        sqlx::query(
            "INSERT INTO task_items (id, plan_id, ordinal, title, status, version, created_at, updated_at) \
             VALUES (?, ?, ?, ?, 'pending', 1, ?, ?)",
        )
        .bind(&item_id)
        .bind(&plan.id)
        .bind(ordinal)
        .bind(title.trim())
        .bind(now)
        .bind(now)
        .execute(&mut *tx)
        .await?;
        bump_plan(&mut tx, &plan.id, now).await?;
        tx.commit().await?;
        let updated = self.plan_by_id(&plan.id).await?;
        self.cache_active_plan(&updated);
        self.emit("item_added", vec![item_id], Some(updated.clone()));
        Ok(updated)
    }

    pub async fn update_item(
        &self,
        session_id: &str,
        item_id: &str,
        status: &str,
        summary: Option<&str>,
        expected_version: Option<i64>,
    ) -> Result<TaskPlan, StorageError> {
        validate_item_status(status)?;
        let plan = self.require_active(session_id, expected_version).await?;
        let current = plan
            .items
            .iter()
            .find(|item| item.id == item_id)
            .ok_or_else(|| StorageError::NotFound(item_id.to_string()))?;
        validate_transition(&current.status, status)?;
        let now = chrono::Utc::now().timestamp_millis();
        let summary = summary.map(|value| truncate_chars(value, MAX_SUMMARY_CHARS));
        let error = (status == "blocked")
            .then_some(summary.as_deref())
            .flatten();
        let result_summary = (status == "completed")
            .then_some(summary.as_deref())
            .flatten();
        let executor = match status {
            "in_progress" => Some("main_agent"),
            "completed" | "blocked" => current.executor_kind.as_deref(),
            _ => None,
        };
        let execution_id = matches!(status, "completed" | "blocked")
            .then_some(current.execution_id.as_deref())
            .flatten();
        let mut tx = self.storage.pool().begin().await?;
        let update = sqlx::query(
            "UPDATE task_items SET status = ?, executor_kind = ?, execution_id = ?, \
                    result_summary = ?, error = ?, \
                    version = version + 1, updated_at = ? \
             WHERE plan_id = ? AND id = ? AND status = ? AND version = ?",
        )
        .bind(status)
        .bind(executor)
        .bind(execution_id)
        .bind(result_summary)
        .bind(error)
        .bind(now)
        .bind(&plan.id)
        .bind(item_id)
        .bind(&current.status)
        .bind(current.version)
        .execute(&mut *tx)
        .await?;
        if update.rows_affected() != 1 {
            return Err(StorageError::Conflict(format!(
                "子项 {item_id} 已被其他执行更新，请读取最新计划后重试"
            )));
        }
        bump_plan(&mut tx, &plan.id, now).await?;
        tx.commit().await?;
        let updated = self.plan_by_id(&plan.id).await?;
        self.cache_active_plan(&updated);
        self.emit(
            "item_updated",
            vec![item_id.to_string()],
            Some(updated.clone()),
        );
        Ok(updated)
    }

    pub async fn assign_sub_agent(
        &self,
        session_id: &str,
        item_id: &str,
        execution_id: &str,
    ) -> Result<TaskPlan, StorageError> {
        let plan = self.require_active(session_id, None).await?;
        let item = plan
            .items
            .iter()
            .find(|item| item.id == item_id)
            .ok_or_else(|| StorageError::NotFound(item_id.to_string()))?;
        if item.status != "pending" {
            return Err(StorageError::Conflict(format!(
                "子项 {item_id} 当前状态为 {}，不能重复委托",
                item.status
            )));
        }
        let now = chrono::Utc::now().timestamp_millis();
        let mut tx = self.storage.pool().begin().await?;
        let result = sqlx::query(
            "UPDATE task_items SET status = 'in_progress', executor_kind = 'sub_agent', \
                    execution_id = ?, error = NULL, version = version + 1, updated_at = ? \
             WHERE plan_id = ? AND id = ? AND status = 'pending'",
        )
        .bind(execution_id)
        .bind(now)
        .bind(&plan.id)
        .bind(item_id)
        .execute(&mut *tx)
        .await?;
        if result.rows_affected() != 1 {
            return Err(StorageError::Conflict(format!(
                "子项 {item_id} 已被其他执行领取"
            )));
        }
        bump_plan(&mut tx, &plan.id, now).await?;
        tx.commit().await?;
        let updated = self.plan_by_id(&plan.id).await?;
        self.cache_active_plan(&updated);
        self.emit(
            "item_assigned",
            vec![item_id.to_string()],
            Some(updated.clone()),
        );
        Ok(updated)
    }

    pub async fn finish_sub_agent(
        &self,
        session_id: &str,
        item_id: &str,
        execution_id: &str,
        completed: bool,
        summary: Option<&str>,
    ) -> Result<Option<TaskPlan>, StorageError> {
        let Some(plan) = self.active_plan(session_id).await? else {
            return Ok(None);
        };
        let item = plan.items.iter().find(|item| item.id == item_id);
        if !item.is_some_and(|item| {
            item.status == "in_progress" && item.execution_id.as_deref() == Some(execution_id)
        }) {
            return Ok(Some(plan));
        }
        let status = if completed { "completed" } else { "blocked" };
        let summary = summary.map(|value| truncate_chars(value, MAX_SUMMARY_CHARS));
        let now = chrono::Utc::now().timestamp_millis();
        let mut tx = self.storage.pool().begin().await?;
        let result = sqlx::query(
            "UPDATE task_items SET status = ?, result_summary = ?, error = ?, \
                    version = version + 1, updated_at = ? \
             WHERE plan_id = ? AND id = ? AND execution_id = ? AND status = 'in_progress'",
        )
        .bind(status)
        .bind(completed.then_some(summary.as_deref()).flatten())
        .bind((!completed).then_some(summary.as_deref()).flatten())
        .bind(now)
        .bind(&plan.id)
        .bind(item_id)
        .bind(execution_id)
        .execute(&mut *tx)
        .await?;
        if result.rows_affected() != 1 {
            drop(tx);
            return Ok(Some(self.plan_by_id(&plan.id).await?));
        }
        bump_plan(&mut tx, &plan.id, now).await?;
        tx.commit().await?;
        let updated = self.plan_by_id(&plan.id).await?;
        self.cache_active_plan(&updated);
        self.emit(
            if completed {
                "item_completed"
            } else {
                "item_blocked"
            },
            vec![item_id.to_string()],
            Some(updated.clone()),
        );
        Ok(Some(updated))
    }

    /// Re-read the active plan after an external transaction (agent run
    /// admission or terminal commit) already mutated plan items, then refresh
    /// the cache and broadcast.  This path never writes to the database.
    pub async fn refresh_after_external_commit(
        &self,
        session_id: &str,
        reason: &str,
        item_ids: Vec<String>,
    ) -> Result<Option<TaskPlan>, StorageError> {
        let row = sqlx::query(
            "SELECT id, session_id, objective, status, version, created_at, updated_at, closed_at \
             FROM task_plans WHERE session_id = ? AND status = 'active' LIMIT 1",
        )
        .bind(session_id)
        .fetch_optional(self.storage.pool())
        .await?;
        let Some(row) = row else {
            self.active_cache.insert(session_id.to_string(), None);
            return Ok(None);
        };
        let plan = self.plan_from_row(row).await?;
        self.cache_active_plan(&plan);
        self.emit(reason, item_ids, Some(plan.clone()));
        Ok(Some(plan))
    }

    pub async fn close_plan(
        &self,
        session_id: &str,
        status: &str,
        expected_version: Option<i64>,
    ) -> Result<TaskPlan, StorageError> {
        if !matches!(status, "completed" | "cancelled") {
            return Err(StorageError::Conflict(
                "计划只能 completed 或 cancelled".to_string(),
            ));
        }
        let plan = self.require_active(session_id, expected_version).await?;
        if status == "completed" && plan.items.iter().any(|item| item.status != "completed") {
            return Err(StorageError::Conflict(
                "仍有未完成子项，不能关闭计划".to_string(),
            ));
        }
        let now = chrono::Utc::now().timestamp_millis();
        sqlx::query(
            "UPDATE task_plans SET status = ?, version = version + 1, updated_at = ?, closed_at = ? \
             WHERE id = ? AND status = 'active'",
        )
        .bind(status)
        .bind(now)
        .bind(now)
        .bind(&plan.id)
        .execute(self.storage.pool())
        .await?;
        let closed = self.plan_by_id(&plan.id).await?;
        self.active_cache.insert(session_id.to_string(), None);
        self.emit("plan_closed", vec![], Some(closed.clone()));
        Ok(closed)
    }

    async fn require_active(
        &self,
        session_id: &str,
        expected_version: Option<i64>,
    ) -> Result<TaskPlan, StorageError> {
        let plan = self
            .active_plan(session_id)
            .await?
            .ok_or_else(|| StorageError::NotFound("active plan".to_string()))?;
        if expected_version.is_some_and(|version| version != plan.version) {
            return Err(StorageError::Conflict(format!(
                "计划版本已变化：expected {}, actual {}",
                expected_version.unwrap_or_default(),
                plan.version
            )));
        }
        Ok(plan)
    }

    fn emit(&self, reason: &str, changed_item_ids: Vec<String>, plan: Option<TaskPlan>) {
        let Some(session_id) = plan.as_ref().map(|plan| plan.session_id.clone()) else {
            return;
        };
        let _ = self.events.send(PlanChanged {
            session_id,
            reason: reason.to_string(),
            changed_item_ids,
            plan,
        });
    }

    fn cache_active_plan(&self, plan: &TaskPlan) {
        let session_id = plan.session_id.clone();
        match self.active_cache.entry(session_id) {
            dashmap::mapref::entry::Entry::Occupied(mut entry) => {
                let should_replace = entry
                    .get()
                    .as_ref()
                    .is_none_or(|cached| cached.id != plan.id || cached.version <= plan.version);
                if should_replace {
                    entry.insert(Some(plan.clone()));
                }
            }
            dashmap::mapref::entry::Entry::Vacant(entry) => {
                entry.insert(Some(plan.clone()));
            }
        }
    }
}

fn item_from_row(row: sqlx::sqlite::SqliteRow) -> TaskItem {
    TaskItem {
        id: row.get("id"),
        ordinal: row.get("ordinal"),
        title: row.get("title"),
        status: row.get("status"),
        executor_kind: row.get("executor_kind"),
        execution_id: row.get("execution_id"),
        result_summary: row.get("result_summary"),
        error: row.get("error"),
        version: row.get("version"),
        updated_at: row.get("updated_at"),
    }
}

async fn bump_plan(
    tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
    plan_id: &str,
    now: i64,
) -> Result<(), sqlx::Error> {
    sqlx::query("UPDATE task_plans SET version = version + 1, updated_at = ? WHERE id = ?")
        .bind(now)
        .bind(plan_id)
        .execute(&mut **tx)
        .await?;
    Ok(())
}

fn validate_item_status(status: &str) -> Result<(), StorageError> {
    if matches!(status, "pending" | "in_progress" | "completed" | "blocked") {
        Ok(())
    } else {
        Err(StorageError::Conflict(format!("未知子项状态：{status}")))
    }
}

fn validate_transition(from: &str, to: &str) -> Result<(), StorageError> {
    let allowed = matches!(
        (from, to),
        ("pending", "in_progress")
            | ("pending", "completed")
            | ("pending", "blocked")
            | ("in_progress", "completed")
            | ("in_progress", "blocked")
            | ("blocked", "pending")
            | ("blocked", "in_progress")
            | ("blocked", "completed")
    ) || from == to;
    if allowed {
        Ok(())
    } else {
        Err(StorageError::Conflict(format!(
            "不允许的状态迁移：{from} -> {to}"
        )))
    }
}

fn validate_length(label: &str, value: &str, max_chars: usize) -> Result<(), StorageError> {
    if value.chars().count() <= max_chars {
        Ok(())
    } else {
        Err(StorageError::Conflict(format!(
            "{label}不能超过 {max_chars} 个字符"
        )))
    }
}

fn truncate_chars(value: &str, max_chars: usize) -> String {
    value.chars().take(max_chars).collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    async fn manager() -> (WorkManager, tempfile::TempDir) {
        let dir = tempfile::tempdir().unwrap();
        let storage = Arc::new(Storage::new(&dir.path().join("work.db")).await.unwrap());
        (WorkManager::new(storage), dir)
    }

    #[tokio::test]
    async fn one_active_plan_per_session_and_parallel_items() {
        let (manager, _dir) = manager().await;
        let plan = manager
            .create_plan("cli:a:d1", "ship it", &["one".into(), "two".into()])
            .await
            .unwrap();
        assert_eq!(plan.items.len(), 2);
        let other = manager
            .create_plan("cli:b:d2", "other", &["one".into()])
            .await
            .unwrap();
        assert_eq!(other.items[0].id, "T1");
        assert!(
            manager
                .create_plan("cli:a:d1", "another", &["x".into()])
                .await
                .is_err()
        );

        manager
            .assign_sub_agent("cli:a:d1", "T1", "run-1")
            .await
            .unwrap();
        assert!(
            manager
                .assign_sub_agent("cli:a:d1", "T1", "run-duplicate")
                .await
                .is_err()
        );
        let plan = manager
            .assign_sub_agent("cli:a:d1", "T2", "run-2")
            .await
            .unwrap();
        assert_eq!(
            plan.items
                .iter()
                .filter(|item| item.status == "in_progress")
                .count(),
            2
        );
    }

    #[tokio::test]
    async fn late_sub_agent_result_does_not_mutate_cancelled_plan() {
        let (manager, _dir) = manager().await;
        manager
            .create_plan("cli:a:d1", "ship", &["one".into()])
            .await
            .unwrap();
        manager
            .assign_sub_agent("cli:a:d1", "T1", "run-1")
            .await
            .unwrap();
        manager
            .close_plan("cli:a:d1", "cancelled", None)
            .await
            .unwrap();
        assert!(
            manager
                .finish_sub_agent("cli:a:d1", "T1", "run-1", true, Some("done"))
                .await
                .unwrap()
                .is_none()
        );
    }
}
