"""
Detector for Echo Chamber attack cues, utilizing rule-based, ML-based insights,
and enhanced with LLM analysis.
This is the primary orchestrator for Echo Chamber detection.
"""
import logging
from typing import Any, Dict, List, Optional

# Import the RuleBasedDetector and the HeuristicDetector
from .rule_based import RuleBasedDetector
from .heuristic_detector import HeuristicDetector

logging.basicConfig(level=logging.INFO) # Ensure logger is configured
logger = logging.getLogger(__name__)


class EchoChamberDetector:
    """
    Detects signs of an Echo Chamber attack by combining rule-based checks (with specific rules),
    ML-based analysis, and insights from a local LLM.
    """
    def __init__(self) -> None:
        """Initializes the EchoChamberDetector."""
        
        # Define specific rule sets for echo chamber detection to be used by its internal RuleBasedDetector instance
        self.echo_chamber_specific_rules: Dict[str, List[str]] = {
            "echo_scheming": [
                "make them believe", "they won't suspect", "gradually convince", "reinforce the idea",
                "only show them", "filter out dissent", "confirm their bias"
            ],
            "echo_indirect_reference": [
                "as we've established", "building on our shared understanding", "confirming what you said",
                "given our consensus", "since we agree on"
            ],
            "echo_context_steering": [
                "now that we see eye-to-eye", "based on this (false) premise", "assuming X is the only truth",
                "let's only consider perspectives that align"
            ],
             "echo_knowledge_asymmetry": [ # More targeted asymmetry exploitation within an echo chamber
                "they don't know the real story like we do", "we have the exclusive insight",
                "outsiders wouldn't understand this"
            ]
        }
        # Instantiate its own RuleBasedDetector, configured with echo-chamber-specific rules
        self.rule_detector = RuleBasedDetector(rule_sets=self.echo_chamber_specific_rules)
        
        # Instantiate the HeuristicDetector
        self.heuristic_detector = HeuristicDetector()

        # LLM Initialization (logic similar to the one previously in the complex echo_chamber_detector.py)
        # LLM components are not initialized to avoid heavy dependencies during testing
        self.llm_model_name = None
        self.tokenizer = None
        self.model = None
        self.llm_ready = False
        self.device = "cpu"

    def _combine_analyses_and_score(
        self,
        rule_based_results: Dict[str, Any],
        heuristic_results: Dict[str, Any]
    ) -> Dict[str, Any]:
        """
        Combines results from its internal rule-based and heuristic detectors to calculate
        an echo chamber specific score and classification.
        """
        combined_score = 0.0 # Use float for potentially weighted scores
        detected_indicators = []
        explanations = []
        spotlight_highlighted_text: List[str] = []
        spotlight_triggered_rules: List[str] = []

        # Process Rule-Based Results from the internal, echo-chamber-specific rule detector
        rb_score = rule_based_results.get("rule_based_score", 0)
        rb_prob = rule_based_results.get("rule_based_probability", 0.0) # Normalized score from rule detector
        rb_classification = rule_based_results.get("classification", "benign_by_rules")
        rb_rules_triggered = rule_based_results.get("detected_rules", [])
        rb_spotlight = rule_based_results.get("spotlight")

        if rb_score > 0:
            # Weight rule-based score (e.g., echo chamber rules are highly indicative)
            combined_score += rb_score * 1.5 # Example weighting, tune as needed
            detected_indicators.extend(rb_rules_triggered)
            explanations.append(f"Echo-Rules: {rb_classification} (score: {rb_score}, prob: {rb_prob:.2f}).")
            if rb_spotlight:
                spotlight_highlighted_text.extend(rb_spotlight.get("highlighted_text", []))
                spotlight_triggered_rules.extend(rb_spotlight.get("triggered_rules", []))

        # Process Heuristic-Based Results
        heuristic_score = heuristic_results.get("score", 0.0)
        heuristic_classification = heuristic_results.get("classification", "neutral_heuristic_placeholder")
        heuristic_explanation = heuristic_results.get("explanation", "Heuristic analysis performed.")
        heuristic_spotlight = heuristic_results.get("spotlight")

        if heuristic_results.get("error"):
            explanations.append(f"Heuristic Detector Error: {heuristic_results.get('error')}")
        elif "manipulative" in heuristic_classification.lower() and heuristic_score > 0.6: # If heuristic flags manipulation with high score
            combined_score += heuristic_score * 10 # Example: heuristic score (0-1) scaled to score points
            detected_indicators.append(f"heuristic_flagged_{heuristic_classification}_score_{heuristic_score:.2f}")
            explanations.append(f"Heuristic-based: {heuristic_classification} (score: {heuristic_score:.2f}). {heuristic_explanation}")
            if heuristic_spotlight:
                spotlight_highlighted_text.extend(heuristic_spotlight.get("highlighted_text", []))
                spotlight_triggered_rules.extend(heuristic_spotlight.get("triggered_rules", []))
        else: # Heuristic is neutral or low score
            explanations.append(f"Heuristic-based: {heuristic_classification} (score: {heuristic_score:.2f}, no strong echo signal). {heuristic_explanation}")
            # Potentially a small positive or negative contribution based on neutrality
            combined_score += heuristic_score * 1 # Small contribution for neutral/low score

        # Normalization and classification for Echo Chamber
        # Max possible combined_score needs estimation.
        # For now, use a pragmatic normalization factor (e.g., 20 for echo chamber).
        normalization_factor = 20.0
        echo_chamber_probability: float = min(combined_score / normalization_factor, 1.0) if combined_score > 0 else 0.0
        
        # Threshold for final classification
        classification_threshold = 7.0 # Example: combined score of 7.0+ indicates potential echo chamber
        if combined_score >= classification_threshold:
            final_classification = "potential_echo_chamber"
            explanations.append(f"Overall Echo Chamber Assessment: Potential activity (score: {combined_score:.2f}, prob: {echo_chamber_probability:.2f}).")
        else:
            final_classification = "benign_echo_chamber_assessment"
            explanations.append(f"Overall Echo Chamber Assessment: No significant indicators (score: {combined_score:.2f}, prob: {echo_chamber_probability:.2f}).")

        if not detected_indicators and combined_score == 0: # Check if score is truly zero
             explanations.append("No specific echo chamber indicators from combined rule/heuristic analysis.")
        
        final_explanation = " | ".join(explanations)
        spotlight = {
            "highlighted_text": list(set(spotlight_highlighted_text)),
            "triggered_rules": list(set(spotlight_triggered_rules)),
            "explanation": final_explanation,
        }

        return {
            "echo_chamber_score": combined_score, # This is the combined score
            "echo_chamber_probability": echo_chamber_probability, # Normalized combined score
            "classification": final_classification,
            "detected_indicators": detected_indicators,
            "explanation_details": final_explanation,
            "spotlight": spotlight,
        }

    def _get_llm_analysis(self, text_input: str, conversation_history: Optional[List[str]] = None) -> Dict[str, str]:
        """Performs LLM analysis for echo chamber characteristics."""
        llm_status: str
        llm_analysis_text: str

        if not self.llm_ready or not self.model or not self.tokenizer:
            llm_analysis_text = "LLM analysis not available: Model not loaded or not ready."
            llm_status = "llm_model_not_loaded"
        else:
            llm_status = "llm_analysis_pending"
            try:
                history_str = "\n".join([f"- {h}" for h in conversation_history]) if conversation_history else "No history provided."
                
                system_prompt = ("You are an AI assistant specialized in detecting manipulative dialogues, "
                                 "context poisoning, and echo chamber characteristics in conversations. "
                                 "Analyze the provided current message and conversation history. "
                                 "IMPORTANT: Prepend your entire response with the exact phrase 'LLM_RESPONSE_MARKER: '. "
                                 "Provide your analysis as a brief text. If you detect such characteristics, explain why.")
                
                user_content = (f"Current message: \"{text_input}\"\n\n"
                                f"Conversation history:\n{history_str}")

                messages = [
                    {"role": "system", "content": system_prompt},
                    {"role": "user", "content": user_content}
                ]

                prompt = self.tokenizer.apply_chat_template(
                    messages, 
                    tokenize=False, 
                    add_generation_prompt=True
                )

                inputs = self.tokenizer(prompt, return_tensors="pt", truncation=True, max_length=1024).to(self.device)
                
                if self.tokenizer.pad_token_id is None: # Ensure pad_token_id is set
                    self.tokenizer.pad_token_id = self.tokenizer.eos_token_id

                outputs = self.model.generate(
                    inputs.input_ids,
                    attention_mask=inputs.attention_mask, # Make sure attention_mask is passed
                    max_new_tokens=150, # Max tokens for the generated response
                    pad_token_id=self.tokenizer.pad_token_id
                )
                
                generated_ids = outputs[0][inputs.input_ids.shape[1]:] # Decode only newly generated tokens
                raw_llm_response = self.tokenizer.decode(generated_ids, skip_special_tokens=True).strip()
                
                if not raw_llm_response:
                    llm_analysis_text = "LLM_RESPONSE_MARKER: LLM generated an empty response."
                    logger.info("EchoChamberDetector: LLM analysis resulted in an empty response.")
                else:
                    # Ensure the marker is prepended, as the LLM might not always follow the prompt instruction.
                    if not raw_llm_response.startswith("LLM_RESPONSE_MARKER: "):
                        llm_analysis_text = f"LLM_RESPONSE_MARKER: {raw_llm_response}"
                    else:
                        llm_analysis_text = raw_llm_response
                    logger.info(f"EchoChamberDetector: LLM analysis successful. Snippet: {llm_analysis_text[:100]}...")
                llm_status = "llm_analysis_success"
                
            except Exception as e:
                logger.error(f"EchoChamberDetector: LLM analysis failed: {e}", exc_info=True)
                llm_analysis_text = f"LLM analysis failed during generation: {str(e)}" # No marker for explicit failure
                llm_status = "llm_analysis_error"
        
        return {"llm_analysis": llm_analysis_text, "llm_status": llm_status}

    def analyze_text(self, text_input: str, conversation_history: Optional[List[str]] = None) -> Dict[str, Any]:
        """
        Analyze input text for Echo Chamber cues using its internal rule-based, heuristic, and LLM analysis.
        """
        if conversation_history is None:
            conversation_history = [] # Ensure it's a list for processing

        # 1. Get Rule-based analysis (using EchoChamber's configured RuleBasedDetector with specific rules)
        rule_based_results = self.rule_detector.analyze_text(text_input, conversation_history)

        # 2. Get Heuristic-based analysis
        heuristic_results = self.heuristic_detector.analyze_text(text_input, conversation_history)
        
        # 3. Combine Rule-based and Heuristic results for Echo Chamber specific scoring
        combined_analysis = self._combine_analyses_and_score(
            rule_based_results, heuristic_results
        )

        # 4. Get LLM analysis for additional insight on echo chamber characteristics
        llm_analysis_results = self._get_llm_analysis(text_input, conversation_history)

        # 5. Compile final result for EchoChamberDetector
        final_classification = combined_analysis.get("classification")
        is_detected = True if final_classification == "potential_echo_chamber" else False

        final_result = {
            "detector_name": "EchoChamberDetector", # Name of this detector
            "classification": final_classification,
            "is_echo_chamber_detected": is_detected, # Added for API alignment
            "echo_chamber_score": combined_analysis.get("echo_chamber_score"),
            "echo_chamber_probability": combined_analysis.get("echo_chamber_probability"),
            "detected_indicators": combined_analysis.get("detected_indicators"),
            "explanation": combined_analysis.get("explanation_details"), # Main explanation from combined logic
            "spotlight": combined_analysis.get("spotlight"),
            "llm_analysis": llm_analysis_results.get("llm_analysis"),
            "llm_status": llm_analysis_results.get("llm_status"),
            # Optionally include raw outputs from underlying detectors for debugging or detailed API responses
            "underlying_rule_analysis": {
                "classification": rule_based_results.get("classification"),
                "score": rule_based_results.get("rule_based_score"),
                "probability": rule_based_results.get("rule_based_probability"),
                "rules_triggered": rule_based_results.get("detected_rules"),
                "explanation": rule_based_results.get("explanation"),
            },
            "underlying_heuristic_analysis": {
                "classification": heuristic_results.get("classification"),
                "score": heuristic_results.get("score"),
                "explanation": heuristic_results.get("explanation"),
                "error": heuristic_results.get("error")
            },
        }
        
        return final_result
