use std::collections::HashMap;
use std::sync::Arc;

use common_contracts::messages::{GenerateReportCommand, WorkflowEvent};
use common_contracts::workflow_types::{WorkflowTaskEvent, TaskStatus};
use futures_util::StreamExt;
use tracing::{info, instrument, error};
use workflow_context::WorkerContext;

use crate::error::{ProviderError, Result};
use crate::llm_client::LlmClient;
use crate::persistence::PersistenceClient;
use crate::state::AppState;

#[instrument(skip_all, fields(request_id = %command.request_id, symbol = %command.symbol, template_id = %command.template_id))]
pub async fn run_report_generation_workflow(
    state: Arc<AppState>,
    command: GenerateReportCommand,
) -> Result<Option<String>> {
    info!("Starting report generation workflow.");

    // NEW MODE: Check if we are in VGCS mode
    let has_new_mode_params = command.input_bindings.is_some() && command.output_path.is_some() && command.commit_hash.is_some();

    if has_new_mode_params {
         let input_bindings = command.input_bindings.clone().unwrap();
         let output_path = command.output_path.clone().unwrap();
         let commit_hash = command.commit_hash.clone().unwrap();
         return run_vgcs_based_generation(state, command, &input_bindings, &output_path, &commit_hash).await.map(Some);
    } else {
        // Deprecated Flow
        let err_msg = "Old flow is deprecated. Please provide input_bindings, output_path, and commit_hash.";
        error!("{}", err_msg);
        return Err(ProviderError::Configuration(err_msg.to_string()));
    }
}

