use common_contracts::dtos::{CompanyProfileDto, TimeSeriesFinancialDto, SessionDataDto, ProviderCacheDto};
use common_contracts::messages::{CompanyProfilePersistedEvent, FetchCompanyDataCommand, FinancialsPersistedEvent, DataFetchFailedEvent};
use common_contracts::observability::{TaskProgress, ObservabilityTaskStatus};
use common_contracts::persistence_client::PersistenceClient;
use crate::error::{Result, AppError};
use crate::state::AppState;
use tracing::{info, error};
use chrono::{Datelike, Utc, Duration};
use uuid::Uuid;

pub async fn handle_fetch_command(
    state: AppState,
    command: FetchCompanyDataCommand,
    publisher: async_nats::Client,
) -> Result<()> {
    match handle_fetch_command_inner(state.clone(), &command, &publisher).await {
        Ok(_) => Ok(()),
        Err(e) => {
            error!("YFinance workflow failed: {}", e);

            // Publish failure event
            let event = DataFetchFailedEvent {
                request_id: command.request_id,
                symbol: command.symbol.clone(),
                error: e.to_string(),
                provider_id: Some("yfinance".to_string()),
            };
            let _ = publisher
                .publish(
                    "events.data.fetch_failed".to_string(),
                    serde_json::to_vec(&event).unwrap().into(),
                )
                .await;

             // Update task status
            if let Some(mut task) = state.tasks.get_mut(&command.request_id) {
                 task.status = ObservabilityTaskStatus::Failed;
                 task.details = format!("Failed: {}", e);
            } else {
                 // If task doesn't exist (e.g. failed at insert), create a failed task
                 let task = TaskProgress {
                    request_id: command.request_id,
                    task_name: format!("yfinance:{}", command.symbol),
                    status: ObservabilityTaskStatus::Failed,
                    progress_percent: 0,
                    details: format!("Failed: {}", e),
                    started_at: Utc::now(),
                };
                state.tasks.insert(command.request_id, task);
            }

            Err(e)
        }
    }
}

