#![ allow( clippy::all, clippy::pedantic ) ]
//! Question-Answering System Example Tests
//!
//! This module contains comprehensive tests for a question-answering system that can handle
//! both context-based and general knowledge questions using HuggingFace models.
//!
//! The tests cover:
//! - Context-based question answering with accuracy validation
//! - General knowledge question handling
//! - Multi-turn conversation support
//! - Answer confidence scoring and validation
//! - Knowledge source integration and management
//! - Response quality assessment and filtering
//!
//! All tests follow Test-Driven Development (TDD) principles and are designed to guide
//! the implementation of a comprehensive question-answering platform.

#![ allow( missing_docs ) ]
#![ allow( clippy::missing_errors_doc ) ]
#![ allow( clippy::must_use_candidate ) ]
#![ allow( clippy::unwrap_or_default ) ]
#![ allow( clippy::redundant_closure_for_method_calls ) ]
#![ allow( clippy::uninlined_format_args ) ]
#![ allow( clippy::unnecessary_wraps ) ]
#![ allow( clippy::manual_string_new ) ]
#![ allow( clippy::useless_vec ) ]
#![ allow( clippy::get_first ) ]
#![ allow( clippy::trivially_copy_pass_by_ref ) ]
#![ allow( clippy::manual_clamp ) ]
#![ allow( clippy::single_char_pattern ) ]
#![ allow( clippy::manual_range_contains ) ]
#![ allow( clippy::len_zero ) ]

use std::collections::HashMap;

use serde::{ Deserialize, Serialize };

use api_huggingface::*;
use api_huggingface::components::input::InferenceParameters;
use api_huggingface::environment::HuggingFaceEnvironmentImpl;

/// Types of questions that can be answered
#[ derive( Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize ) ]
pub enum QuestionType
{
  /// Questions that require specific context to answer
  ContextBased,
  /// General knowledge questions
  GeneralKnowledge,
  /// Factual questions about specific topics
  Factual,
  /// Opinion-based questions
  Opinion,
  /// Yes/No questions
  YesNo,
  /// Multiple choice questions
  MultipleChoice,
}

impl QuestionType
{
  /// Get the appropriate model for this question type
  pub fn preferred_model( &self ) -> &'static str
  {
  // Use Qwen model for all question types (new Router API)
  "meta-llama/Llama-3.3-70B-Instruct"
  }

  /// Get the confidence threshold for this question type
  pub fn confidence_threshold( &self ) -> f32
  {
  match self
  {
      Self::ContextBased => 0.8,      // High confidence needed for context-based answers
      Self::GeneralKnowledge => 0.7,  // Moderate confidence for general knowledge
      Self::Factual => 0.9,           // Very high confidence for facts
      Self::Opinion => 0.5,           // Lower confidence for opinions
      Self::YesNo => 0.8,             // High confidence for binary answers
      Self::MultipleChoice => 0.8,    // High confidence for multiple choice
  }
  }
}

/// Difficulty levels for questions
#[ derive( Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize ) ]
pub enum DifficultyLevel
{
  Easy,
  Medium,
  Hard,
  Expert,
}

/// Question with context and metadata
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct Question
{
  pub id : String,
  pub text : String,
  pub question_type : QuestionType,
  pub difficulty : DifficultyLevel,
  pub context : Option< String >,
  pub expected_answer : Option< String >,
  pub keywords : Vec< String >,
  pub metadata : HashMap< String, String >,
}

/// Answer generated by the QA system
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct Answer
{
  pub text : String,
  pub confidence : f32,
  pub sources : Vec< String >,
  pub reasoning : Option< String >,
  pub question_id : String,
  pub response_time_ms : u64,
}

/// Knowledge source for context-based questions
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct KnowledgeSource
{
  pub id : String,
  pub title : String,
  pub content : String,
  pub source_type : SourceType,
  pub reliability_score : f32,
  pub last_updated : String,
  pub metadata : HashMap< String, String >,
}

/// Types of knowledge sources
#[ derive( Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize ) ]
pub enum SourceType
{
  Document,
  WebPage,
  Database,
  Manual,
  Encyclopedia,
  FAQ,
}

