//! Task comment handlers for managing task discussions

use crate::error::{ApiError, ApiResult};
use crate::state::AppState;
use axum::{
    extract::{Path, State},
    http::StatusCode,
    Json,
};
use chrono::Utc;
use serde::{Deserialize, Serialize};
use syntaxis_core::persistence::Database;
use uuid::Uuid;

/// Add comment request
#[derive(Debug, Deserialize)]
pub struct AddCommentRequest {
    /// Comment content
    pub content: String,
    /// User ID (in production, extracted from auth token)
    pub user_id: String,
}

/// Comment DTO
#[derive(Debug, Serialize)]
pub struct CommentDto {
    /// Comment ID
    pub id: String,
    /// Task ID
    pub task_id: String,
    /// User ID
    pub user_id: String,
    /// User name
    pub user_name: String,
    /// Comment content
    pub content: String,
    /// Created timestamp
    pub created_at: String,
    /// Updated timestamp
    pub updated_at: String,
}

/// Get comments for a task
///
/// Retrieves all comments associated with a specific task.
pub async fn get_comments(
    State(state): State<AppState>,
    Path((project_id, task_id)): Path<(String, String)>,
) -> ApiResult<Json<Vec<CommentDto>>> {
    // Verify project exists
    let _project = state
        .db
        .get_project(&project_id)
        .await?
        .ok_or_else(|| ApiError::NotFound(format!("Project {} not found", project_id)))?;

    // Verify task exists
    let _task = sqlx::query("SELECT id FROM checklist_items WHERE id = ? AND project_id = ?")
        .bind(&task_id)
        .bind(&project_id)
        .fetch_optional(state.db.pool())
        .await
        .map_err(|e| ApiError::Database(e.to_string()))?
        .ok_or_else(|| ApiError::NotFound(format!("Task {} not found", task_id)))?;

    // Fetch comments
    let comments: Vec<(String, String, String, String, String, String, String)> = sqlx::query_as(
        r#"
        SELECT c.id, c.task_id, c.user_id, u.name as user_name, c.content, c.created_at, c.updated_at
        FROM task_comments c
        JOIN users u ON c.user_id = u.id
        WHERE c.task_id = ?
        ORDER BY c.created_at ASC
        "#
    )
    .bind(&task_id)
    .fetch_all(state.db.pool())
    .await
    .map_err(|e| ApiError::Database(e.to_string()))?;

    let dtos = comments
        .into_iter()
        .map(
            |(id, task_id, user_id, user_name, content, created_at, updated_at)| CommentDto {
                id,
                task_id,
                user_id,
                user_name,
                content,
                created_at,
                updated_at,
            },
        )
        .collect();

    Ok(Json(dtos))
}

/// Add a comment to a task
///
/// Creates a new comment on a task and broadcasts the event.
pub async fn add_comment(
    State(state): State<AppState>,
    Path((project_id, task_id)): Path<(String, String)>,
    Json(req): Json<AddCommentRequest>,
) -> ApiResult<(StatusCode, Json<CommentDto>)> {
    // Verify project exists
    let _project = state
        .db
        .get_project(&project_id)
        .await?
        .ok_or_else(|| ApiError::NotFound(format!("Project {} not found", project_id)))?;

    // Verify task exists
    let _task = sqlx::query("SELECT id FROM checklist_items WHERE id = ? AND project_id = ?")
        .bind(&task_id)
        .bind(&project_id)
        .fetch_optional(state.db.pool())
        .await
        .map_err(|e| ApiError::Database(e.to_string()))?
        .ok_or_else(|| ApiError::NotFound(format!("Task {} not found", task_id)))?;

    // Verify user exists
    let user: (String, String) = sqlx::query_as("SELECT id, name FROM users WHERE id = ?")
        .bind(&req.user_id)
        .fetch_optional(state.db.pool())
        .await
        .map_err(|e| ApiError::Database(e.to_string()))?
        .ok_or_else(|| ApiError::NotFound(format!("User {} not found", req.user_id)))?;

    // Create comment
    let comment_id = Uuid::new_v4().to_string();
    let now = Utc::now().to_rfc3339();

    sqlx::query(
        r#"
        INSERT INTO task_comments (id, task_id, user_id, content, created_at, updated_at)
        VALUES (?, ?, ?, ?, ?, ?)
        "#,
    )
    .bind(&comment_id)
    .bind(&task_id)
    .bind(&req.user_id)
    .bind(&req.content)
    .bind(&now)
    .bind(&now)
    .execute(state.db.pool())
    .await
    .map_err(|e| ApiError::Database(e.to_string()))?;

    // Broadcast comment added event
    state
        .events
        .broadcast(crate::websocket::ProjectEvent::CommentAdded {
            project_id,
            task_id: task_id.clone(),
            comment_id: comment_id.clone(),
            user_name: user.1.clone(),
            timestamp: now.clone(),
        });

    Ok((
        StatusCode::CREATED,
        Json(CommentDto {
            id: comment_id,
            task_id,
            user_id: req.user_id,
            user_name: user.1,
            content: req.content,
            created_at: now.clone(),
            updated_at: now,
        }),
    ))
}

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

    #[test]
    fn test_add_comment_request() {
        let json = r#"{"content":"Great progress!","user_id":"user-123"}"#;
        let req: AddCommentRequest = serde_json::from_str(json).unwrap();

        assert_eq!(req.content, "Great progress!");
        assert_eq!(req.user_id, "user-123");
    }

    #[test]
    fn test_comment_dto_serialization() {
        let dto = CommentDto {
            id: "comment-1".to_string(),
            task_id: "task-1".to_string(),
            user_id: "user-1".to_string(),
            user_name: "John Doe".to_string(),
            content: "Test comment".to_string(),
            created_at: "2025-01-01T00:00:00Z".to_string(),
            updated_at: "2025-01-01T00:00:00Z".to_string(),
        };

        let json = serde_json::to_string(&dto).unwrap();
        assert!(json.contains("Test comment"));
        assert!(json.contains("John Doe"));
    }
}
