//! 项目管理器 — 项目的领域层(CRUD 构造 + 状态机 + 业务约束)
//!
//! 任务 #16 真分层:本 crate 不再只是"构造实体的工厂函数",而是承载项目领域规则。
//! Storage 层(df-storage::ProjectRepo)仅负责持久化,状态合法性 / 业务约束在此。
//!
//! 调用方(src-tauri commands/project.rs)推进项目状态时必须经
//! `ProjectManager::can_transition` / `transition` 校验,防非法跳态。

use serde::{Deserialize, Serialize};

use df_types::types::{IdeaId, Priority, ProjectId, ProjectStatus};

/// 项目实体
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Project {
    /// 唯一 ID
    pub id: ProjectId,
    /// 项目名称
    pub name: String,
    /// 项目描述
    pub description: String,
    /// 当前状态
    pub status: ProjectStatus,
    /// 来源想法 ID（可选，如果是从想法晋升来的）
    pub idea_id: Option<IdeaId>,
    /// 优先级
    pub priority: Priority,
    /// 标签
    pub tags: Vec<String>,
    /// 创建时间
    pub created_at: chrono::DateTime<chrono::Utc>,
    /// 更新时间
    pub updated_at: chrono::DateTime<chrono::Utc>,
}

/// 创建项目的输入
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateProjectInput {
    pub name: String,
    pub description: String,
    pub idea_id: Option<IdeaId>,
    #[serde(default)]
    pub priority: Priority,
    #[serde(default)]
    pub tags: Vec<String>,
}

/// 项目状态机错误
#[derive(Debug, thiserror::Error)]
pub enum ProjectTransitionError {
    #[error("项目状态转换非法: {from:?} → {to:?}")]
    IllegalTransition {
        from: ProjectStatus,
        to: ProjectStatus,
    },
    #[error("项目名称不能为空")]
    EmptyName,
    #[error("项目已处在终态 {0:?},不可再推进")]
    TerminalState(ProjectStatus),
}

/// 项目管理器(无状态纯逻辑,实体构造 + 状态机 + 业务约束)
pub struct ProjectManager;

impl ProjectManager {
    /// 创建新项目 — 构造领域实体(不落库);持久化由调用方经 storage 层 ProjectRecord 映射完成
    /// (见 commands/idea.rs::promote_idea)。领域层不依赖 storage,保持分层。
    pub fn create(input: CreateProjectInput) -> Result<Project, ProjectTransitionError> {
        if input.name.trim().is_empty() {
            return Err(ProjectTransitionError::EmptyName);
        }
        let now = chrono::Utc::now();
        Ok(Project {
            id: df_types::types::new_id(),
            name: input.name,
            description: input.description,
            status: ProjectStatus::Planning,
            idea_id: input.idea_id,
            priority: input.priority,
            tags: input.tags,
            created_at: now,
            updated_at: now,
        })
    }

    /// 从想法创建项目
    pub fn create_from_idea(
        name: String,
        description: String,
        idea_id: IdeaId,
    ) -> Result<Project, ProjectTransitionError> {
        Self::create(CreateProjectInput {
            name,
            description,
            idea_id: Some(idea_id),
            priority: Priority::default(),
            tags: Vec::new(),
        })
    }

    /// 判断项目状态转换是否合法(状态机核心)。
    ///
    /// 项目状态语义(与 ARCHITECTURE.md 一致):
    ///   - Planning   → InProgress(开始), Cancelled(取消)
    ///   - InProgress → Testing(提交测试), Paused(暂停), Cancelled
    ///   - Testing    → Completed(测试通过), InProgress(退回开发), Cancelled
    ///   - Completed  → 终态(不可变)
    ///   - Paused     → InProgress(恢复), Cancelled
    ///   - Cancelled  → 终态
    pub fn can_transition(from: &ProjectStatus, to: &ProjectStatus) -> bool {
        use ProjectStatus::*;
        matches!((from, to),
            (Planning, InProgress) | (Planning, Cancelled)
            | (InProgress, Testing) | (InProgress, Paused) | (InProgress, Cancelled)
            | (Testing, Completed) | (Testing, InProgress) | (Testing, Cancelled)
            | (Paused, InProgress) | (Paused, Cancelled)
        )
    }

    /// 执行状态转换,返回新状态或非法错误。
    ///
    /// 调用方(如 IPC `update_project_status` / `advance_project`)应用本方法校验后
    /// 再写 storage,防跳态(如 Planning → Completed 跳过 Testing)。
    pub fn transition(
        from: ProjectStatus,
        to: ProjectStatus,
    ) -> Result<ProjectStatus, ProjectTransitionError> {
        if !Self::can_transition(&from, &to) {
            // 区分错误: 终态→任何 vs 一般非法
            if matches!(from, ProjectStatus::Completed | ProjectStatus::Cancelled) {
                return Err(ProjectTransitionError::TerminalState(from));
            }
            return Err(ProjectTransitionError::IllegalTransition { from, to });
        }
        Ok(to)
    }

    /// 是否为终态(不可再转换)
    pub fn is_terminal(s: &ProjectStatus) -> bool {
        matches!(s, ProjectStatus::Completed | ProjectStatus::Cancelled)
    }
}

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

    #[test]
    fn create_rejects_empty_name() {
        let err = ProjectManager::create(CreateProjectInput {
            name: "  ".into(),
            description: String::new(),
            idea_id: None,
            priority: Priority::default(),
            tags: vec![],
        }).unwrap_err();
        assert!(matches!(err, ProjectTransitionError::EmptyName));
    }

    #[test]
    fn main_path_planning_to_completed() {
        use ProjectStatus::*;
        assert!(ProjectManager::can_transition(&Planning, &InProgress));
        assert!(ProjectManager::can_transition(&InProgress, &Testing));
        assert!(ProjectManager::can_transition(&Testing, &Completed));
    }

    #[test]
    fn illegal_skips_rejected() {
        use ProjectStatus::*;
        assert!(!ProjectManager::can_transition(&Planning, &Completed));
        assert!(!ProjectManager::can_transition(&Planning, &Testing));
        assert!(!ProjectManager::can_transition(&InProgress, &Completed));
    }

    #[test]
    fn terminal_states_block_all() {
        use ProjectStatus::*;
        for term in [Completed, Cancelled] {
            for to in [Planning, InProgress, Testing, Paused] {
                assert!(!ProjectManager::can_transition(&term, &to));
            }
        }
    }

    #[test]
    fn paused_round_trip() {
        use ProjectStatus::*;
        assert!(ProjectManager::can_transition(&InProgress, &Paused));
        assert!(ProjectManager::can_transition(&Paused, &InProgress));
    }

    #[test]
    fn transition_returns_target_on_legal() {
        use ProjectStatus::*;
        assert_eq!(
            ProjectManager::transition(Planning, InProgress).unwrap(),
            InProgress
        );
    }

    #[test]
    fn transition_errors_on_illegal() {
        use ProjectStatus::*;
        let err = ProjectManager::transition(Planning, Completed).unwrap_err();
        assert!(matches!(err, ProjectTransitionError::IllegalTransition { .. }));
    }

    #[test]
    fn transition_errors_on_terminal_source() {
        use ProjectStatus::*;
        let err = ProjectManager::transition(Completed, InProgress).unwrap_err();
        assert!(matches!(err, ProjectTransitionError::TerminalState(_)));
    }
}
