#!/usr/bin/env python3
"""
Eden Causal Tester
Safe hypothesis validation sandbox

Purpose: Test interventions and validate causal hypotheses before real-world actions
Examples:
  - "If I add an index, will query speed improve?"
  - "Does caching reduce server load?"
  - "Will recursion depth cause stack overflow?"
"""

import json
import numpy as np
from dataclasses import dataclass, asdict
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Any, Callable
from enum import Enum
import logging

# Configuration
EDEN_ROOT = Path("/Eden/CORE")
CAUSAL_LOG = EDEN_ROOT / "logs" / "phi_causal.log"
CAUSAL_STATE = EDEN_ROOT / "phi_fractal" / "sandbox_envs" / "causal_state.json"
EVIDENCE_DIR = EDEN_ROOT / "phi_fractal" / "sandbox_envs" / "evidence"

# Create directories
CAUSAL_STATE.parent.mkdir(parents=True, exist_ok=True)
EVIDENCE_DIR.mkdir(parents=True, exist_ok=True)
CAUSAL_LOG.parent.mkdir(parents=True, exist_ok=True)

# Setup logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - CAUSAL - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler(CAUSAL_LOG),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)


class CausalRelation(Enum):
    """Types of causal relationships"""
    CAUSES = "causes"
    PREVENTS = "prevents"
    CORRELATES = "correlates"
    INDEPENDENT = "independent"
    UNKNOWN = "unknown"


@dataclass
class Hypothesis:
    """A testable causal hypothesis"""
    id: str
    description: str
    intervention: str  # What we change
    expected_effect: str  # What we expect to happen
    variables: Dict[str, Any]  # Variables involved
    confidence: float = 0.5  # Prior confidence
    tested: bool = False
    result: Optional[str] = None


@dataclass
class Experiment:
    """An experiment to test a hypothesis"""
    hypothesis_id: str
    timestamp: str
    control_condition: Dict[str, Any]
    intervention_condition: Dict[str, Any]
    control_outcome: float
    intervention_outcome: float
    effect_size: float
    causal_relation: CausalRelation
    confidence: float
    evidence_strength: float


class SandboxEnvironment:
    """Simulated environment for safe testing"""
    
    def __init__(self, name: str):
        self.name = name
        self.state = {}
        self.history = []
    
    def reset(self):
        """Reset environment to initial state"""
        self.state = {}
        self.history = []
    
    def simulate(self, action: str, params: Dict[str, Any]) -> Dict[str, Any]:
        """
        Simulate an action and return outcomes
        This is a simplified simulation - in production, use real models
        """
        # Simple rule-based simulation for demonstration
        if action == "add_index":
            baseline_time = params.get('query_time', 100)
            index_benefit = 0.7  # 30% improvement
            noise = np.random.normal(0, 0.05)
            new_time = baseline_time * (index_benefit + noise)
            return {
                'query_time': new_time,
                'improvement': baseline_time - new_time,
                'success': new_time < baseline_time
            }
        
        elif action == "add_caching":
            baseline_load = params.get('server_load', 80)
            cache_benefit = 0.6  # 40% reduction
            noise = np.random.normal(0, 0.03)
            new_load = baseline_load * (cache_benefit + noise)
            return {
                'server_load': new_load,
                'reduction': baseline_load - new_load,
                'success': new_load < baseline_load
            }
        
        elif action == "increase_recursion_depth":
            depth = params.get('depth', 100)
            max_safe_depth = 1000
            
            if depth > max_safe_depth:
                return {
                    'stack_overflow': True,
                    'completed': False,
                    'success': False
                }
            else:
                execution_time = depth * 0.01  # Linear with depth
                return {
                    'stack_overflow': False,
                    'completed': True,
                    'execution_time': execution_time,
                    'success': True
                }
        
        elif action == "add_validation":
            baseline_bugs = params.get('bugs_found', 10)
            validation_effectiveness = 0.3  # Catches 30% more bugs
            noise = np.random.normal(0, 0.05)
            additional_bugs = baseline_bugs * (validation_effectiveness + noise)
            return {
                'bugs_found': baseline_bugs + additional_bugs,
                'improvement': additional_bugs,
                'success': additional_bugs > 0
            }
        
        else:
            # Generic simulation
            baseline = params.get('baseline_value', 50)
            effect = np.random.normal(10, 5)  # Random effect
            return {
                'outcome': baseline + effect,
                'effect': effect,
                'success': effect > 0
            }