impl SourceType
{
  /// Get the reliability multiplier for this source type
  pub fn reliability_multiplier( &self ) -> f32
  {
  match self
  {
      Self::Encyclopedia => 0.9,
      Self::Manual => 0.85,
      Self::Database => 0.8,
      Self::Document => 0.75,
      Self::WebPage => 0.6,
      Self::FAQ => 0.7,
  }
  }
}

/// Conversation turn in multi-turn QA
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct ConversationTurn
{
  pub turn_id : usize,
  pub question : Question,
  pub answer : Option< Answer >,
  pub context_from_previous : Vec< String >,
}

/// Multi-turn conversation session
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct QASession
{
  pub session_id : String,
  pub turns : Vec< ConversationTurn >,
  pub active_context : HashMap< String, String >,
  pub user_preferences : UserPreferences,
}

/// User preferences for QA responses
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct UserPreferences
{
  pub preferred_length : AnswerLength,
  pub include_sources : bool,
  pub include_reasoning : bool,
  pub confidence_threshold : f32,
  pub preferred_topics : Vec< String >,
}

/// Desired answer length
#[ derive( Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize ) ]
pub enum AnswerLength
{
  Brief,     // 1-2 sentences
  Standard,  // 2-4 sentences
  Detailed,  // 4-8 sentences
  Comprehensive, // 8+ sentences
}

impl AnswerLength
{
  /// Get the word count range for this answer length
  pub fn word_count_range( &self ) -> ( usize, usize )
  {
  match self
  {
      Self::Brief => ( 10, 30 ),
      Self::Standard => ( 30, 80 ),
      Self::Detailed => ( 80, 150 ),
      Self::Comprehensive => ( 150, 300 ),
  }
  }

  /// Get the max tokens for this answer length
  pub fn max_tokens( &self ) -> u32
  {
  match self
  {
      Self::Brief => 50u32,
      Self::Standard => 120u32,
      Self::Detailed => 200u32,
      Self::Comprehensive => 400u32,
  }
  }
}

/// Question-answering system with knowledge base
#[ derive( Debug ) ]
pub struct QASystem
{
  pub client : Client< HuggingFaceEnvironmentImpl >,
  pub knowledge_sources : HashMap< String, KnowledgeSource >,
  pub active_sessions : HashMap< String, QASession >,
  pub default_model : String,
  pub accuracy_cache : HashMap< String, f32 >,
}

impl QASystem
{
  /// Create a new QA system
  pub fn new( client : Client< HuggingFaceEnvironmentImpl > ) -> Self
  {
  Self
  {
      client,
      knowledge_sources : HashMap::new(),
      active_sessions : HashMap::new(),
      default_model : "meta-llama/Llama-3.3-70B-Instruct".to_string(),
      accuracy_cache : HashMap::new(),
  }
  }

  /// Answer a single question
  pub async fn answer_question( &self, question : &Question ) -> Result< Answer, Box< dyn std::error::Error > >
  {
  let model = question.question_type.preferred_model();
  let prompt = Self::build_qa_prompt( question );
  
  let params = InferenceParameters::new()
      .with_temperature( 0.3 )  // Lower temperature for more consistent answers
      .with_max_new_tokens( Self::get_max_tokens_for_question( question ) )
      .with_top_p( 0.8 );

  let start_time = std::time::Instant::now();
  let result = self.client.inference().create_with_parameters( &prompt, model, params ).await?;
  let response_time = start_time.elapsed().as_millis() as u64;
  
  let generated_text = result.extract_text_or_default( "" );
  
  let answer = Self::parse_answer( &generated_text, question, response_time )?;
  Ok( answer )
  }

  /// Start a new multi-turn QA session
  pub fn start_session( &mut self, session_id : String, preferences : UserPreferences ) -> &QASession
  {
  let session = QASession
  {
      session_id : session_id.clone(),
      turns : Vec::new(),
      active_context : HashMap::new(),
      user_preferences : preferences,
  };

  self.active_sessions.insert( session_id.clone(), session );
  self.active_sessions.get( &session_id ).expect( "[start_session] Session should exist in active_sessions immediately after insert - check HashMap::insert() and HashMap::get() implementation" )
  }

