use std::sync::Arc;

use common_contracts::{
    dtos::{CompanyProfileDto, TimeSeriesFinancialDto, SessionDataDto, ProviderCacheDto},
    messages::{CompanyProfilePersistedEvent, FetchCompanyDataCommand, FinancialsPersistedEvent, DataFetchFailedEvent},
    observability::ObservabilityTaskStatus,
    persistence_client::PersistenceClient,
};
use tokio::sync::mpsc;
use tracing::{info, error};
use chrono::{Datelike, Utc, Duration};

use crate::{error::AppError, state::AppState};

pub async fn run_tushare_workflow(
    state: Arc<AppState>,
    command: FetchCompanyDataCommand,
    completion_tx: mpsc::Sender<()>,
) -> Result<(), AppError> {
    let task_id = command.request_id;
    let symbol = command.symbol.clone();

    match run_tushare_workflow_inner(state.clone(), &command).await {
        Ok(_) => {
            info!("Tushare workflow for symbol {} completed successfully.", symbol);
            let _ = completion_tx.send(()).await;
            Ok(())
        }
        Err(e) => {
            error!("Tushare workflow for symbol {} failed: {}", symbol, e);
            
            // Try to publish failure event
            if let Ok(nats_client) = async_nats::connect(&state.config.nats_addr).await {
                 let _ = publish_failure_event(&nats_client, &command, e.to_string()).await;
            } else {
                error!("Failed to connect to NATS to report failure for {}", symbol);
            }
            
            // Ensure task status is failed in memory if it wasn't already
            if let Some(mut task) = state.tasks.get_mut(&task_id) {
                if task.status != ObservabilityTaskStatus::Failed {
                    task.status = ObservabilityTaskStatus::Failed;
                    task.details = format!("Workflow failed: {}", e);
                }
            }

            let _ = completion_tx.send(()).await;
            Err(e)
        }
    }
}

async fn run_tushare_workflow_inner(
    state: Arc<AppState>,
    command: &FetchCompanyDataCommand,
) -> Result<Vec<TimeSeriesFinancialDto>, AppError> {
    let task_id = command.request_id;
    let symbol = command.symbol.clone();

    let provider = match state.get_provider().await {
        Some(p) => p,
        None => {
            let reason = "Execution failed: Tushare provider is not available (misconfigured).".to_string();
            error!("{}", reason);
            if let Some(mut task) = state.tasks.get_mut(&task_id) {
                task.status = ObservabilityTaskStatus::Failed;
                task.details = reason.clone();
            }
            return Err(AppError::ProviderNotAvailable(reason));
        }
    };

    let persistence_client = PersistenceClient::new(state.config.data_persistence_service_url.clone());

    // 1. Update task progress: Checking Cache
    {
        let mut entry = state
        .tasks
        .get_mut(&task_id)
        .ok_or_else(|| AppError::Internal("Task not found".to_string()))?;
        entry.status = ObservabilityTaskStatus::InProgress;
        entry.progress_percent = 10;
        entry.details = "Checking cache...".to_string();
    }

    // Cache Key: tushare:{symbol}:all
    let cache_key = format!("tushare:{}:all", symbol);
    
    let (profile, financials) = match persistence_client.get_cache(&cache_key).await.map_err(|e| AppError::Internal(e.to_string()))? {
        Some(cache_entry) => {
             info!("Cache HIT for {}", cache_key);
             // Deserialize
             let data: (CompanyProfileDto, Vec<TimeSeriesFinancialDto>) = serde_json::from_value(cache_entry.data_payload)
                .map_err(|e| AppError::Internal(format!("Failed to deserialize cache: {}", e)))?;
             
             {
                let mut entry = state.tasks.get_mut(&task_id).unwrap();
                entry.details = "Data retrieved from cache".to_string();
                entry.progress_percent = 50;
             }
             data
        },
        None => {
             info!("Cache MISS for {}", cache_key);
             {
                let mut entry = state.tasks.get_mut(&task_id).unwrap();
                entry.details = "Fetching from Tushare API...".to_string();
                entry.progress_percent = 20;
             }

             let (p, f) = provider.fetch_all_data(symbol.as_str()).await?;
             
             // Write to Cache
             let payload = serde_json::json!((&p, &f));
             persistence_client.set_cache(&ProviderCacheDto {
                 cache_key,
                 data_payload: payload,
                 expires_at: Utc::now() + Duration::hours(24),
                 updated_at: None,
             }).await.map_err(|e| AppError::Internal(e.to_string()))?;

             (p, f)
        }
    };

    // 2. Snapshot to Session Data (and update Global Profile)
    {
        let mut entry = state.tasks.get_mut(&task_id).unwrap();
        entry.details = "Snapshotting data...".to_string();
        entry.progress_percent = 80;
    }

    // Global Profile (Optional, but good for search)
    // Ignore errors here as it's secondary
    // REMOVED: upsert_company_profile is deprecated.
    // let _ = persistence_client.upsert_company_profile(profile.clone()).await;

    // Snapshot Profile
    persistence_client.insert_session_data(&SessionDataDto {
        request_id: task_id,
        symbol: symbol.clone().into(), // CanonicalSymbol to string
        provider: "tushare".to_string(),
        data_type: "company_profile".to_string(),
        data_payload: serde_json::to_value(&profile).unwrap(),
        created_at: None,
    }).await.map_err(|e| AppError::Internal(e.to_string()))?;

    // Snapshot Financials
    persistence_client.insert_session_data(&SessionDataDto {
        request_id: task_id,
        symbol: symbol.clone().into(),
        provider: "tushare".to_string(),
        data_type: "financial_statements".to_string(),
        data_payload: serde_json::to_value(&financials).unwrap(),
        created_at: None,
    }).await.map_err(|e| AppError::Internal(e.to_string()))?;


    // 3. Publish events
    let nats_client = async_nats::connect(&state.config.nats_addr)
        .await
        .map_err(|e| AppError::Internal(format!("NATS connection failed: {}", e)))?;

    publish_events(&nats_client, command, &financials).await?;

    // 4. Finalize task
    {
        let mut entry = state
        .tasks
        .get_mut(&task_id)
        .ok_or_else(|| AppError::Internal("Task not found".to_string()))?;
        entry.status = ObservabilityTaskStatus::Completed;
        entry.progress_percent = 100;
        entry.details = "Workflow finished successfully".to_string();
    }

    Ok(financials)
}

