use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};

use serde::{Deserialize, Serialize};
use tauri::{Emitter, Manager, Runtime, Wry};

use crate::agent::{AgentRuntimeConfig, TaskProfile};
use crate::error::AppError;

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BackgroundJobKind {
    NewsScan,
    WatchlistBrief,
    MultiSymbolAnalysis,
    ReportCompilation,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BackgroundJobStatus {
    Queued,
    Running,
    Completed,
    Failed,
    Cancelled,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StartBackgroundJobRequest {
    pub workspace_id: String,
    pub kind: BackgroundJobKind,
    pub params: Option<serde_json::Value>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BackgroundJobSnapshot {
    pub job_id: String,
    pub workspace_id: String,
    pub kind: BackgroundJobKind,
    pub status: BackgroundJobStatus,
    pub progress: Option<f32>,
    pub label: Option<String>,
    pub result: Option<serde_json::Value>,
    pub error: Option<String>,
    pub created_at: String,
    pub updated_at: String,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CancelBackgroundJobRequest {
    pub job_id: String,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BackgroundJobEvent {
    pub job_id: String,
    pub workspace_id: String,
    pub kind: BackgroundJobKind,
    pub status: BackgroundJobStatus,
    pub progress: Option<f32>,
    pub label: Option<String>,
    pub result: Option<serde_json::Value>,
    pub error: Option<String>,
}

struct JobEntry {
    snapshot: BackgroundJobSnapshot,
    cancel_sender: Option<tokio::sync::oneshot::Sender<()>>,
}

pub struct BackgroundJobCoordinator<R: Runtime> {
    app_handle: tauri::AppHandle<R>,
    jobs: Mutex<HashMap<String, JobEntry>>,
    next_job_id: AtomicU64,
}

impl<R: Runtime> BackgroundJobCoordinator<R> {
    pub fn new(app_handle: tauri::AppHandle<R>) -> Self {
        Self {
            app_handle,
            jobs: Mutex::new(HashMap::new()),
            next_job_id: AtomicU64::new(1),
        }
    }

    pub fn get_job_status(&self, job_id: &str) -> Result<BackgroundJobSnapshot, AppError> {
        let jobs = self.jobs.lock().map_err(|_| {
            AppError::InvalidSettings("background job state is unavailable".to_string())
        })?;
        jobs.get(job_id)
            .map(|entry| entry.snapshot.clone())
            .ok_or_else(|| AppError::BackgroundJobNotFound(job_id.to_string()))
    }

    pub fn cancel_job(&self, job_id: &str) -> Result<(), AppError> {
        let mut jobs = self.jobs.lock().map_err(|_| {
            AppError::InvalidSettings("background job state is unavailable".to_string())
        })?;

        let entry = jobs
            .get_mut(job_id)
            .ok_or_else(|| AppError::BackgroundJobNotFound(job_id.to_string()))?;

        if let Some(sender) = entry.cancel_sender.take() {
            let _ = sender.send(());
        }

        entry.snapshot.status = BackgroundJobStatus::Cancelled;
        entry.snapshot.updated_at = chrono::Utc::now().to_rfc3339();

        let workspace_id = entry.snapshot.workspace_id.clone();
        let kind = entry.snapshot.kind;

        drop(jobs);

        self.emit_job_event(
            job_id,
            &workspace_id,
            kind,
            BackgroundJobStatus::Cancelled,
            None,
            None,
            None,
            None,
        )
    }

    fn emit_job_event(
        &self,
        job_id: &str,
        workspace_id: &str,
        kind: BackgroundJobKind,
        status: BackgroundJobStatus,
        progress: Option<f32>,
        label: Option<String>,
        result: Option<serde_json::Value>,
        error: Option<String>,
    ) -> Result<(), AppError> {
        self.app_handle
            .emit(
                "background_job_event",
                BackgroundJobEvent {
                    job_id: job_id.to_string(),
                    workspace_id: workspace_id.to_string(),
                    kind,
                    status,
                    progress,
                    label,
                    result,
                    error,
                },
            )
            .map_err(|error| AppError::ProviderRequest(error.to_string()))
    }
}

impl BackgroundJobCoordinator<Wry> {
    pub fn start_job(
        &self,
        request: StartBackgroundJobRequest,
        runtime: AgentRuntimeConfig,
        task_profile: TaskProfile,
    ) -> Result<BackgroundJobSnapshot, AppError> {
        let job_id = format!("job-{}", self.next_job_id.fetch_add(1, Ordering::Relaxed));
        let now = chrono::Utc::now().to_rfc3339();
        let label = job_label(&request.kind);

        let snapshot = BackgroundJobSnapshot {
            job_id: job_id.clone(),
            workspace_id: request.workspace_id.clone(),
            kind: request.kind,
            status: BackgroundJobStatus::Queued,
            progress: None,
            label: Some(label),
            result: None,
            error: None,
            created_at: now.clone(),
            updated_at: now,
        };

        let (cancel_sender, cancel_receiver) = tokio::sync::oneshot::channel();

        {
            let mut jobs = self.jobs.lock().map_err(|_| {
                AppError::InvalidSettings("background job state is unavailable".to_string())
            })?;
            jobs.insert(
                job_id.clone(),
                JobEntry {
                    snapshot: snapshot.clone(),
                    cancel_sender: Some(cancel_sender),
                },
            );
        }

        let app_handle = self.app_handle.clone();
        let job_id_for_task = job_id.clone();
        let workspace_id = request.workspace_id.clone();
        let kind = request.kind;

        tauri::async_runtime::spawn(async move {
            let _ = run_background_job(
                app_handle,
                job_id_for_task,
                workspace_id,
                kind,
                request.params.clone(),
                runtime,
                task_profile,
                cancel_receiver,
            )
            .await;
        });

        self.emit_job_event(
            &job_id,
            &request.workspace_id,
            request.kind,
            BackgroundJobStatus::Queued,
            None,
            Some(job_label(&request.kind)),
            None,
            None,
        )?;

        Ok(snapshot)
    }

    fn update_job(
        &self,
        job_id: &str,
        status: BackgroundJobStatus,
        progress: Option<f32>,
        label: Option<String>,
        result: Option<serde_json::Value>,
        error: Option<String>,
    ) -> Result<(), AppError> {
        let mut jobs = self.jobs.lock().map_err(|_| {
            AppError::InvalidSettings("background job state is unavailable".to_string())
        })?;

        let entry = jobs
            .get_mut(job_id)
            .ok_or_else(|| AppError::BackgroundJobNotFound(job_id.to_string()))?;

        entry.snapshot.status = status;
        entry.snapshot.progress = progress;
        if label.is_some() {
            entry.snapshot.label = label;
        }
        if result.is_some() {
            entry.snapshot.result = result;
        }
        if error.is_some() {
            entry.snapshot.error = error;
        }
        entry.snapshot.updated_at = chrono::Utc::now().to_rfc3339();

        let workspace_id = entry.snapshot.workspace_id.clone();
        let kind = entry.snapshot.kind;
        let final_progress = entry.snapshot.progress;
        let final_label = entry.snapshot.label.clone();
        let final_result = entry.snapshot.result.clone();
        let final_error = entry.snapshot.error.clone();

        drop(jobs);

        self.emit_job_event(
            job_id,
            &workspace_id,
            kind,
            status,
            final_progress,
            final_label,
            final_result,
            final_error,
        )
    }
}

fn job_label(kind: &BackgroundJobKind) -> String {
    match kind {
        BackgroundJobKind::NewsScan => "Market News Scan".to_string(),
        BackgroundJobKind::WatchlistBrief => "Watchlist Brief".to_string(),
        BackgroundJobKind::MultiSymbolAnalysis => "Multi-Symbol Analysis".to_string(),
        BackgroundJobKind::ReportCompilation => "Report Compilation".to_string(),
    }
}

async fn run_background_job(
    app_handle: tauri::AppHandle<Wry>,
    job_id: String,
    _workspace_id: String,
    kind: BackgroundJobKind,
    params: Option<serde_json::Value>,
    runtime: AgentRuntimeConfig,
    _task_profile: TaskProfile,
    cancel_receiver: tokio::sync::oneshot::Receiver<()>,
) -> Result<(), AppError> {
    let coordinator = extract_coordinator(&app_handle)?;

    coordinator.update_job(
        &job_id,
        BackgroundJobStatus::Running,
        Some(0.0),
        Some(format!("Starting {}...", job_label(&kind))),
        None,
        None,
    )?;

    let result = tokio::select! {
        result = execute_job(kind, params, runtime, &app_handle, &job_id, &coordinator) => result,
        _ = cancel_receiver => {
            let _ = coordinator.update_job(
                &job_id,
                BackgroundJobStatus::Cancelled,
                None,
                None,
                None,
                None,
            );
            return Ok(());
        }
    };

    match result {
        Ok(output) => {
            coordinator.update_job(
                &job_id,
                BackgroundJobStatus::Completed,
                Some(1.0),
                Some(format!("{} complete", job_label(&kind))),
                Some(output),
                None,
            )?;
        }
        Err(error) => {
            coordinator.update_job(
                &job_id,
                BackgroundJobStatus::Failed,
                None,
                Some(format!("{} failed", job_label(&kind))),
                None,
                Some(error.to_string()),
            )?;
        }
    }

    Ok(())
}

fn extract_coordinator(
    app_handle: &tauri::AppHandle<Wry>,
) -> Result<Arc<BackgroundJobCoordinator<Wry>>, AppError> {
    let state = app_handle.state::<crate::state::AppState>();
    Ok(state.background_jobs.clone())
}

async fn execute_job(
    kind: BackgroundJobKind,
    params: Option<serde_json::Value>,
    runtime: AgentRuntimeConfig,
    app_handle: &tauri::AppHandle<Wry>,
    job_id: &str,
    coordinator: &Arc<BackgroundJobCoordinator<Wry>>,
) -> Result<serde_json::Value, AppError> {
    match kind {
        BackgroundJobKind::NewsScan => execute_news_scan(app_handle, job_id, coordinator).await,
        BackgroundJobKind::WatchlistBrief => {
            execute_watchlist_brief(&params, runtime, job_id, coordinator).await
        }
        BackgroundJobKind::MultiSymbolAnalysis => {
            execute_multi_symbol_analysis(&params, runtime, job_id, coordinator).await
        }
        BackgroundJobKind::ReportCompilation => {
            execute_report_compilation(&params, runtime, job_id, coordinator).await
        }
    }
}

async fn execute_news_scan(
    app_handle: &tauri::AppHandle<Wry>,
    job_id: &str,
    coordinator: &Arc<BackgroundJobCoordinator<Wry>>,
) -> Result<serde_json::Value, AppError> {
    let state = app_handle.state::<crate::state::AppState>();

    coordinator.update_job(
        job_id,
        BackgroundJobStatus::Running,
        Some(0.3),
        Some("Refreshing news feeds...".to_string()),
        None,
        None,
    )?;

    let result = state
        .news_service
        .refresh_feed(crate::news::RefreshNewsFeedRequest { force: Some(true) })
        .await
        .map_err(|error| AppError::ProviderRequest(error.to_string()))?;

    let articles = state
        .news_service
        .query_feed(crate::news::QueryNewsFeedRequest {
            ticker: None,
            search: None,
            only_highlighted: None,
            only_saved: None,
            only_unread: None,
            limit: Some(20),
            offset: None,
        })
        .await
        .map_err(|error| AppError::ProviderRequest(error.to_string()))?;

    coordinator.update_job(
        job_id,
        BackgroundJobStatus::Running,
        Some(0.8),
        Some(format!(
            "Scan complete. {} articles found.",
            articles.articles.len()
        )),
        None,
        None,
    )?;

    Ok(serde_json::json!({
        "feedsRefreshed": result.feeds_checked,
        "newArticles": result.new_articles,
        "totalArticles": articles.total,
    }))
}

async fn execute_watchlist_brief(
    params: &Option<serde_json::Value>,
    runtime: AgentRuntimeConfig,
    job_id: &str,
    coordinator: &Arc<BackgroundJobCoordinator<Wry>>,
) -> Result<serde_json::Value, AppError> {
    let symbols = extract_symbols(params);

    coordinator.update_job(
        job_id,
        BackgroundJobStatus::Running,
        Some(0.1),
        Some(format!("Generating brief for {} symbols...", symbols.len())),
        None,
        None,
    )?;

    let prompt = format!(
        "Generate a concise market brief for these symbols: {}. \
         For each symbol, provide a 1-2 sentence status update covering recent price action and key fundamentals. \
         End with overall market sentiment assessment.",
        symbols.join(", ")
    );

    let response = call_agent(runtime, &prompt).await?;

    Ok(serde_json::json!({
        "symbols": symbols,
        "brief": response,
    }))
}

async fn execute_multi_symbol_analysis(
    params: &Option<serde_json::Value>,
    runtime: AgentRuntimeConfig,
    job_id: &str,
    coordinator: &Arc<BackgroundJobCoordinator<Wry>>,
) -> Result<serde_json::Value, AppError> {
    let symbols = extract_symbols(params);

    coordinator.update_job(
        job_id,
        BackgroundJobStatus::Running,
        Some(0.1),
        Some(format!("Analyzing {} symbols...", symbols.len())),
        None,
        None,
    )?;

    let mut analyses = serde_json::Map::new();

    for (i, symbol) in symbols.iter().enumerate() {
        let progress = 0.1 + (0.8 * (i as f32 / symbols.len().max(1) as f32));
        coordinator.update_job(
            job_id,
            BackgroundJobStatus::Running,
            Some(progress),
            Some(format!("Analyzing {}...", symbol)),
            None,
            None,
        )?;

        let prompt = format!(
            "Analyze the stock {}. Provide a structured JSON analysis with these fields: \
             symbol, summary (2-3 sentences), sentiment (bullish/bearish/neutral), \
             keyPoints (3-5 items), risks (2-3 items), opportunities (2-3 items), \
             recommendation (buy/sell/hold), targetPrice (number or null). \
             Return only the JSON object.",
            symbol
        );

        match call_agent(runtime.clone(), &prompt).await {
            Ok(response) => {
                analyses.insert(symbol.clone(), serde_json::Value::String(response));
            }
            Err(_) => {
                analyses.insert(
                    symbol.clone(),
                    serde_json::Value::String("Analysis unavailable".to_string()),
                );
            }
        }
    }

    Ok(serde_json::Value::Object(analyses))
}

async fn execute_report_compilation(
    params: &Option<serde_json::Value>,
    runtime: AgentRuntimeConfig,
    job_id: &str,
    coordinator: &Arc<BackgroundJobCoordinator<Wry>>,
) -> Result<serde_json::Value, AppError> {
    let symbols = extract_symbols(params);
    let title = params
        .as_ref()
        .and_then(|p| p.get("title"))
        .and_then(|v| v.as_str())
        .unwrap_or("Research Report");

    coordinator.update_job(
        job_id,
        BackgroundJobStatus::Running,
        Some(0.1),
        Some("Compiling research report...".to_string()),
        None,
        None,
    )?;

    let prompt = format!(
        "Compile a research report titled '{}' covering these symbols: {}. \
         Structure the report with: Executive Summary, Individual Analysis (per symbol), \
         Comparative Analysis, Risk Assessment, and Conclusion. \
         Be data-driven and specific.",
        title,
        symbols.join(", ")
    );

    let response = call_agent(runtime, &prompt).await?;

    Ok(serde_json::json!({
        "title": title,
        "symbols": symbols,
        "report": response,
        "compiledAt": chrono::Utc::now().to_rfc3339(),
    }))
}

fn extract_symbols(params: &Option<serde_json::Value>) -> Vec<String> {
    params
        .as_ref()
        .and_then(|p| p.get("symbols"))
        .and_then(|v| v.as_array())
        .map(|arr| {
            arr.iter()
                .filter_map(|v| v.as_str().map(|s| s.to_uppercase()))
                .collect()
        })
        .unwrap_or_default()
}

async fn call_agent(runtime: AgentRuntimeConfig, prompt: &str) -> Result<String, AppError> {
    use crate::agent::gateway::ChatGateway;
    let gateway = crate::agent::RigChatGateway;
    gateway
        .summarize_history(runtime, None, vec![rig::completion::Message::user(prompt)])
        .await
}

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

    #[test]
    fn extracts_symbols_from_params() {
        let params = serde_json::json!({
            "symbols": ["AAPL", "msft", "GOOGL"]
        });
        let symbols = extract_symbols(&Some(params));
        assert_eq!(symbols, vec!["AAPL", "MSFT", "GOOGL"]);
    }

    #[test]
    fn returns_empty_symbols_when_missing() {
        let symbols = extract_symbols(&None);
        assert!(symbols.is_empty());
    }

    #[test]
    fn job_label_matches_kind() {
        assert_eq!(job_label(&BackgroundJobKind::NewsScan), "Market News Scan");
        assert_eq!(
            job_label(&BackgroundJobKind::WatchlistBrief),
            "Watchlist Brief"
        );
    }
}
