//! Enhanced task management handlers for update, delete, assign, and deadline operations

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

/// Update task request
#[derive(Debug, Deserialize)]
pub struct UpdateTaskRequest {
    /// Task description
    pub description: Option<String>,
    /// Task completed status
    pub completed: Option<bool>,
    /// Task priority
    pub priority: Option<String>,
    /// Task tags (JSON array)
    pub tags: Option<String>,
    /// Estimated hours
    pub estimated_hours: Option<f64>,
    /// Actual hours
    pub actual_hours: Option<f64>,
}

/// Assign task request
#[derive(Debug, Deserialize)]
pub struct AssignTaskRequest {
    /// Assignee user ID
    pub assignee_id: String,
}

/// Set deadline request
#[derive(Debug, Deserialize)]
pub struct SetDeadlineRequest {
    /// Deadline timestamp
    pub deadline: String,
}

/// Enhanced task DTO
#[derive(Debug, Serialize)]
pub struct EnhancedTaskDto {
    /// Task ID
    pub id: String,
    /// Task description
    pub description: String,
    /// Completed status
    pub completed: bool,
    /// Assignee ID
    pub assignee_id: Option<String>,
    /// Deadline
    pub deadline: Option<String>,
    /// Priority
    pub priority: Option<String>,
    /// Tags
    pub tags: Option<String>,
    /// Estimated hours
    pub estimated_hours: Option<f64>,
    /// Actual hours
    pub actual_hours: Option<f64>,
    /// Created timestamp
    pub created_at: String,
    /// Completed timestamp
    pub completed_at: Option<String>,
}

/// Update a task
///
/// Updates task properties such as description, completion status, priority, tags, and time tracking.
pub async fn update_task(
    State(state): State<AppState>,
    Path((project_id, task_id)): Path<(String, String)>,
    Json(req): Json<UpdateTaskRequest>,
) -> ApiResult<Json<EnhancedTaskDto>> {
    // Verify project exists
    let _project = state
        .db
        .get_project(&project_id)
        .await?
        .ok_or_else(|| ApiError::NotFound(format!("Project {} not found", project_id)))?;

    // Fetch current task
    let task: EnhancedTaskTuple = sqlx::query_as(
        r#"
        SELECT id, description, completed, assignee_id, deadline, priority, tags,
               estimated_hours, actual_hours, created_at, completed_at
        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)))?;

    // Update task fields
    let description = req.description.unwrap_or(task.1);
    let completed = req.completed.unwrap_or(task.2);
    let priority = req.priority.or(task.5);
    let tags = req.tags.or(task.6);
    let estimated_hours = req.estimated_hours.or(task.7);
    let actual_hours = req.actual_hours.or(task.8);
    let completed_at = if completed && task.10.is_none() {
        Some(Utc::now().to_rfc3339())
    } else if !completed {
        None
    } else {
        task.10
    };

    sqlx::query(
        r#"
        UPDATE checklist_items
        SET description = ?, completed = ?, priority = ?, tags = ?,
            estimated_hours = ?, actual_hours = ?, completed_at = ?
        WHERE id = ?
        "#,
    )
    .bind(&description)
    .bind(completed)
    .bind(&priority)
    .bind(&tags)
    .bind(estimated_hours)
    .bind(actual_hours)
    .bind(&completed_at)
    .bind(&task_id)
    .execute(state.db.pool())
    .await
    .map_err(|e| ApiError::Database(e.to_string()))?;

    // Broadcast task updated event
    state
        .events
        .broadcast(crate::websocket::ProjectEvent::TaskUpdated {
            project_id: project_id.clone(),
            task_id: task_id.clone(),
            timestamp: Utc::now().to_rfc3339(),
        });

    Ok(Json(EnhancedTaskDto {
        id: task_id,
        description,
        completed,
        assignee_id: task.3,
        deadline: task.4,
        priority,
        tags,
        estimated_hours,
        actual_hours,
        created_at: task.9,
        completed_at,
    }))
}

/// Delete a task
///
/// Permanently deletes a task from the project.
pub async fn delete_task(
    State(state): State<AppState>,
    Path((project_id, task_id)): Path<(String, String)>,
) -> ApiResult<StatusCode> {
    // Verify project exists
    let _project = state
        .db
        .get_project(&project_id)
        .await?
        .ok_or_else(|| ApiError::NotFound(format!("Project {} not found", project_id)))?;

    // Delete task
    let result = sqlx::query("DELETE FROM checklist_items WHERE id = ? AND project_id = ?")
        .bind(&task_id)
        .bind(&project_id)
        .execute(state.db.pool())
        .await
        .map_err(|e| ApiError::Database(e.to_string()))?;

    if result.rows_affected() == 0 {
        return Err(ApiError::NotFound(format!("Task {} not found", task_id)));
    }

    // Broadcast task deleted event
    state
        .events
        .broadcast(crate::websocket::ProjectEvent::TaskDeleted {
            project_id,
            task_id,
            timestamp: Utc::now().to_rfc3339(),
        });

    Ok(StatusCode::NO_CONTENT)
}

