"""Truth registry for managing factoid verification and consensus"""

import json
import pandas as pd
from typing import List, Dict, Any, Optional
from datetime import datetime
from pathlib import Path

from .factoid import Factoid
from .config import Config
from .llm import LLMClient


class TruthRegistry:
    """
    Oxen-backed truth registry for managing verified factoids.
    
    Stores factoids with truth scores, verification history, and consensus data.
    """
    
    def __init__(self, config: Config, repo_name: str = "truth-registry"):
        self.config = config
        self.repo_name = repo_name
        self.namespace = config.oxen_namespace
        self.factoids: List[Factoid] = []
        self.verification_log: List[Dict[str, Any]] = []
        
    def add_factoid(self, factoid: Factoid) -> None:
        """Add a factoid to the registry"""
        self.factoids.append(factoid)
    
    def add_factoids(self, factoids: List[Factoid]) -> None:
        """Add multiple factoids to the registry"""
        self.factoids.extend(factoids)
    
    def verify_factoid(
        self,
        factoid: Factoid,
        truth_sources: List[str],
        llm_client: LLMClient,
        use_web_search: bool = False
    ) -> float:
        """
        Verify a factoid against truth sources and return truth score.
        
        Args:
            factoid: The factoid to verify
            truth_sources: List of authoritative source URLs
            llm_client: LLM client for verification
            use_web_search: Whether to use web search (Perplexity)
        
        Returns:
            Truth score (0.0 = likely false, 1.0 = likely true)
        """
        verification_result = {
            "factoid_id": factoid.factoid_id,
            "claim": factoid.claim_text,
            "verified_at": datetime.now(),
            "method": "web_search" if use_web_search else "llm",
            "truth_score": 0.5
        }
        
        try:
            if use_web_search:
                # Use Perplexity for web-search based verification
                truth_score = self._verify_with_web_search(factoid, llm_client)
            else:
                # Use standard LLM comparison
                truth_score = self._verify_with_llm(factoid, truth_sources, llm_client)
            
            factoid.truth_score = truth_score
            factoid.last_verified = datetime.now()
            factoid.verification_method = "web_search" if use_web_search else "llm"
            
            verification_result["truth_score"] = truth_score
            verification_result["success"] = True
            
        except Exception as e:
            print(f"⚠️  Verification failed for factoid {factoid.factoid_id}: {e}")
            verification_result["success"] = False
            verification_result["error"] = str(e)
        
        self.verification_log.append(verification_result)
        return factoid.truth_score
    
    def _verify_with_web_search(self, factoid: Factoid, llm_client: LLMClient) -> float:
        """Verify factoid using web search (Perplexity)"""
        
        prompt = f"""
Is the following technical claim accurate based on current web information?

CLAIM: {factoid.claim_text}
SOURCE: {factoid.source_url}
DOMAIN: {factoid.domain}
CATEGORY: {factoid.category}

Search the web for current, authoritative information to verify this claim.
Consider:
- Official documentation
- Recent announcements
- GitHub repositories
- Technical forums

Respond with a JSON object:
{{
    "is_accurate": true/false,
    "confidence": 0.0-1.0,
    "evidence": "brief explanation with sources",
    "supporting_urls": ["url1", "url2"],
    "conflicting_urls": ["url1", "url2"]
}}
"""
        
        try:
            response = llm_client.client.chat.completions.create(
                model="perplexity/sonar-pro",  # Web-search enabled model
                messages=[
                    {
                        "role": "system",
                        "content": "You are a technical fact-checker with web search capabilities. Verify technical claims using current, authoritative sources."
                    },
                    {
                        "role": "user",
                        "content": prompt
                    }
                ],
                temperature=0.1,
                max_tokens=1500,
                response_format={"type": "json_object"}
            )
            
            result = response.choices[0].message.content
            if result:
                data = json.loads(result)
                
                # Update factoid with verification data
                factoid.truth_sources = data.get("supporting_urls", [])
                factoid.conflict_sources = data.get("conflicting_urls", [])
                
                # Calculate truth score
                if data.get("is_accurate"):
                    return data.get("confidence", 0.8)
                else:
                    return 1.0 - data.get("confidence", 0.8)
        
        except Exception as e:
            print(f"⚠️  Web search verification failed: {e}")
            return 0.5
        
        return 0.5
    
    def _verify_with_llm(
        self,
        factoid: Factoid,
        truth_sources: List[str],
        llm_client: LLMClient
    ) -> float:
        """Verify factoid against known truth sources using LLM"""
        
        # For now, return moderate confidence
        # This would compare against extracted content from truth sources
        return 0.7
    
    def calculate_consensus(self, claim_text: str) -> Dict[str, Any]:
        """
        Calculate consensus score for a claim across multiple factoids.
        
        Args:
            claim_text: The claim to find consensus for
        
        Returns:
            Consensus data including score and supporting factoids
        """
        # Find all factoids with similar claims
        similar_factoids = []
        for factoid in self.factoids:
            if self._claims_are_similar(claim_text, factoid.claim_text):
                similar_factoids.append(factoid)
        
        if not similar_factoids:
            return {
                "claim": claim_text,
                "consensus_score": 0.0,
                "supporting_count": 0,
                "conflicting_count": 0,
                "factoids": []
            }
        
        # Calculate weighted consensus
        total_weight = sum(f.trust_weight for f in similar_factoids)
        weighted_truth = sum(f.truth_score * f.trust_weight for f in similar_factoids)
        
        consensus_score = weighted_truth / total_weight if total_weight > 0 else 0.5
        
        # Count supporting vs conflicting
        supporting = [f for f in similar_factoids if f.truth_score >= 0.7]
        conflicting = [f for f in similar_factoids if f.truth_score < 0.3]
        
        return {
            "claim": claim_text,
            "consensus_score": consensus_score,
            "supporting_count": len(supporting),
            "conflicting_count": len(conflicting),
            "total_factoids": len(similar_factoids),
            "factoids": [f.factoid_id for f in similar_factoids]
        }
    
    def _claims_are_similar(self, claim1: str, claim2: str, threshold: float = 0.7) -> bool:
        """Check if two claims are semantically similar"""
        # Simple implementation - can be enhanced with embeddings
        import re
        
        # Normalize
        norm1 = re.sub(r'[^\w\s]', '', claim1.lower())
        norm2 = re.sub(r'[^\w\s]', '', claim2.lower())
        
        words1 = set(norm1.split())
        words2 = set(norm2.split())
        
        if not words1 or not words2:
            return False
        
        overlap = len(words1 & words2) / max(len(words1), len(words2))
        return overlap >= threshold
    
    def get_accuracy_by_url(self, url: str) -> Dict[str, Any]:
        """Calculate accuracy score for a specific URL"""
        url_factoids = [f for f in self.factoids if f.source_url == url]
        
        if not url_factoids:
            return {
                "url": url,
                "accuracy_score": 0.0,
                "total_factoids": 0,
                "true_count": 0,
                "false_count": 0,
                "uncertain_count": 0
            }
        
        true_count = len([f for f in url_factoids if f.truth_score >= 0.7])
        false_count = len([f for f in url_factoids if f.truth_score < 0.3])
        uncertain_count = len(url_factoids) - true_count - false_count
        
        accuracy_score = true_count / len(url_factoids) * 100
        
        return {
            "url": url,
            "accuracy_score": accuracy_score,
            "total_factoids": len(url_factoids),
            "true_count": true_count,
            "false_count": false_count,
            "uncertain_count": uncertain_count,
            "average_confidence": sum(f.confidence for f in url_factoids) / len(url_factoids)
        }
    
    def get_accuracy_by_domain(self, domain: str) -> Dict[str, Any]:
        """Calculate aggregate accuracy score for a domain/site"""
        domain_factoids = [f for f in self.factoids if f.domain == domain]
        
        if not domain_factoids:
            return {
                "domain": domain,
                "accuracy_score": 0.0,
                "total_factoids": 0,
                "total_urls": 0
            }
        
        # Get all unique URLs for this domain
        unique_urls = list(set(f.source_url for f in domain_factoids))
        
        # Calculate weighted average by trust weight
        total_weight = sum(f.trust_weight for f in domain_factoids)
        weighted_truth = sum(f.truth_score * f.trust_weight for f in domain_factoids)
        
        accuracy_score = (weighted_truth / total_weight * 100) if total_weight > 0 else 0.0
        
        # Get per-URL breakdown
        url_scores = [self.get_accuracy_by_url(url) for url in unique_urls]
        url_scores.sort(key=lambda x: x['accuracy_score'], reverse=True)
        
        return {
            "domain": domain,
            "accuracy_score": accuracy_score,
            "total_factoids": len(domain_factoids),
            "total_urls": len(unique_urls),
            "url_breakdown": url_scores,
            "best_url": url_scores[0] if url_scores else None,
            "worst_url": url_scores[-1] if url_scores else None
        }
    
    def save_to_oxen(self, storage_backend) -> bool:
        """
        Save the truth registry to an Oxen repository.
        
        Args:
            storage_backend: OxenStorage instance
        
        Returns:
            True if save was successful
        """
        try:
            import tempfile
            
            with tempfile.TemporaryDirectory() as temp_dir:
                temp_path = Path(temp_dir)
                
                # Convert factoids to DataFrame
                factoid_data = [f.model_dump() for f in self.factoids]
                df_factoids = pd.DataFrame(factoid_data)
                
                # Save as CSV
                factoids_file = temp_path / "factoids.csv"
                df_factoids.to_csv(factoids_file, index=False)
                
                # Save verification log
                if self.verification_log:
                    df_log = pd.DataFrame(self.verification_log)
                    log_file = temp_path / "verification_log.csv"
                    df_log.to_csv(log_file, index=False)
                
                # Generate accuracy reports
                domains = list(set(f.domain for f in self.factoids))
                domain_accuracy = [self.get_accuracy_by_domain(d) for d in domains]
                
                # Create summary README
                readme_file = temp_path / "README.md"
                self._create_registry_readme(readme_file, domain_accuracy)
                
                # Use Oxen SDK to push data
                return self._push_to_oxen(temp_path, storage_backend)
        
        except Exception as e:
            print(f"❌ Failed to save truth registry: {e}")
            return False
    
    def _push_to_oxen(self, temp_path: Path, storage_backend) -> bool:
        """Push truth registry data to Oxen using SDK"""
        try:
            import oxen
            
            # Initialize local Oxen repo
            local_repo = temp_path / "truth_registry_local"
            local_repo.mkdir(exist_ok=True)
            
            oxen.init(str(local_repo))
            
            # Copy files to local repo
            import shutil
            for file in temp_path.glob("*.csv"):
                shutil.copy(file, local_repo / file.name)
            for file in temp_path.glob("*.md"):
                shutil.copy(file, local_repo / file.name)
            
            # Add and commit
            oxen.add(str(local_repo / "factoids.csv"))
            if (local_repo / "verification_log.csv").exists():
                oxen.add(str(local_repo / "verification_log.csv"))
            oxen.add(str(local_repo / "README.md"))
            
            oxen.commit(f"Truth registry update: {len(self.factoids)} factoids")
            
            # Set remote and push
            remote_url = f"https://hub.oxen.ai/{self.namespace}/{self.repo_name}"
            oxen.config.set_remote("origin", remote_url)
            
            if storage_backend.api_key:
                oxen.auth.config_auth(host=storage_backend.base_url, token=storage_backend.api_key)
            
            oxen.push("origin", "main")
            
            print(f"✅ Truth registry saved to: https://oxen.ai/{self.namespace}/{self.repo_name}")
            return True
            
        except Exception as e:
            print(f"❌ Failed to push to Oxen: {e}")
            import traceback
            traceback.print_exc()
            return False
    
    def _create_registry_readme(self, readme_path: Path, domain_accuracy: List[Dict[str, Any]]) -> None:
        """Create a README summary for the truth registry"""
        
        with open(readme_path, 'w') as f:
            f.write("# Pantsonfire Truth Registry\n\n")
            f.write(f"**Last Updated**: {datetime.now().isoformat()}\n\n")
            f.write(f"**Total Factoids**: {len(self.factoids)}\n")
            f.write(f"**Domains Analyzed**: {len(domain_accuracy)}\n\n")
            
            f.write("## Accuracy Rankings by Domain\n\n")
            
            # Sort by accuracy score
            domain_accuracy.sort(key=lambda x: x['accuracy_score'], reverse=True)
            
            for domain_data in domain_accuracy:
                score = domain_data['accuracy_score']
                emoji = "✅" if score >= 80 else "⚠️" if score >= 60 else "❌"
                
                f.write(f"### {emoji} {domain_data['domain']}\n")
                f.write(f"- **Accuracy**: {score:.1f}%\n")
                f.write(f"- **Total Factoids**: {domain_data['total_factoids']}\n")
                f.write(f"- **URLs Analyzed**: {domain_data['total_urls']}\n")
                
                if domain_data.get('best_url'):
                    best = domain_data['best_url']
                    f.write(f"- **Best URL**: [{best['url']}]({best['url']}) ({best['accuracy_score']:.1f}%)\n")
                
                if domain_data.get('worst_url'):
                    worst = domain_data['worst_url']
                    f.write(f"- **Worst URL**: [{worst['url']}]({worst['url']}) ({worst['accuracy_score']:.1f}%)\n")
                
                f.write("\n")
            
            f.write("## Files in This Registry\n\n")
            f.write("- `factoids.csv` - All extracted factoids with truth scores\n")
            f.write("- `verification_log.csv` - History of verification checks\n")
            f.write("- `README.md` - This summary file\n\n")
            
            f.write("## Schema\n\n")
            f.write("### Factoids Table\n")
            f.write("- `factoid_id` - Unique identifier\n")
            f.write("- `claim_text` - The factual claim\n")
            f.write("- `source_url` - Where it was found\n")
            f.write("- `truth_score` - Likelihood of being true (0-1)\n")
            f.write("- `category` - Type of claim (api, version, etc.)\n")
            f.write("- `confidence` - Extraction confidence\n")
            f.write("- `trust_weight` - Source trustworthiness\n\n")
    
    def query_factoids(
        self,
        category: Optional[str] = None,
        domain: Optional[str] = None,
        min_truth_score: Optional[float] = None,
        max_truth_score: Optional[float] = None
    ) -> List[Factoid]:
        """
        Query factoids with filters.
        
        Args:
            category: Filter by category
            domain: Filter by domain
            min_truth_score: Minimum truth score
            max_truth_score: Maximum truth score
        
        Returns:
            Filtered list of factoids
        """
        filtered = self.factoids
        
        if category:
            filtered = [f for f in filtered if f.category == category]
        
        if domain:
            filtered = [f for f in filtered if f.domain == domain]
        
        if min_truth_score is not None:
            filtered = [f for f in filtered if f.truth_score >= min_truth_score]
        
        if max_truth_score is not None:
            filtered = [f for f in filtered if f.truth_score <= max_truth_score]
        
        return filtered
    
    def get_conflicts(self, min_conflict_score: float = 0.7) -> List[Dict[str, Any]]:
        """
        Find factoids with conflicting claims.
        
        Args:
            min_conflict_score: Minimum score difference to consider a conflict
        
        Returns:
            List of conflict groups
        """
        conflicts = []
        processed = set()
        
        for i, factoid in enumerate(self.factoids):
            if i in processed:
                continue
            
            # Find conflicting factoids (similar claim, different truth score)
            for j, other in enumerate(self.factoids[i+1:], start=i+1):
                if j in processed:
                    continue
                
                if self._claims_are_similar(factoid.claim_text, other.claim_text):
                    score_diff = abs(factoid.truth_score - other.truth_score)
                    
                    if score_diff >= min_conflict_score:
                        conflicts.append({
                            "factoid_1": factoid.model_dump(),
                            "factoid_2": other.model_dump(),
                            "score_difference": score_diff,
                            "requires_review": True
                        })
                        
                        processed.add(i)
                        processed.add(j)
        
        return conflicts