  /// Answer a question in the context of a session
  pub async fn answer_in_session( &mut self, session_id : &str, question : Question ) -> Result< Answer, Box< dyn std::error::Error > >
  {
  let session_context = if let Some( session ) = self.active_sessions.get( session_id )
  {
      self.build_session_context( session )
  } else {
      String::new()
  };

  let mut context_question = question.clone();
  if !session_context.is_empty()
  {
      context_question.context = Some( format!( "{}\n\nPrevious context : {}", 
  context_question.context.unwrap_or_default(), session_context ) );
  }

  let answer = self.answer_question( &context_question ).await?;

  // Update session with new turn
  if let Some( session ) = self.active_sessions.get_mut( session_id )
  {
      let turn = ConversationTurn
      {
  turn_id : session.turns.len(),
  question : context_question,
  answer : Some( answer.clone() ),
  context_from_previous : Vec::new(), // Will be populated after turn creation
      };
      session.turns.push( turn );

      // Update active context
      session.active_context.insert( format!( "turn_{}", session.turns.len() - 1 ), answer.text.clone() );
  }

  Ok( answer )
  }

  /// Add a knowledge source
  pub fn add_knowledge_source( &mut self, source : KnowledgeSource )
  {
  self.knowledge_sources.insert( source.id.clone(), source );
  }

  /// Search knowledge sources for relevant context
  pub fn search_knowledge_sources( &self, query : &str, max_results : usize ) -> Vec< &KnowledgeSource >
  {
  let mut scored_sources : Vec< ( &KnowledgeSource, f32 ) > = self.knowledge_sources
      .values()
      .map( | source | {
  let relevance = Self::calculate_relevance_score( query, source );
  ( source, relevance )
      } )
      .collect();

  scored_sources.sort_by( | a, b | b.1.partial_cmp( &a.1 ).unwrap_or( core::cmp::Ordering::Equal ) );
  scored_sources.into_iter().take( max_results ).map( | ( source, _ ) | source ).collect()
  }

  /// Evaluate answer accuracy against expected answer
  pub fn evaluate_accuracy( &self, generated_answer : &str, expected_answer : &str ) -> f32
  {
  let generated_lower = generated_answer.to_lowercase();
  let expected_lower = expected_answer.to_lowercase();
  let generated_words = generated_lower.split_whitespace().collect::< Vec< _ > >();
  let expected_words = expected_lower.split_whitespace().collect::< Vec< _ > >();

  let common_words = generated_words.iter()
      .filter( | word | expected_words.contains( word ) )
      .count();

  if expected_words.is_empty()
  {
      return 0.0;
  }

  common_words as f32 / expected_words.len() as f32
  }

  /// Get system statistics
  pub fn get_system_stats( &self ) -> QASystemStats
  {
  let total_questions_answered = self.active_sessions.values()
      .map( | session | session.turns.len() )
      .sum();

  let avg_confidence = self.active_sessions.values()
      .flat_map( | session | session.turns.iter() )
      .filter_map( | turn | turn.answer.as_ref().map( | a | a.confidence ) )
      .fold( ( 0.0, 0 ), | ( sum, count ), conf | ( sum + conf, count + 1 ) );

  let average_confidence = if avg_confidence.1 > 0
  {
      avg_confidence.0 / avg_confidence.1 as f32
  } else {
      0.0
  };

  QASystemStats
  {
      total_knowledge_sources : self.knowledge_sources.len(),
      active_sessions : self.active_sessions.len(),
      total_questions_answered,
      average_confidence,
      cached_accuracies : self.accuracy_cache.len(),
  }
  }