/// Assign a task to a user
///
/// Assigns a task to a specific user and broadcasts the assignment event.
pub async fn assign_task(
    State(state): State<AppState>,
    Path((project_id, task_id)): Path<(String, String)>,
    Json(req): Json<AssignTaskRequest>,
) -> ApiResult<Json<EnhancedTaskDto>> {
    // Verify project exists
    let _project = state
        .db
        .get_project(&project_id)
        .await?
        .ok_or_else(|| ApiError::NotFound(format!("Project {} not found", project_id)))?;

    // Verify assignee exists
    let assignee: (String, String) = sqlx::query_as("SELECT id, name FROM users WHERE id = ?")
        .bind(&req.assignee_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.assignee_id)))?;

    // Update task assignee
    sqlx::query("UPDATE checklist_items SET assignee_id = ? WHERE id = ? AND project_id = ?")
        .bind(&req.assignee_id)
        .bind(&task_id)
        .bind(&project_id)
        .execute(state.db.pool())
        .await
        .map_err(|e| ApiError::Database(e.to_string()))?;

    // Fetch updated task
    let task: EnhancedTaskTuple = sqlx::query_as(
        r#"
        SELECT id, description, completed, assignee_id, deadline, priority, tags,
               estimated_hours, actual_hours, created_at, completed_at
        FROM checklist_items
        WHERE id = ?
        "#,
    )
    .bind(&task_id)
    .fetch_one(state.db.pool())
    .await
    .map_err(|e| ApiError::Database(e.to_string()))?;

    // Broadcast task assigned event
    state
        .events
        .broadcast(crate::websocket::ProjectEvent::TaskAssigned {
            project_id,
            task_id: task_id.clone(),
            assignee_id: req.assignee_id,
            assignee_name: assignee.1,
            timestamp: Utc::now().to_rfc3339(),
        });

    Ok(Json(EnhancedTaskDto {
        id: task.0,
        description: task.1,
        completed: task.2,
        assignee_id: task.3,
        deadline: task.4,
        priority: task.5,
        tags: task.6,
        estimated_hours: task.7,
        actual_hours: task.8,
        created_at: task.9,
        completed_at: task.10,
    }))
}

/// Set task deadline
///
/// Sets or updates the deadline for a task.
pub async fn set_deadline(
    State(state): State<AppState>,
    Path((project_id, task_id)): Path<(String, String)>,
    Json(req): Json<SetDeadlineRequest>,
) -> ApiResult<Json<EnhancedTaskDto>> {
    // Verify project exists
    let _project = state
        .db
        .get_project(&project_id)
        .await?
        .ok_or_else(|| ApiError::NotFound(format!("Project {} not found", project_id)))?;

    // Update task deadline
    sqlx::query("UPDATE checklist_items SET deadline = ? WHERE id = ? AND project_id = ?")
        .bind(&req.deadline)
        .bind(&task_id)
        .bind(&project_id)
        .execute(state.db.pool())
        .await
        .map_err(|e| ApiError::Database(e.to_string()))?;

    // Fetch updated task
    let task: EnhancedTaskTuple = sqlx::query_as(
        r#"
        SELECT id, description, completed, assignee_id, deadline, priority, tags,
               estimated_hours, actual_hours, created_at, completed_at
        FROM checklist_items
        WHERE id = ?
        "#,
    )
    .bind(&task_id)
    .fetch_one(state.db.pool())
    .await
    .map_err(|e| ApiError::Database(e.to_string()))?;

    // Broadcast deadline set event
    state
        .events
        .broadcast(crate::websocket::ProjectEvent::DeadlineSet {
            project_id,
            task_id: task_id.clone(),
            deadline: req.deadline.clone(),
            timestamp: Utc::now().to_rfc3339(),
        });

    Ok(Json(EnhancedTaskDto {
        id: task.0,
        description: task.1,
        completed: task.2,
        assignee_id: task.3,
        deadline: task.4,
        priority: task.5,
        tags: task.6,
        estimated_hours: task.7,
        actual_hours: task.8,
        created_at: task.9,
        completed_at: task.10,
    }))
}

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

    #[test]
    fn test_update_task_request_deserialization() {
        let json = r#"{"description":"New description","priority":"high"}"#;
        let req: UpdateTaskRequest = serde_json::from_str(json).unwrap();

        assert_eq!(req.description, Some("New description".to_string()));
        assert_eq!(req.priority, Some("high".to_string()));
        assert!(req.completed.is_none());
    }

    #[test]
    fn test_assign_task_request() {
        let json = r#"{"assignee_id":"user-123"}"#;
        let req: AssignTaskRequest = serde_json::from_str(json).unwrap();

        assert_eq!(req.assignee_id, "user-123");
    }

    #[test]
    fn test_set_deadline_request() {
        let json = r#"{"deadline":"2025-12-31T23:59:59Z"}"#;
        let req: SetDeadlineRequest = serde_json::from_str(json).unwrap();

        assert_eq!(req.deadline, "2025-12-31T23:59:59Z");
    }
}
