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

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!("Finnhub 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("finnhub".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!("finnhub:{}", 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<()> {
    info!("Handling Finnhub fetch data command.");

    state.tasks.insert(
        command.request_id,
        TaskProgress {
            request_id: command.request_id,
            task_name: format!("finnhub:{}", command.symbol),
            status: ObservabilityTaskStatus::InProgress,
            progress_percent: 10,
            details: "Fetching data from Finnhub".to_string(),
            started_at: chrono::Utc::now(),
        },
    );

    let provider = match state.get_provider().await {
        Some(p) => p,
        None => {
            let reason = "Execution failed: Finnhub provider is not available (misconfigured).".to_string();
            // Return error to trigger outer handler
            return Err(AppError::ProviderNotAvailable(reason));
        }
    };

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

    // --- 1. Check Cache ---
    if let Some(mut task) = state.tasks.get_mut(&command.request_id) {
        task.details = "Checking cache...".to_string();
    }
    let cache_key = format!("finnhub:{}: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);
             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(&command.request_id) {
                task.details = "Data retrieved from cache".to_string();
                task.progress_percent = 50;
             }
             data
        },
        None => {
             info!("Cache MISS for {}", cache_key);
             if let Some(mut task) = state.tasks.get_mut(&command.request_id) {
                task.details = "Fetching from Finnhub API...".to_string();
                task.progress_percent = 20;
             }

             let (p, f) = provider.fetch_all_data(command.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 Data ---
    if let Some(mut task) = state.tasks.get_mut(&command.request_id) {
        task.details = "Snapshotting data...".to_string();
        task.progress_percent = 80;
    }

    // Global Profile
    // 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: command.request_id,
        symbol: symbol.clone(),
        provider: "finnhub".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: command.request_id,
        symbol: symbol.clone(),
        provider: "finnhub".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
    // REMOVED: update_provider_status is deprecated or missing in client.
    /*
    persistence_client.update_provider_status(command.symbol.as_str(), "finnhub", 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: command.request_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_set: std::collections::BTreeSet<u16> =
        financials.iter().map(|f| f.period_date.year() as u16).collect();
    
    let summary = format!("Fetched {} years of data from Finnhub", years_set.len());

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

    // 4. Finalize
    if let Some(mut task) = state.tasks.get_mut(&command.request_id) {
        task.status = ObservabilityTaskStatus::Completed;
        task.progress_percent = 100;
        task.details = "Workflow finished successfully".to_string();
    }
    info!("Task {} completed successfully.", command.request_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 uuid::Uuid;

    #[tokio::test]
    async fn test_finnhub_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("FINNHUB_API_KEY")
            .unwrap_or_else(|_| "d3fjs5pr01qolkndil0gd3fjs5pr01qolkndil10".to_string());
        
        let api_url = std::env::var("FINNHUB_API_URL")
            .unwrap_or_else(|_| "https://finnhub.io/api/v1".to_string());

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

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

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

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

        // 5. Run
        let result = handle_fetch_command_inner(state.clone(), &cmd, &nats_client).await;

        // 6. 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);
    }
}