class CausalTester:
    """Main causal testing and hypothesis validation system"""
    
    def __init__(self):
        self.hypotheses: Dict[str, Hypothesis] = {}
        self.experiments: List[Experiment] = []
        self.causal_graph: Dict[str, List[tuple]] = {}  # Variable -> [(effect, relation)]
        self.sandbox = SandboxEnvironment("default")
        
        self._load_state()
        logger.info("🔬 Causal Tester initialized")
    
    def _load_state(self):
        """Load saved causal state"""
        if CAUSAL_STATE.exists():
            try:
                with open(CAUSAL_STATE, 'r') as f:
                    data = json.load(f)
                
                # Load hypotheses
                for hyp_id, hyp_data in data.get('hypotheses', {}).items():
                    self.hypotheses[hyp_id] = Hypothesis(**hyp_data)
                
                logger.info(f"Loaded {len(self.hypotheses)} hypotheses from state")
            except Exception as e:
                logger.error(f"Failed to load state: {e}")
    
    def _save_state(self):
        """Save causal state"""
        try:
            data = {
                'hypotheses': {hid: asdict(h) for hid, h in self.hypotheses.items()},
                'experiment_count': len(self.experiments),
                'causal_graph': {
                    k: [(effect, rel.value) for effect, rel in v]
                    for k, v in self.causal_graph.items()
                },
                'last_updated': datetime.now().isoformat()
            }
            
            with open(CAUSAL_STATE, 'w') as f:
                json.dump(data, f, indent=2)
            
            logger.info(f"Saved {len(self.hypotheses)} hypotheses to state")
        except Exception as e:
            logger.error(f"Failed to save state: {e}")
    
    def propose_hypothesis(self, hypothesis: Hypothesis) -> Hypothesis:
        """Propose a new hypothesis to test"""
        logger.info(f"💡 Proposed hypothesis: {hypothesis.description}")
        
        self.hypotheses[hypothesis.id] = hypothesis
        self._save_state()
        
        return hypothesis
    
    def test_hypothesis(self, hypothesis_id: str, n_trials: int = 10) -> Experiment:
        """
        Test a hypothesis through controlled experiments
        
        Runs n_trials with control and intervention conditions
        Computes effect size and causal relation
        """
        if hypothesis_id not in self.hypotheses:
            raise ValueError(f"Hypothesis {hypothesis_id} not found")
        
        hypothesis = self.hypotheses[hypothesis_id]
        logger.info(f"🧪 Testing hypothesis: {hypothesis.description}")
        logger.info(f"   Running {n_trials} trials...")
        
        control_outcomes = []
        intervention_outcomes = []
        
        # Run trials
        for trial in range(n_trials):
            # Control condition (no intervention)
            self.sandbox.reset()
            control_result = self.sandbox.simulate(
                "baseline",
                hypothesis.variables
            )
            control_outcomes.append(control_result.get('outcome', 
                                   hypothesis.variables.get('baseline_value', 50)))
            
            # Intervention condition
            self.sandbox.reset()
            intervention_result = self.sandbox.simulate(
                hypothesis.intervention,
                hypothesis.variables
            )
            intervention_outcomes.append(intervention_result.get('outcome',
                                        intervention_result.get('query_time',
                                        intervention_result.get('server_load', 50))))
        
        # Analyze results
        control_mean = np.mean(control_outcomes)
        intervention_mean = np.mean(intervention_outcomes)
        
        # Effect size (Cohen's d)
        pooled_std = np.sqrt((np.std(control_outcomes)**2 + 
                             np.std(intervention_outcomes)**2) / 2)
        effect_size = (intervention_mean - control_mean) / pooled_std if pooled_std > 0 else 0
        
        # Determine causal relation
        if abs(effect_size) > 0.8:
            if effect_size > 0:
                causal_relation = CausalRelation.CAUSES
            else:
                causal_relation = CausalRelation.PREVENTS
            evidence_strength = 0.9
        elif abs(effect_size) > 0.5:
            causal_relation = CausalRelation.CORRELATES
            evidence_strength = 0.7
        elif abs(effect_size) > 0.2:
            causal_relation = CausalRelation.CORRELATES
            evidence_strength = 0.5
        else:
            causal_relation = CausalRelation.INDEPENDENT
            evidence_strength = 0.3
        
        # Calculate confidence
        confidence = min(0.95, 0.5 + (abs(effect_size) / 2))
        
        # Create experiment record
        experiment = Experiment(
            hypothesis_id=hypothesis_id,
            timestamp=datetime.now().isoformat(),
            control_condition=hypothesis.variables,
            intervention_condition={**hypothesis.variables, 'intervention': hypothesis.intervention},
            control_outcome=control_mean,
            intervention_outcome=intervention_mean,
            effect_size=effect_size,
            causal_relation=causal_relation,
            confidence=confidence,
            evidence_strength=evidence_strength
        )
        
        # Update hypothesis
        hypothesis.tested = True
        hypothesis.result = causal_relation.value
        hypothesis.confidence = confidence
        
        # Update causal graph
        intervention_var = hypothesis.intervention
        effect_var = hypothesis.expected_effect
        
        if intervention_var not in self.causal_graph:
            self.causal_graph[intervention_var] = []
        
        self.causal_graph[intervention_var].append((effect_var, causal_relation))
        
        # Save
        self.experiments.append(experiment)
        self._save_state()
        self._save_evidence(experiment)
        
        logger.info(f"   ✅ Test complete!")
        logger.info(f"   Relation: {causal_relation.value}")
        logger.info(f"   Effect size: {effect_size:.3f}")
        logger.info(f"   Confidence: {confidence:.2f}")
        
        return experiment
    
    def _save_evidence(self, experiment: Experiment):
        """Save experiment evidence for later analysis"""
        evidence_file = EVIDENCE_DIR / f"evidence_{experiment.hypothesis_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
        
        try:
            with open(evidence_file, 'w') as f:
                json.dump(asdict(experiment), f, indent=2)
        except Exception as e:
            logger.error(f"Failed to save evidence: {e}")
    
    def get_causal_report(self) -> Dict[str, Any]:
        """Get causal testing statistics"""
        tested_hypotheses = [h for h in self.hypotheses.values() if h.tested]
        
        causal_counts = {}
        for exp in self.experiments:
            rel = exp.causal_relation.value
            causal_counts[rel] = causal_counts.get(rel, 0) + 1
        
        return {
            'total_hypotheses': len(self.hypotheses),
            'tested_hypotheses': len(tested_hypotheses),
            'experiments_run': len(self.experiments),
            'causal_relations_discovered': len(self.causal_graph),
            'relation_distribution': causal_counts,
            'avg_confidence': np.mean([h.confidence for h in tested_hypotheses]) if tested_hypotheses else 0
        }
    
    def visualize_causal_graph(self):
        """Print causal graph"""
        print("\n🕸️  Causal Graph:")
        print("=" * 60)
        
        if not self.causal_graph:
            print("  (No causal relations discovered yet)")
            return
        
        for cause, effects in self.causal_graph.items():
            print(f"\n📌 {cause}")
            for effect, relation in effects:
                symbol = "→" if relation == CausalRelation.CAUSES else "⊥" if relation == CausalRelation.PREVENTS else "~"
                print(f"   {symbol} {effect} ({relation.value})")


