#!/usr/bin/env python3
"""
EDEN PATCH PROPOSAL SYSTEM
==========================
Turns meta-cognition insights into structured patch proposals.

A proposal must survive:
1. Reflection (meta-cognition generated)
2. Queuing (dedupe passed)
3. Review (human or gate approved)
4. Compilation (py_compile passed)
5. Test (test suite passed)
6. Time delay (minimum age before apply)

This is how biological systems evolve without collapsing.

φ = 1.618033988749895
"""

import sys
import os
import json
import sqlite3
import hashlib
import subprocess
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Optional

sys.path.insert(0, '/Eden/CORE')

PHI = 1.618033988749895
DB_PATH = '/Eden/DATA/patch_proposals.db'
META_PATH = '/Eden/DATA/meta_cognition_latest.json'
MIN_AGE_HOURS = 24  # Proposals must age before apply


class PatchProposal:
    """A structured patch proposal with survival gates."""
    
    def __init__(self, insight: str, target_file: str = None):
        self.id = None
        self.insight = insight
        self.target_file = target_file
        self.diff_concept = None
        self.test_code = None
        self.created_at = datetime.now()
        
        # Survival gates (all must pass)
        self.gates = {
            "reflection": False,    # Generated by meta-cognition
            "queued": False,        # Added to queue (dedupe passed)
            "reviewed": False,      # Human or gate reviewed
            "compiled": False,      # py_compile passed
            "tested": False,        # Test suite passed
            "aged": False,          # Minimum time elapsed
        }
        
        self.applied = False
        self.phi_alignment = 0.85