async fn publish_events(
    nats_client: &async_nats::Client,
    command: &FetchCompanyDataCommand,
    financials: &[TimeSeriesFinancialDto],
) -> Result<(), AppError> {
    let profile_event = CompanyProfilePersistedEvent {
        request_id: command.request_id,
        symbol: command.symbol.clone(),
    };
    nats_client
        .publish(
            "events.data.company_profile_persisted",
            serde_json::to_vec(&profile_event).unwrap().into(),
        )
        .await?;

    let years: std::collections::BTreeSet<u16> = financials
        .iter()
        .map(|f| f.period_date.year() as u16)
        .collect();
    
    let summary = format!("Fetched {} years of financial data", years.len());

    let financials_event = FinancialsPersistedEvent {
        request_id: command.request_id,
        symbol: command.symbol.clone(),
        years_updated: years.into_iter().collect(),
        template_id: command.template_id.clone(),
        provider_id: Some("tushare".to_string()),
        data_summary: Some(summary),
    };
    nats_client
        .publish(
            "events.data.financials_persisted",
            serde_json::to_vec(&financials_event).unwrap().into(),
        )
        .await?;

    Ok(())
}

async fn publish_failure_event(
    nats_client: &async_nats::Client,
    command: &FetchCompanyDataCommand,
    error_msg: String,
) -> Result<(), AppError> {
    let event = DataFetchFailedEvent {
        request_id: command.request_id,
        symbol: command.symbol.clone(),
        error: error_msg,
        provider_id: Some("tushare".to_string()),
    };
    nats_client
        .publish(
            "events.data.fetch_failed",
            serde_json::to_vec(&event).unwrap().into(),
        )
        .await
        .map_err(|e| AppError::Internal(format!("Failed to publish failure event: {}", e)))?;
    Ok(())
}

#[cfg(test)]
mod integration_tests {
    use super::*;
    use crate::config::AppConfig;
    use crate::state::AppState;
    use secrecy::SecretString;
    use common_contracts::observability::TaskProgress;
    use common_contracts::symbol_utils::{CanonicalSymbol, Market};
    use uuid::Uuid;

    #[tokio::test]
    async fn test_tushare_fetch_flow() {
        if std::env::var("NATS_ADDR").is_err() {
             println!("Skipping integration test (no environment)");
             return;
        }

        // 1. Environment
        let api_key = std::env::var("TUSHARE_API_KEY")
            .unwrap_or_else(|_| "f62b415de0a5a947fcb693b66cd299dd6242868bf04ad687800c7f3f".to_string());
        
        let api_url = std::env::var("TUSHARE_API_URL")
            .unwrap_or_else(|_| "http://api.tushare.pro".to_string());

        let config = AppConfig::load().expect("Failed to load config");
        let state = Arc::new(AppState::new(config.clone()));

        // 2. Manual Init Provider
        state.update_provider(
            Some(SecretString::new(api_key.into())),
            Some(api_url)
        ).await;

        assert!(state.get_provider().await.is_some());

        // 3. Construct Command (Use Maotai as example)
        let request_id = Uuid::new_v4();
        let cmd = FetchCompanyDataCommand {
            request_id,
            symbol: CanonicalSymbol::new("600519", &Market::CN),
            market: "CN".to_string(),
            template_id: Some("default".to_string()),
        };
        
        // Init task in map (usually done by handler wrapper)
        state.tasks.insert(request_id, TaskProgress {
             request_id,
             task_name: "tushare:600519".to_string(),
             status: ObservabilityTaskStatus::Queued,
             progress_percent: 0,
             details: "Init".to_string(),
             started_at: Utc::now()
        });

        // 4. Run
        let result = run_tushare_workflow_inner(state.clone(), &cmd).await;

        // 5. Assert
        assert!(result.is_ok(), "Worker execution failed: {:?}", result.err());
        
        let task = state.tasks.get(&request_id).expect("Task should exist");
        assert_eq!(task.status, ObservabilityTaskStatus::Completed);
    }
}
