use std::{
    collections::{HashMap, VecDeque},
    path::{Path, PathBuf},
    sync::Arc,
};

use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use tokio::{fs, sync::RwLock};
use tracing::{debug, info, warn};

use crate::{TaskStatus, WorkflowTask};

const TASKS_DIR: &str = "tasks";
const QUEUE_DIR: &str = "queue";

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueuedTask {
    pub id: String,
    pub priority: u8,
    pub enqueued_at: chrono::DateTime<chrono::Utc>,
    pub retry_count: u8,
    pub max_retries: u8,
}

pub struct TaskQueue {
    data_dir: PathBuf,
    in_memory_queue: Arc<RwLock<VecDeque<QueuedTask>>>,
    tasks: Arc<RwLock<HashMap<String, WorkflowTask>>>,
}

impl TaskQueue {
    pub async fn new<P: AsRef<Path>>(data_dir: P) -> Result<Self> {
        let data_dir = data_dir.as_ref().to_path_buf();

        // Create required directories
        fs::create_dir_all(&data_dir)
            .await
            .context("Failed to create data directory")?;
        fs::create_dir_all(data_dir.join(TASKS_DIR))
            .await
            .context("Failed to create tasks directory")?;
        fs::create_dir_all(data_dir.join(QUEUE_DIR))
            .await
            .context("Failed to create queue directory")?;

        let queue = Self {
            data_dir,
            in_memory_queue: Arc::new(RwLock::new(VecDeque::new())),
            tasks: Arc::new(RwLock::new(HashMap::new())),
        };

        // Load existing tasks from persistent storage
        queue.load_from_storage().await?;

        Ok(queue)
    }

    async fn load_from_storage(&self) -> Result<()> {
        let mut tasks = self.tasks.write().await;
        let mut queue = self.in_memory_queue.write().await;

        // Load all tasks
        let tasks_dir = self.data_dir.join(TASKS_DIR);
        if tasks_dir.exists() {
            let mut entries = fs::read_dir(&tasks_dir).await?;
            while let Some(entry) = entries.next_entry().await? {
                if !entry.file_type().await?.is_file() {
                    continue;
                }
                let file_name = entry.file_name();
                let Some(task_id) = file_name.to_str() else {
                    continue;
                };
                let Some(task_id_stripped) = task_id.strip_suffix(".json") else {
                    continue;
                };
                let content = fs::read_to_string(entry.path()).await?;
                let task: WorkflowTask = serde_json::from_str(&content)?;
                tasks.insert(task_id_stripped.to_string(), task);
            }
        }

        // Load queue entries
        let queue_dir = self.data_dir.join(QUEUE_DIR);
        if queue_dir.exists() {
            let mut entries = fs::read_dir(&queue_dir).await?;
            while let Some(entry) = entries.next_entry().await? {
                if entry.file_type().await?.is_file() {
                    let content = fs::read_to_string(entry.path()).await?;
                    let queued_task: QueuedTask = serde_json::from_str(&content)?;
                    queue.push_back(queued_task);
                }
            }
        }

        // Sort queue by priority (higher priority first) and enqueue time
        let mut sorted_queue: Vec<_> = queue.drain(..).collect();
        sorted_queue.sort_by(|a, b| {
            b.priority
                .cmp(&a.priority)
                .then_with(|| a.enqueued_at.cmp(&b.enqueued_at))
        });

        queue.extend(sorted_queue);

        info!(
            "Loaded {} tasks and {} queue entries from storage",
            tasks.len(),
            queue.len()
        );

        Ok(())
    }

    pub async fn enqueue(&self, task: WorkflowTask, priority: u8) -> Result<()> {
        let task_id = task.id.clone();

        // Store task to file
        let task_path = self
            .data_dir
            .join(TASKS_DIR)
            .join(format!("{}.json", task_id));
        let task_data = serde_json::to_string_pretty(&task)?;
        fs::write(&task_path, task_data)
            .await
            .context("Failed to write task to storage")?;

        // Add to in-memory tasks
        {
            let mut tasks = self.tasks.write().await;
            tasks.insert(task_id.clone(), task);
        }

        // Create queue entry
        let queued_task = QueuedTask {
            id: task_id.clone(),
            priority,
            enqueued_at: chrono::Utc::now(),
            retry_count: 0,
            max_retries: 3,
        };

        // Store queue entry to file
        let queue_path = self
            .data_dir
            .join(QUEUE_DIR)
            .join(format!("{}.json", task_id));
        let queue_data = serde_json::to_string_pretty(&queued_task)?;
        fs::write(&queue_path, queue_data)
            .await
            .context("Failed to write queue entry to storage")?;

        // Add to in-memory queue with priority ordering
        {
            let mut queue = self.in_memory_queue.write().await;

            // Find the right position to insert based on priority
            let insert_pos = queue
                .iter()
                .position(|item| item.priority < priority)
                .unwrap_or(queue.len());

            queue.insert(insert_pos, queued_task);
        }

        debug!("Enqueued task {} with priority {}", task_id, priority);
        Ok(())
    }

    pub async fn dequeue(&self) -> Result<Option<WorkflowTask>> {
        let queued_task = {
            let mut queue = self.in_memory_queue.write().await;
            queue.pop_front()
        };

        match queued_task {
            Some(queued) => {
                // Remove from persistent queue
                let queue_path = self
                    .data_dir
                    .join(QUEUE_DIR)
                    .join(format!("{}.json", queued.id));
                if queue_path.exists() {
                    fs::remove_file(&queue_path)
                        .await
                        .context("Failed to remove queue entry from storage")?;
                }

                // Get the actual task
                let tasks = self.tasks.read().await;
                if let Some(task) = tasks.get(&queued.id) {
                    debug!("Dequeued task {}", queued.id);
                    Ok(Some(task.clone()))
                } else {
                    warn!("Queued task {} not found in task storage", queued.id);
                    Ok(None)
                }
            }
            None => Ok(None),
        }
    }