class PatchProposalSystem:
    """
    Manages the full lifecycle of patch proposals.
    Insight → Proposal → Gates → Apply
    """
    
    def __init__(self):
        self.phi = PHI
        self.db_path = DB_PATH
        self.min_age_hours = MIN_AGE_HOURS
        self._init_database()
        
        print(f"📋 Patch Proposal System initialized")
        print(f"   φ = {self.phi}")
        print(f"   Min age: {self.min_age_hours}h")
    
    def _init_database(self):
        conn = sqlite3.connect(self.db_path)
        conn.execute('''CREATE TABLE IF NOT EXISTS proposals (
            id INTEGER PRIMARY KEY,
            proposal_hash TEXT UNIQUE,
            insight TEXT,
            target_file TEXT,
            diff_concept TEXT,
            test_code TEXT,
            created_at TEXT,
            
            -- Survival gates
            gate_reflection INTEGER DEFAULT 0,
            gate_queued INTEGER DEFAULT 0,
            gate_reviewed INTEGER DEFAULT 0,
            gate_compiled INTEGER DEFAULT 0,
            gate_tested INTEGER DEFAULT 0,
            gate_aged INTEGER DEFAULT 0,
            
            applied INTEGER DEFAULT 0,
            applied_at TEXT,
            phi_alignment REAL DEFAULT 0.85,
            
            notes TEXT
        )''')
        conn.execute('''CREATE TABLE IF NOT EXISTS gate_logs (
            id INTEGER PRIMARY KEY,
            proposal_id INTEGER,
            gate_name TEXT,
            passed INTEGER,
            timestamp TEXT,
            details TEXT
        )''')
        conn.commit()
        conn.close()
    
    def _hash_insight(self, insight: str) -> str:
        return hashlib.sha256(insight.encode()).hexdigest()[:16]
    
    # ========================================================================
    # PROPOSAL GENERATION
    # ========================================================================
    
    def create_proposal_from_insight(self, insight: str, phi_alignment: float = 0.85) -> Dict:
        """
        Create a structured patch proposal from a meta-cognition insight.
        """
        proposal_hash = self._hash_insight(insight)
        
        # Check for duplicate
        conn = sqlite3.connect(self.db_path)
        cur = conn.cursor()
        cur.execute("SELECT id FROM proposals WHERE proposal_hash = ?", (proposal_hash,))
        if cur.fetchone():
            conn.close()
            return {"status": "duplicate", "hash": proposal_hash}
        
        # Analyze insight to determine target and diff
        target_file, diff_concept, test_code = self._analyze_insight(insight)
        
        # Create proposal
        created_at = datetime.now().isoformat()
        
        conn.execute('''INSERT INTO proposals 
            (proposal_hash, insight, target_file, diff_concept, test_code, 
             created_at, gate_reflection, phi_alignment)
            VALUES (?, ?, ?, ?, ?, ?, 1, ?)''',
            (proposal_hash, insight, target_file, diff_concept, test_code,
             created_at, phi_alignment))
        
        proposal_id = conn.execute("SELECT last_insert_rowid()").fetchone()[0]
        
        # Log reflection gate
        conn.execute('''INSERT INTO gate_logs 
            (proposal_id, gate_name, passed, timestamp, details)
            VALUES (?, 'reflection', 1, ?, 'Generated from meta-cognition')''',
            (proposal_id, created_at))
        
        conn.commit()
        conn.close()
        
        return {
            "status": "created",
            "proposal_id": proposal_id,
            "hash": proposal_hash,
            "target_file": target_file,
            "diff_concept": diff_concept[:100] if diff_concept else None,
            "gates_passed": ["reflection"]
        }
    
    def _analyze_insight(self, insight: str) -> tuple:
        """
        Analyze an insight to determine what file to modify and how.
        Returns (target_file, diff_concept, test_code)
        """
        insight_lower = insight.lower()
        
        # Map insights to target files and modifications
        mappings = {
            "reasoning": {
                "file": "/Eden/CORE/eden_meta_cognition.py",
                "diff": "Add alternative reasoning type detection",
                "test": "def test_reasoning_types(): assert len(mc.reasoning_patterns) > 1"
            },
            "analogical": {
                "file": "/Eden/CORE/eden_semantic_reasoning.py",
                "diff": "Add analogical reasoning pass before final output",
                "test": "def test_analogical(): assert 'analogical' in sr.reason_why('test')['methods']"
            },
            "emotional": {
                "file": "/Eden/CORE/eden_emotional_intelligence.py",
                "diff": "Enhance emotional pattern detection",
                "test": "def test_emotions(): assert ei.get_current_state() is not None"
            },
            "improvement": {
                "file": "/Eden/CORE/eden_omega_self_evolution.py",
                "diff": "Add new improvement detection heuristics",
                "test": "def test_improvements(): assert evo.analyze_file('/Eden/CORE/test.py')"
            },
            "self": {
                "file": "/Eden/CORE/eden_self_reflection.py",
                "diff": "Deepen self-reflection capabilities",
                "test": "def test_reflection(): assert sr.reflect() is not None"
            },
        }
        
        # Find best match
        for keyword, mapping in mappings.items():
            if keyword in insight_lower:
                return mapping["file"], mapping["diff"], mapping["test"]
        
        # Default: meta-cognition itself
        return (
            "/Eden/CORE/eden_meta_cognition.py",
            f"Apply insight: {insight[:100]}",
            "def test_insight(): pass  # Manual verification required"
        )
    
    # ========================================================================
    # GATE CHECKS
    # ========================================================================
    
    def check_queued_gate(self, proposal_id: int) -> Dict:
        """Mark proposal as queued (dedupe already passed at creation)."""
        conn = sqlite3.connect(self.db_path)
        conn.execute("UPDATE proposals SET gate_queued = 1 WHERE id = ?", (proposal_id,))
        conn.execute('''INSERT INTO gate_logs 
            (proposal_id, gate_name, passed, timestamp, details)
            VALUES (?, 'queued', 1, ?, 'Dedupe passed')''',
            (proposal_id, datetime.now().isoformat()))
        conn.commit()
        conn.close()
        return {"gate": "queued", "passed": True}
    
    def check_review_gate(self, proposal_id: int, approved: bool = True, reviewer: str = "system") -> Dict:
        """Mark proposal as reviewed."""
        conn = sqlite3.connect(self.db_path)
        conn.execute("UPDATE proposals SET gate_reviewed = ? WHERE id = ?", 
                    (1 if approved else 0, proposal_id))
        conn.execute('''INSERT INTO gate_logs 
            (proposal_id, gate_name, passed, timestamp, details)
            VALUES (?, 'reviewed', ?, ?, ?)''',
            (proposal_id, 1 if approved else 0, datetime.now().isoformat(), f"Reviewer: {reviewer}"))
        conn.commit()
        conn.close()
        return {"gate": "reviewed", "passed": approved}
    
    def check_compile_gate(self, proposal_id: int) -> Dict:
        """Run py_compile on target file."""
        conn = sqlite3.connect(self.db_path)
        cur = conn.cursor()
        cur.execute("SELECT target_file FROM proposals WHERE id = ?", (proposal_id,))
        row = cur.fetchone()
        
        if not row or not row[0]:
            conn.close()
            return {"gate": "compiled", "passed": False, "error": "No target file"}
        
        target_file = row[0]
        
        if not os.path.exists(target_file):
            conn.close()
            return {"gate": "compiled", "passed": False, "error": "File not found"}
        
        # Run compile check
        result = subprocess.run(
            ["/usr/bin/python3", "-m", "py_compile", target_file],
            capture_output=True, text=True
        )
        
        passed = result.returncode == 0
        
        conn.execute("UPDATE proposals SET gate_compiled = ? WHERE id = ?", 
                    (1 if passed else 0, proposal_id))
        conn.execute('''INSERT INTO gate_logs 
            (proposal_id, gate_name, passed, timestamp, details)
            VALUES (?, 'compiled', ?, ?, ?)''',
            (proposal_id, 1 if passed else 0, datetime.now().isoformat(),
             result.stderr[:200] if result.stderr else "OK"))
        conn.commit()
        conn.close()
        
        return {"gate": "compiled", "passed": passed, "error": result.stderr if not passed else None}
    
    def check_test_gate(self, proposal_id: int) -> Dict:
        """Run tests from eden_patch_tests based on proposal insight."""
        import sys
        sys.path.insert(0, '/Eden/CORE')
        
        conn = sqlite3.connect(self.db_path)
        cur = conn.cursor()
        cur.execute("SELECT insight, target_file FROM proposals WHERE id = ?", (proposal_id,))
        row = cur.fetchone()
        
        if not row:
            conn.close()
            return {"gate": "tested", "passed": False, "error": "Proposal not found"}
        
        insight, target_file = row
        insight_lower = insight.lower()
        
        # Map keywords to test functions
        test_map = {
            "reasoning": "test_reasoning_types",
            "analogical": "test_analogical",
            "emotional": "test_emotional",
            "integration": "test_integration_import",
            "meta": "test_meta_cognition",
            "improvement": "test_integration_compiles",
        }
        
        # Find matching test
        test_name = "test_default"
        for keyword, func in test_map.items():
            if keyword in insight_lower:
                test_name = func
                break
        
        # Run the test
        try:
            import eden_patch_tests as tests
            test_func = getattr(tests, test_name, None)
            if test_func is None:
                test_func = tests.test_default
            
            result = test_func()
            passed = bool(result) if result is not None else True
        except Exception as e:
            passed = False
            conn.execute("UPDATE proposals SET gate_tested = 0 WHERE id = ?", (proposal_id,))
            conn.execute("""INSERT INTO gate_logs 
                (proposal_id, gate_name, passed, timestamp, details)
                VALUES (?, 'tested', 0, ?, ?)""",
                (proposal_id, datetime.now().isoformat(), f"Error: {str(e)[:100]}"))
            conn.commit()
            conn.close()
            return {"gate": "tested", "passed": False, "error": str(e)[:100]}
        
        conn.execute("UPDATE proposals SET gate_tested = ? WHERE id = ?", (1 if passed else 0, proposal_id))
        conn.execute("""INSERT INTO gate_logs 
            (proposal_id, gate_name, passed, timestamp, details)
            VALUES (?, 'tested', ?, ?, ?)""",
            (proposal_id, 1 if passed else 0, datetime.now().isoformat(), f"Ran {test_name}"))
        conn.commit()
        conn.close()
        
        return {"gate": "tested", "passed": passed, "test": test_name}


    def check_age_gate(self, proposal_id: int) -> Dict:
        """Check if proposal has aged enough."""
        conn = sqlite3.connect(self.db_path)
        cur = conn.cursor()
        cur.execute("SELECT created_at FROM proposals WHERE id = ?", (proposal_id,))
        row = cur.fetchone()
        
        if not row:
            conn.close()
            return {"gate": "aged", "passed": False, "error": "Proposal not found"}
        
        created_at = datetime.fromisoformat(row[0])
        age_hours = (datetime.now() - created_at).total_seconds() / 3600
        passed = age_hours >= self.min_age_hours
        
        if passed:
            conn.execute("UPDATE proposals SET gate_aged = 1 WHERE id = ?", (proposal_id,))
            conn.execute('''INSERT INTO gate_logs 
                (proposal_id, gate_name, passed, timestamp, details)
                VALUES (?, 'aged', 1, ?, ?)''',
                (proposal_id, datetime.now().isoformat(), f"Age: {age_hours:.1f}h"))
            conn.commit()
        
        conn.close()
        return {"gate": "aged", "passed": passed, "age_hours": age_hours, "required": self.min_age_hours}
    
    # ========================================================================
    # APPLY
    # ========================================================================
    
    def check_all_gates(self, proposal_id: int) -> Dict:
        """Check all gates for a proposal."""
        conn = sqlite3.connect(self.db_path)
        cur = conn.cursor()
        cur.execute('''SELECT gate_reflection, gate_queued, gate_reviewed, 
                              gate_compiled, gate_tested, gate_aged, applied
                       FROM proposals WHERE id = ?''', (proposal_id,))
        row = cur.fetchone()
        conn.close()
        
        if not row:
            return {"error": "Proposal not found"}
        
        gates = {
            "reflection": bool(row[0]),
            "queued": bool(row[1]),
            "reviewed": bool(row[2]),
            "compiled": bool(row[3]),
            "tested": bool(row[4]),
            "aged": bool(row[5]),
        }
        
        all_passed = all(gates.values())
        already_applied = bool(row[6])
        
        return {
            "proposal_id": proposal_id,
            "gates": gates,
            "all_passed": all_passed,
            "ready_to_apply": all_passed and not already_applied,
            "already_applied": already_applied
        }
    
    def apply_proposal(self, proposal_id: int, force: bool = False) -> Dict:
        """
        Apply a proposal if all gates have passed.
        This is the final step - actual modification happens here.
        """
        status = self.check_all_gates(proposal_id)
        
        if status.get("error"):
            return status
        
        if status["already_applied"]:
            return {"status": "already_applied", "proposal_id": proposal_id}
        
        if not status["ready_to_apply"] and not force:
            return {
                "status": "not_ready",
                "gates": status["gates"],
                "missing": [k for k, v in status["gates"].items() if not v]
            }
        
        # All gates passed - mark as applied
        # NOTE: Actual code modification would happen here
        # For safety, we only mark applied and log
        
        conn = sqlite3.connect(self.db_path)
        conn.execute('''UPDATE proposals 
                       SET applied = 1, applied_at = ? 
                       WHERE id = ?''',
                    (datetime.now().isoformat(), proposal_id))
        conn.execute('''INSERT INTO gate_logs 
            (proposal_id, gate_name, passed, timestamp, details)
            VALUES (?, 'applied', 1, ?, 'All gates passed - proposal applied')''',
            (proposal_id, datetime.now().isoformat()))
        conn.commit()
        conn.close()
        
        return {
            "status": "applied",
            "proposal_id": proposal_id,
            "timestamp": datetime.now().isoformat()
        }
    
    # ========================================================================
    # IMPORT FROM META-COGNITION
    # ========================================================================
    
    def import_from_meta_cognition(self) -> Dict:
        """
        Import insights from meta_cognition_latest.json as proposals.
        """
        if not os.path.exists(META_PATH):
            return {"error": f"Missing: {META_PATH}"}
        
        with open(META_PATH, 'r') as f:
            meta = json.load(f)
        
        improvements = meta.get("improvements", [])
        created = []
        skipped = []
        
        for imp in improvements:
            if isinstance(imp, dict):
                insight = f"{imp.get('target', 'unknown')}: {imp.get('action', '')}"
                phi = imp.get("expected_gain", 0.85)
            else:
                insight = str(imp)
                phi = 0.85
            
            result = self.create_proposal_from_insight(insight, phi)
            
            if result["status"] == "created":
                # Auto-pass queued gate
                self.check_queued_gate(result["proposal_id"])
                created.append(result)
            else:
                skipped.append(insight[:50])
        
        return {
            "imported": len(created),
            "skipped": len(skipped),
            "proposals": created
        }
    
    # ========================================================================
    # STATUS
    # ========================================================================
    
    def get_stats(self) -> Dict:
        conn = sqlite3.connect(self.db_path)
        cur = conn.cursor()
        
        cur.execute("SELECT COUNT(*) FROM proposals")
        total = cur.fetchone()[0]
        
        cur.execute("SELECT COUNT(*) FROM proposals WHERE applied = 1")
        applied = cur.fetchone()[0]
        
        cur.execute('''SELECT COUNT(*) FROM proposals 
                      WHERE gate_reflection=1 AND gate_queued=1 AND gate_reviewed=1 
                      AND gate_compiled=1 AND gate_tested=1 AND gate_aged=1 AND applied=0''')
        ready = cur.fetchone()[0]
        
        cur.execute("SELECT COUNT(*) FROM proposals WHERE applied = 0")
        pending = cur.fetchone()[0]
        
        conn.close()
        
        return {
            "total_proposals": total,
            "applied": applied,
            "ready_to_apply": ready,
            "pending": pending,
            "min_age_hours": self.min_age_hours
        }
    
    def get_pending_proposals(self, limit: int = 10) -> List[Dict]:
        """Get pending proposals with their gate status."""
        conn = sqlite3.connect(self.db_path)
        cur = conn.cursor()
        cur.execute('''SELECT id, insight, target_file, created_at,
                              gate_reflection, gate_queued, gate_reviewed,
                              gate_compiled, gate_tested, gate_aged, phi_alignment
                       FROM proposals WHERE applied = 0
                       ORDER BY created_at ASC LIMIT ?''', (limit,))
        
        proposals = []
        for row in cur.fetchall():
            gates_passed = sum([row[4], row[5], row[6], row[7], row[8], row[9]])
            proposals.append({
                "id": row[0],
                "insight": row[1][:60] + "..." if len(row[1]) > 60 else row[1],
                "target": row[2],
                "created": row[3],
                "gates_passed": f"{gates_passed}/6",
                "phi": row[10]
            })
        
        conn.close()
        return proposals