async fn handle_fetch_command_inner(
    state: AppState,
    command: &FetchCompanyDataCommand,
    publisher: &async_nats::Client,
) -> Result<()> {
    let task_id = command.request_id;

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

    // Fetch dynamic config from data-persistence-service
    // adhering to "single source of truth" rule (DB config overrides env var)
    let config = client.get_data_sources_config().await?;
    let is_enabled = config.get("yfinance")
        .map(|c| c.enabled)
        .unwrap_or(false);

    if !is_enabled {
        info!("YFinance provider is disabled. Skipping task {}.", task_id);
        return Ok(());
    }

    state.tasks.insert(task_id, common_contracts::observability::TaskProgress {
        request_id: task_id,
        task_name: format!("yfinance:{}", command.symbol),
        status: ObservabilityTaskStatus::InProgress,
        progress_percent: 5,
        details: "Checking cache...".to_string(),
        started_at: chrono::Utc::now(),
    });

    let symbol = command.symbol.to_string();
    let cache_key = format!("yfinance:{}:all", symbol);
    
    // --- 1. Check Cache ---
    let (profile, financials) = match client.get_cache(&cache_key).await.map_err(|e| AppError::Internal(e.to_string()))? {
        Some(cache_entry) => {
             info!("Cache HIT for {}", cache_key);
             let data: (CompanyProfileDto, Vec<TimeSeriesFinancialDto>) = serde_json::from_value(cache_entry.data_payload)
                .map_err(|e| AppError::Internal(format!("Failed to deserialize cache: {}", e)))?;
             
             if let Some(mut task) = state.tasks.get_mut(&task_id) {
                task.status = ObservabilityTaskStatus::InProgress;
                task.progress_percent = 50;
                task.details = "Data retrieved from cache".to_string();
            }
             data
        },
        None => {
             info!("Cache MISS for {}", cache_key);
             if let Some(mut task) = state.tasks.get_mut(&task_id) {
                task.status = ObservabilityTaskStatus::InProgress;
                task.progress_percent = 20;
                task.details = "Fetching data from YFinance".to_string();
            }

             let (p, f) = state.yfinance_provider.fetch_all_data(command.symbol.as_str()).await?;
             
             // Write to Cache
             let payload = serde_json::json!((&p, &f));
             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 Data ---
    if let Some(mut task) = state.tasks.get_mut(&task_id) {
        task.progress_percent = 80;
        task.details = "Snapshotting data...".to_string();
    }

    // Global Profile (Optional but good for search)
    // let _ = client.upsert_company_profile(profile.clone()).await;

    // Snapshot Profile
    client.insert_session_data(&SessionDataDto {
        request_id: command.request_id,
        symbol: symbol.clone(),
        provider: "yfinance".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
    client.insert_session_data(&SessionDataDto {
        request_id: command.request_id,
        symbol: symbol.clone(),
        provider: "yfinance".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()))?;

    // Update Provider Status
    /*
    client.update_provider_status(command.symbol.as_str(), "yfinance", common_contracts::dtos::ProviderStatusDto {
        last_updated: chrono::Utc::now(),
        status: TaskStatus::Completed,
        data_version: None,
    }).await?;
    */

    // --- 3. Publish events ---
    let profile_event = CompanyProfilePersistedEvent {
        request_id: task_id,
        symbol: command.symbol.clone(),
    };
    publisher
        .publish(
            "events.data.company_profile_persisted".to_string(),
            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 data from Yahoo Finance", years.len());
    let financials_event = FinancialsPersistedEvent {
        request_id: task_id,
        symbol: command.symbol.clone(),
        years_updated: years.into_iter().collect(),
        template_id: command.template_id.clone(),
        provider_id: Some("yfinance".to_string()),
        data_summary: Some(summary),
    };
    publisher
        .publish(
            "events.data.financials_persisted".to_string(),
            serde_json::to_vec(&financials_event).unwrap().into(),
        )
        .await?;

    if let Some(mut task) = state.tasks.get_mut(&task_id) {
        task.status = ObservabilityTaskStatus::Completed;
        task.progress_percent = 100;
        task.details = "Workflow finished successfully".to_string();
    }
    info!("Task {} completed successfully.", task_id);
    Ok(())
}

#[cfg(test)]
mod integration_tests {
    use super::*;
    use crate::config::AppConfig;
    use crate::state::AppState;
    use common_contracts::symbol_utils::{CanonicalSymbol, Market};
    use common_contracts::config_models::{DataSourceConfig, DataSourceProvider};
    use std::collections::HashMap;
    use uuid::Uuid;

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

        let config = AppConfig::load().expect("Failed to load config");
        // Ensure yfinance is enabled in AppConfig for initial state
        // (Though worker checks persistence config dynamically)
        let mut config_enabled = config.clone();
        config_enabled.yfinance_enabled = true;
        let state = AppState::new(config_enabled);

        // 1. Enable YFinance in Persistence Service
        let persistence_client = PersistenceClient::new(config.data_persistence_service_url.clone());
        let mut current_config = persistence_client.get_data_sources_config().await.unwrap_or_default();
        current_config.insert("yfinance".to_string(), DataSourceConfig {
            provider: DataSourceProvider::Yfinance,
            enabled: true,
            api_key: None,
            api_url: None,
        });
        persistence_client.update_data_sources_config(&current_config).await
            .expect("Failed to enable YFinance in persistence");

        // 2. Construct Command (MSFT)
        let request_id = Uuid::new_v4();
        let cmd = FetchCompanyDataCommand {
            request_id,
            symbol: CanonicalSymbol::new("MSFT", &Market::US),
            market: "US".to_string(),
            template_id: Some("default".to_string()),
        };
        
        // 3. NATS
        let nats_client = async_nats::connect(&config.nats_addr).await
            .expect("Failed to connect to NATS");

        // 4. Run
        let result = handle_fetch_command_inner(state.clone(), &cmd, &nats_client).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);
    }
}