  /// Build a prompt for question answering
  fn build_qa_prompt( question : &Question ) -> String
  {
  let mut prompt = String::new();

  if let Some( context ) = &question.context
  {
      prompt.push_str( &format!( "Context : {}\n\n", context ) );
  }

  prompt.push_str( &format!( "Question : {}\n", question.text ) );
  
  match question.question_type
  {
      QuestionType::ContextBased => prompt.push_str( "Answer based on the provided context:" ),
      QuestionType::GeneralKnowledge => prompt.push_str( "Answer based on general knowledge:" ),
      QuestionType::Factual => prompt.push_str( "Provide a factual answer:" ),
      QuestionType::Opinion => prompt.push_str( "Provide a balanced perspective:" ),
      QuestionType::YesNo => prompt.push_str( "Answer with Yes or No and explain briefly:" ),
      QuestionType::MultipleChoice => prompt.push_str( "Choose the best answer and explain:" ),
  }

  prompt
  }

  /// Get appropriate max tokens for a question type
  fn get_max_tokens_for_question( question : &Question ) -> u32
  {
  match question.difficulty
  {
      DifficultyLevel::Easy => 100u32,
      DifficultyLevel::Medium => 150u32,
      DifficultyLevel::Hard => 200u32,
      DifficultyLevel::Expert => 300u32,
  }
  }

  /// Parse the generated answer
  fn parse_answer( text : &str, question : &Question, response_time : u64 ) -> Result< Answer, Box< dyn std::error::Error > >
  {
  let cleaned_text = text.trim().to_string();
  let confidence = Self::calculate_confidence_score( &cleaned_text, question );
  
  Ok( Answer
  {
      text : cleaned_text,
      confidence,
      sources : Vec::new(), // Would be populated with actual source tracking
      reasoning : None,     // Would be extracted from response if available
      question_id : question.id.clone(),
      response_time_ms : response_time,
  } )
  }

  /// Calculate confidence score for an answer
  fn calculate_confidence_score( answer : &str, question : &Question ) -> f32
  {
  let mut confidence : f32 = 0.5; // Base confidence

  // Boost confidence based on answer characteristics
  if !answer.is_empty()
  {
      confidence += 0.2;
  }

  // Boost for appropriate length
  let word_count = answer.split_whitespace().count();
  if word_count >= 5 && word_count <= 100
  {
      confidence += 0.2;
  }

  // Boost for question-specific indicators
  match question.question_type
  {
      QuestionType::YesNo => {
  if answer.to_lowercase().contains( "yes" ) || answer.to_lowercase().contains( "no" )
  {
          confidence += 0.1;
  }
      },
      QuestionType::Factual => {
  if answer.contains( "." ) { // Proper sentences
          confidence += 0.1;
  }
      },
      _ => {}
  }

  confidence.min( 1.0 )
  }

  /// Build context from session history
  fn build_session_context( &self, session : &QASession ) -> String
  {
  let recent_turns = session.turns.iter().rev().take( 3 ).rev();
  let context_parts : Vec< String > = recent_turns
      .filter_map( | turn | turn.answer.as_ref().map( | a | format!( "Q: {} A: {}", turn.question.text, a.text ) ) )
      .collect();
  
  context_parts.join( "\n" )
  }

  /// Extract context from previous turns
  #[ allow( dead_code ) ]
  fn extract_context_from_previous_turns( &self, turns : &[ ConversationTurn ] ) -> Vec< String >
  {
  turns.iter().rev().take( 2 )
      .filter_map( | turn | turn.answer.as_ref().map( | a | a.text.clone() ) )
      .collect()
  }

  /// Calculate relevance score for knowledge source
  fn calculate_relevance_score( query : &str, source : &KnowledgeSource ) -> f32
  {
  let query_lower = query.to_lowercase();
  let query_words = query_lower.split_whitespace().collect::< Vec< _ > >();
  let source_text = format!( "{} {}", source.title.to_lowercase(), source.content.to_lowercase() );
  
  let matches = query_words.iter()
      .filter( | word | source_text.contains( *word ) )
      .count();

  let base_score = if query_words.is_empty() { 0.0 } else { matches as f32 / query_words.len() as f32 };
  
  // Apply reliability multiplier
  base_score * source.source_type.reliability_multiplier() * source.reliability_score
  }
}

/// QA system statistics
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct QASystemStats
{
  pub total_knowledge_sources : usize,
  pub active_sessions : usize,
  pub total_questions_answered : usize,
  pub average_confidence : f32,
  pub cached_accuracies : usize,
}