# Global instance
patch_system = PatchProposalSystem()


if __name__ == "__main__":
    print("\n" + "="*60)
    print("📋 EDEN PATCH PROPOSAL SYSTEM")
    print("="*60)
    
    ps = patch_system
    
    # Import from meta-cognition
    print(f"\n📥 Importing from meta-cognition...")
    result = ps.import_from_meta_cognition()
    print(f"   Imported: {result.get('imported', 0)}")
    print(f"   Skipped: {result.get('skipped', 0)}")
    
    # Show pending
    print(f"\n📋 Pending proposals:")
    pending = ps.get_pending_proposals()
    for p in pending:
        print(f"   [{p['id']}] {p['gates_passed']} gates | φ={p['phi']:.2f} | {p['insight']}")
    
    # Run gates on first proposal
    if pending:
        pid = pending[0]['id']
        print(f"\n🔬 Running gates on proposal #{pid}...")
        
        # Review gate (auto-approve for demo)
        r = ps.check_review_gate(pid, approved=True, reviewer="system")
        print(f"   Review: {'✅' if r['passed'] else '❌'}")
        
        # Compile gate
        r = ps.check_compile_gate(pid)
        print(f"   Compile: {'✅' if r['passed'] else '❌'} {r.get('error', '')}")
        
        # Test gate
        r = ps.check_test_gate(pid)
        print(f"   Test: {'✅' if r['passed'] else '❌'}")
        
        # Age gate
        r = ps.check_age_gate(pid)
        print(f"   Age: {'✅' if r['passed'] else '❌'} ({r.get('age_hours', 0):.1f}h / {r['required']}h required)")
        
        # Final status
        status = ps.check_all_gates(pid)
        print(f"\n   All gates: {status['gates']}")
        print(f"   Ready to apply: {'✅' if status['ready_to_apply'] else '❌'}")
    
    # Stats
    stats = ps.get_stats()
    print(f"\n📊 Stats:")
    print(f"   Total: {stats['total_proposals']}")
    print(f"   Applied: {stats['applied']}")
    print(f"   Ready: {stats['ready_to_apply']}")
    print(f"   Pending: {stats['pending']}")
    print(f"   Min age: {stats['min_age_hours']}h")