    pub async fn peek(&self) -> Result<Option<WorkflowTask>> {
        let queue = self.in_memory_queue.read().await;

        if let Some(queued) = queue.front() {
            let tasks = self.tasks.read().await;
            if let Some(task) = tasks.get(&queued.id) {
                Ok(Some(task.clone()))
            } else {
                warn!("Queued task {} not found in task storage", queued.id);
                Ok(None)
            }
        } else {
            Ok(None)
        }
    }

    pub async fn get_task(&self, id: &str) -> Option<WorkflowTask> {
        let tasks = self.tasks.read().await;
        tasks.get(id).cloned()
    }

    pub async fn update_task(&self, task: WorkflowTask) -> Result<()> {
        let task_id = task.id.clone();

        // Update persistent storage
        let task_path = self
            .data_dir
            .join(TASKS_DIR)
            .join(format!("{}.json", task_id));
        let task_data = serde_json::to_string_pretty(&task)?;
        fs::write(&task_path, task_data)
            .await
            .context("Failed to update task in storage")?;

        // Update in-memory storage
        {
            let mut tasks = self.tasks.write().await;
            tasks.insert(task_id.clone(), task);
        }

        debug!("Updated task {}", task_id);
        Ok(())
    }

    pub async fn update_task_status(&self, id: &str, status: TaskStatus) -> Result<()> {
        let mut task = {
            let tasks = self.tasks.read().await;
            tasks.get(id).cloned()
        };

        if let Some(ref mut task) = task {
            task.status = status.clone();

            match status {
                TaskStatus::Running => task.started_at = Some(chrono::Utc::now()),
                TaskStatus::Completed | TaskStatus::Failed | TaskStatus::Cancelled => {
                    task.completed_at = Some(chrono::Utc::now());
                }
                _ => {}
            }

            self.update_task(task.clone()).await?;
            debug!("Updated task {} status to {:?}", id, status);
        }

        Ok(())
    }

    pub async fn requeue_failed_task(&self, id: &str) -> Result<bool> {
        let queue_path = self.data_dir.join(QUEUE_DIR).join(format!("{}.json", id));

        let queued_task = if queue_path.exists() {
            let content = fs::read_to_string(&queue_path)
                .await
                .context("Failed to read queue entry")?;
            let mut queued: QueuedTask = serde_json::from_str(&content)?;

            if queued.retry_count < queued.max_retries {
                queued.retry_count += 1;
                queued.enqueued_at = chrono::Utc::now();

                // Update in storage
                let queue_data = serde_json::to_string_pretty(&queued)?;
                fs::write(&queue_path, queue_data)
                    .await
                    .context("Failed to update queue entry")?;

                Some(queued)
            } else {
                warn!("Task {} exceeded max retries ({})", id, queued.max_retries);
                None
            }
        } else {
            None
        };

        if let Some(queued) = queued_task {
            // Reset task status to pending
            self.update_task_status(id, TaskStatus::Pending).await?;

            // Add back to queue
            {
                let mut queue = self.in_memory_queue.write().await;
                let insert_pos = queue
                    .iter()
                    .position(|item| item.priority < queued.priority)
                    .unwrap_or(queue.len());
                queue.insert(insert_pos, queued);
            }

            info!("Requeued failed task {} for retry", id);
            Ok(true)
        } else {
            Ok(false)
        }
    }

    pub async fn list_tasks(&self, status_filter: Option<TaskStatus>) -> Vec<WorkflowTask> {
        let tasks = self.tasks.read().await;

        tasks
            .values()
            .filter(|task| {
                status_filter
                    .as_ref()
                    .is_none_or(|filter| &task.status == filter)
            })
            .cloned()
            .collect()
    }

    pub async fn queue_size(&self) -> usize {
        let queue = self.in_memory_queue.read().await;
        queue.len()
    }

    pub async fn total_tasks(&self) -> usize {
        let tasks = self.tasks.read().await;
        tasks.len()
    }

    pub async fn cleanup_completed_tasks(&self, older_than: chrono::Duration) -> Result<usize> {
        let cutoff = chrono::Utc::now() - older_than;
        let mut removed_count = 0;

        let tasks_to_remove: Vec<String> = {
            let tasks = self.tasks.read().await;
            tasks
                .values()
                .filter(|task| {
                    matches!(
                        task.status,
                        TaskStatus::Completed | TaskStatus::Failed | TaskStatus::Cancelled
                    ) && task.completed_at.map(|t| t < cutoff).unwrap_or(false)
                })
                .map(|task| task.id.clone())
                .collect()
        };

        for task_id in tasks_to_remove {
            // Remove from persistent storage
            let task_path = self
                .data_dir
                .join(TASKS_DIR)
                .join(format!("{}.json", task_id));
            let queue_path = self
                .data_dir
                .join(QUEUE_DIR)
                .join(format!("{}.json", task_id));

            if task_path.exists() {
                fs::remove_file(&task_path).await?;
            }
            if queue_path.exists() {
                fs::remove_file(&queue_path).await?;
            }

            // Remove from in-memory storage
            {
                let mut tasks = self.tasks.write().await;
                tasks.remove(&task_id);
            }

            removed_count += 1;
        }

        if removed_count > 0 {
            info!(
                "Cleaned up {} completed tasks older than {:?}",
                removed_count, older_than
            );
        }

        Ok(removed_count)
    }
}