def test_causal_tester():
    """Test the Causal Tester with example hypotheses"""
    print("\n" + "=" * 70)
    print("🔬 EDEN CAUSAL TESTER - TEST MODE")
    print("=" * 70 + "\n")
    
    ct = CausalTester()
    
    # Define test hypotheses
    test_hypotheses = [
        Hypothesis(
            id="hyp_001",
            description="Adding database index improves query performance",
            intervention="add_index",
            expected_effect="faster_queries",
            variables={'query_time': 100}
        ),
        Hypothesis(
            id="hyp_002",
            description="Caching reduces server load",
            intervention="add_caching",
            expected_effect="reduced_load",
            variables={'server_load': 80}
        ),
        Hypothesis(
            id="hyp_003",
            description="Deep recursion causes stack overflow",
            intervention="increase_recursion_depth",
            expected_effect="stack_overflow",
            variables={'depth': 1500}
        ),
        Hypothesis(
            id="hyp_004",
            description="Input validation catches more bugs",
            intervention="add_validation",
            expected_effect="more_bugs_found",
            variables={'bugs_found': 10}
        ),
    ]
    
    # Propose and test each hypothesis
    print("💡 Proposing hypotheses...\n")
    for hyp in test_hypotheses:
        ct.propose_hypothesis(hyp)
        print(f"   ✅ {hyp.id}: {hyp.description}")
    
    print("\n" + "=" * 70)
    print("🧪 Testing hypotheses...\n")
    
    for hyp in test_hypotheses:
        print(f"Testing: {hyp.description}")
        experiment = ct.test_hypothesis(hyp.id, n_trials=20)
        
        print(f"   Control:      {experiment.control_outcome:.2f}")
        print(f"   Intervention: {experiment.intervention_outcome:.2f}")
        print(f"   Effect size:  {experiment.effect_size:.3f}")
        print(f"   Relation:     {experiment.causal_relation.value}")
        print(f"   Confidence:   {experiment.confidence:.2f}")
        print()
    
    # Show causal graph
    ct.visualize_causal_graph()
    
    # Final report
    print("\n" + "=" * 70)
    print("📊 Causal Testing Report")
    print("=" * 70)
    
    report = ct.get_causal_report()
    print(f"\nTotal hypotheses: {report['total_hypotheses']}")
    print(f"Tested: {report['tested_hypotheses']}")
    print(f"Experiments run: {report['experiments_run']}")
    print(f"Causal relations discovered: {report['causal_relations_discovered']}")
    print(f"Average confidence: {report['avg_confidence']:.2f}")
    
    if report['relation_distribution']:
        print("\nRelation types discovered:")
        for rel_type, count in report['relation_distribution'].items():
            print(f"   {rel_type}: {count}")
    
    print(f"\n💾 State saved to: {CAUSAL_STATE}")
    print(f"📁 Evidence saved to: {EVIDENCE_DIR}\n")


def main():
    import sys
    if len(sys.argv) > 1 and sys.argv[1] == "test":
        test_causal_tester()
    else:
        print("Eden Causal Tester")
        print("\nUsage:")
        print("  python3 causal_tester.py test    # Run tests")
        print("\nIntegration:")
        print("  from causal_tester import CausalTester, Hypothesis")
        print("  ct = CausalTester()")
        print("  ct.propose_hypothesis(my_hypothesis)")
        print("  ct.test_hypothesis(hypothesis_id)")


if __name__ == "__main__":
    main()