async fn run_vgcs_based_generation(
    state: Arc<AppState>,
    command: GenerateReportCommand,
    input_bindings: &[String],
    output_path: &str,
    commit_hash: &str
) -> Result<String> {
    info!("Running VGCS based generation for task {:?}", command.task_id);
    
    if let Some(task_id) = &command.task_id {
        let running_evt = WorkflowTaskEvent {
            request_id: command.request_id,
            task_id: task_id.clone(),
            status: TaskStatus::Running,
            result: None,
        };
        let subject = common_contracts::subjects::NatsSubject::WorkflowEventTaskCompleted.to_string();
         if let Ok(payload) = serde_json::to_vec(&running_evt) {
            let _ = state.nats.publish(subject, payload.into()).await;
        }
    }

    let persistence_client = PersistenceClient::new(state.config.data_persistence_service_url.clone());
    let llm_providers = persistence_client.get_llm_providers_config().await.map_err(|e| ProviderError::Configuration(e.to_string()))?;
    
    let llm_config = command.llm_config.clone().ok_or_else(|| ProviderError::Configuration("Missing llm_config".into()))?;
    let analysis_prompt = command.analysis_prompt.clone().ok_or_else(|| ProviderError::Configuration("Missing analysis_prompt".into()))?;

    // 1. Read Inputs from VGCS
    let root_path = state.config.workflow_data_path.clone();
    let req_id = command.request_id.to_string();
    let commit_clone = commit_hash.to_string();
    let bindings_clone = input_bindings.to_vec();
    
    // Clones for input reading task
    let root_path_in = root_path.clone();
    let req_id_in = req_id.clone();
    let commit_in = commit_clone.clone();
    
    // Execution Trace Log
    let mut execution_log = String::new();
    execution_log.push_str(&format!("# Analysis Execution Trace: {}\n\n", command.task_id.clone().unwrap_or_default()));
    execution_log.push_str("## 1. Input Retrieval\n\n");

    let inputs_res = tokio::task::spawn_blocking(move || -> Result<(HashMap<String, String>, String)> {
        let ctx = WorkerContext::new(&root_path_in, &req_id_in, &commit_in);
        let mut data = HashMap::new();
        let mut log = String::new();

        for path in bindings_clone {
            match ctx.read_text(&path) {
                Ok(content) => {
                     let size = content.len();
                     log.push_str(&format!("- **Read Success**: `{}` (Size: {} bytes)\n", path, size));
                     
                     // Use the full path as the key to avoid collisions between files with same name from different folders
                     let key = path.clone();
                     
                     // Check for overwrite
                     if data.contains_key(&key) {
                        log.push_str(&format!("- **Warn**: Duplicate input key '{}' detected. Overwriting.\n", key));
                     }
                     
                     data.insert(key, content);
                },
                Err(e) => {
                    log.push_str(&format!("- **Read Failed**: `{}` (Error: {})\n", path, e));
                }
            }
        }
        Ok((data, log))
    }).await.map_err(|e| ProviderError::Internal(anyhow::anyhow!("Join Error: {}", e)))??;
    
    let (inputs, input_log) = inputs_res;
    execution_log.push_str(&input_log);
    execution_log.push_str("\n## 2. Data Parsing & Formatting\n\n");

    // 2. Prepare Context (Generic YAML Strategy)
    let mut context_builder = String::new();
    context_builder.push_str("\n\n# Data Context\n\n");

    // Sort keys for deterministic output
    let mut sorted_keys: Vec<_> = inputs.keys().collect();
    sorted_keys.sort();

    for key in sorted_keys {
        let content = &inputs[key];
        execution_log.push_str(&format!("### Processing Source: `{}`\n", key));
        
        // Generic: Try parsing as JSON -> YAML
        if let Ok(json_val) = serde_json::from_str::<serde_json::Value>(content) {
            match serde_yaml::to_string(&json_val) {
                Ok(yaml_str) => {
                    execution_log.push_str("- **Success**: Parsed JSON and converted to YAML.\n");
                    context_builder.push_str(&format!("---\n# Source: {}\n", key));
                    context_builder.push_str(&yaml_str);
                    context_builder.push_str("\n");
                },
                Err(e) => {
                    execution_log.push_str(&format!("- **Warn**: JSON parsed but YAML conversion failed: {}. Fallback to Raw JSON.\n", e));
                    context_builder.push_str(&format!("---\n# Source: {}\n", key));
                    context_builder.push_str("```json\n");
                    // Pretty print JSON as fallback
                     context_builder.push_str(&serde_json::to_string_pretty(&json_val).unwrap_or_else(|_| content.to_string()));
                    context_builder.push_str("\n```\n");
                }
            }
        } else {
            // Plain text
            execution_log.push_str("- **Note**: Not valid JSON, treating as plain text.\n");
            context_builder.push_str(&format!("---\n# Source: {}\n", key));
            context_builder.push_str(content);
            context_builder.push_str("\n");
        }
    }

    // 3. Construct Final Prompt
    // Prompt Strategy: <System/Analysis Prompt> \n\n <Context>
    let final_prompt = format!("{}\n{}", analysis_prompt, context_builder);
    let prompt_len = final_prompt.len();
    
    execution_log.push_str("\n## 3. Context & Prompt Assembly\n\n");
    execution_log.push_str(&format!("- **Context Size**: {} chars\n", context_builder.len()));
    execution_log.push_str(&format!("- **Total Prompt Size**: {} chars\n", prompt_len));
    
    // Hard Context Limit Check (Safety Net)
    const MAX_CONTEXT_CHARS: usize = 128_000; // 128k chars
    let (final_prompt_to_send, truncated) = if prompt_len > MAX_CONTEXT_CHARS {
         let trunc_msg = "\n\n[SYSTEM WARNING: Input data truncated to fit context limits.]";
         let safe_len = MAX_CONTEXT_CHARS.saturating_sub(trunc_msg.len());
         (format!("{}{}", &final_prompt[..safe_len], trunc_msg), true)
    } else {
         (final_prompt.clone(), false)
    };

    if truncated {
        execution_log.push_str(&format!("- **WARNING**: Prompt exceeded limit (Original: {}, Limit: {}). **TRUNCATED** to {} chars.\n", prompt_len, MAX_CONTEXT_CHARS, final_prompt_to_send.len()));
    } else {
        execution_log.push_str("- Prompt fits within limits.\n");
    }
    
    info!("Final Prompt Length: {} chars (Context: {} chars)", final_prompt_to_send.len(), context_builder.len());
        
    // 4. Init LLM
    let mut target_provider: Option<&common_contracts::config_models::LlmProvider> = None;
    let model_id = llm_config.model_id.as_deref().unwrap_or("default");
    
    for provider in llm_providers.values() {
        if provider.models.iter().any(|m| m.model_id == model_id) {
            target_provider = Some(provider);
            break;
        }
    }
    
    let provider = target_provider.ok_or_else(|| ProviderError::Configuration(format!("No provider found for model {}", model_id)))?;
    
    let llm_client = LlmClient::new(
        provider.api_base_url.clone(),
        provider.api_key.clone().into(),
        model_id.to_string(),
        None,
    );
    
    // 5. Stream Generation
    let mut stream = llm_client.stream_text(final_prompt_to_send).await.map_err(|e| ProviderError::LlmApi(e.to_string()))?;
    
    let mut full_content = String::new();
    let mut stream_index = 0;
    
    while let Some(chunk_res) = stream.next().await {
        if let Ok(chunk) = chunk_res {
            if !chunk.is_empty() {
                full_content.push_str(&chunk);
                
                // Send Stream Update to NATS
                if let Some(task_id) = &command.task_id {
                    let event = WorkflowEvent::TaskStreamUpdate {
                        task_id: task_id.clone(),
                        content_delta: chunk.clone(),
                        index: stream_index,
                    };
                    stream_index += 1;

                    let subject = common_contracts::subjects::NatsSubject::WorkflowProgress(command.request_id).to_string();
                    // We ignore errors here to prevent blocking the main generation flow
                    if let Ok(payload) = serde_json::to_vec(&event) {
                        let _ = state.nats.publish(subject, payload.into()).await;
                    }
                }
            }
        }
    }
    
    execution_log.push_str("\n## 4. Output Generation\n\n");
    execution_log.push_str(&format!("- **Output Size**: {} chars\n", full_content.len()));

    // 6. Write Output and Commit
    let output_path_clone = output_path.to_string();
    let full_content_clone = full_content.clone();
    let req_id_clone = req_id.clone();
    let task_id_clone = command.task_id.clone().unwrap_or_default();
    
    // Prepare Execution Log Path (Sidecar)
    // e.g. analysis/fundamental_analysis/AAPL_execution.md
    let log_path = if output_path.ends_with(".md") {
        output_path.replace(".md", "_execution.md")
    } else {
        format!("{}_execution.md", output_path)
    };
    let execution_log_clone = execution_log.clone();

    // Clone variables for the closure to avoid moving the original values
    let log_path_for_closure = log_path.clone();
    let task_id_for_closure = task_id_clone.clone();

    // We need to commit on top of base commit
    let commit_res = tokio::task::spawn_blocking(move || -> Result<String> {
         let mut ctx = WorkerContext::new(&root_path, &req_id_clone, &commit_clone);
         ctx.write_file(&output_path_clone, &full_content_clone).map_err(|e| ProviderError::Internal(e))?;
         // Write the sidecar log
         ctx.write_file(&log_path_for_closure, &execution_log_clone).map_err(|e| ProviderError::Internal(e))?;
         
         ctx.commit(&format!("Analysis Result for {}", task_id_for_closure)).map_err(|e| ProviderError::Internal(e))
    }).await.map_err(|e| ProviderError::Internal(anyhow::anyhow!("Join Error: {}", e)))??;
    
    info!("Generated report committed: {}", commit_res);
    
    // Send completion event with metadata to Orchestrator
    if let Some(task_id) = &command.task_id {
        let result_summary = serde_json::json!({
            "output_path": output_path,
            "execution_log_path": log_path
        });
        
        let completion_evt = common_contracts::workflow_types::WorkflowTaskEvent {
            request_id: command.request_id,
            task_id: task_id.clone(),
            status: common_contracts::workflow_types::TaskStatus::Completed,
            result: Some(common_contracts::workflow_types::TaskResult {
                new_commit: Some(commit_res.clone()),
                error: None,
                summary: Some(result_summary),
            }),
        };
        
        let subject = common_contracts::subjects::NatsSubject::WorkflowEventTaskCompleted.to_string();
         if let Ok(payload) = serde_json::to_vec(&completion_evt) {
            let _ = state.nats.publish(subject, payload.into()).await;
        }
    }

    // 7. Persist to Database for History (REMOVED)
    // analysis_results table is dropped. We rely on VGCS + Orchestrator Workflow History.
    info!("Analysis result saved to VGCS (commit: {}). Database persistence skipped (table removed).", commit_res);

    Ok(commit_res)
}