class ConsensusEngine:
    """Calculate consensus across multiple factoid sources"""
    
    def __init__(self, config: Config):
        self.config = config
        self.consensus_threshold = getattr(config, 'consensus_threshold', 0.75)
    
    def calculate_consensus_score(
        self,
        factoids: List[Factoid],
        weights: Optional[Dict[str, float]] = None
    ) -> float:
        """
        Calculate weighted consensus score.
        
        Args:
            factoids: List of factoids for the same claim
            weights: Optional custom weights by source type
        
        Returns:
            Consensus score (0-1)
        """
        if not factoids:
            return 0.0
        
        # Default weights favor authoritative sources
        default_weights = {
            'official_docs_match': 0.4,
            'web_search_corroboration': 0.3,
            'github_repo_verification': 0.2,
            'recency_factor': 0.1
        }
        
        weights = weights or default_weights
        
        # Calculate components
        official_docs_score = self._calculate_official_docs_score(factoids)
        web_search_score = self._calculate_web_search_score(factoids)
        github_score = self._calculate_github_score(factoids)
        recency_score = self._calculate_recency_score(factoids)
        
        consensus = (
            weights['official_docs_match'] * official_docs_score +
            weights['web_search_corroboration'] * web_search_score +
            weights['github_repo_verification'] * github_score +
            weights['recency_factor'] * recency_score
        )
        
        return consensus
    
    def _calculate_official_docs_score(self, factoids: List[Factoid]) -> float:
        """Score based on official documentation matches"""
        docs_factoids = [f for f in factoids if f.content_type in ['docs', 'api']]
        if not docs_factoids:
            return 0.5
        
        return sum(f.truth_score for f in docs_factoids) / len(docs_factoids)
    
    def _calculate_web_search_score(self, factoids: List[Factoid]) -> float:
        """Score based on web search corroboration"""
        web_verified = [f for f in factoids if f.verification_method == 'web_search']
        if not web_verified:
            return 0.5
        
        return sum(f.truth_score for f in web_verified) / len(web_verified)
    
    def _calculate_github_score(self, factoids: List[Factoid]) -> float:
        """Score based on GitHub repository verification"""
        github_factoids = [f for f in factoids if 'github.com' in f.source_url]
        if not github_factoids:
            return 0.5
        
        return sum(f.truth_score for f in github_factoids) / len(github_factoids)
    
    def _calculate_recency_score(self, factoids: List[Factoid]) -> float:
        """Score based on content recency"""
        dated_factoids = [f for f in factoids if f.published_at]
        if not dated_factoids:
            return 0.5
        
        # Average staleness across all factoids
        staleness_scores = []
        for f in dated_factoids:
            days_old = (datetime.now() - f.published_at).days
            staleness = max(0.0, 1.0 - (days_old / 730))  # 2 year decay
            staleness_scores.append(staleness)
        
        return sum(staleness_scores) / len(staleness_scores) if staleness_scores else 0.5

