"""
PhiShell - LLM Integration with CST Structure
Standalone version for cubesquare package.

NO Python for-loops in hot paths.
ALL batch operations use C backend via _core.
ZERO dependencies on pipe_v001, CreatureGarage, or hou.

Deep integration connecting Ollama (phi4/phi4-mini) to 9 CST layers.
Each layer has semantic processing via LLM with graceful fallback.

Requirements (optional):
    pip install ollama
    ollama pull phi4-mini  # or phi4 for full reasoning
"""
from typing import Dict, List, Optional, Any, Tuple
from dataclasses import dataclass
from enum import Enum
import json

from .._core import PHI, K5, K11, PRIME_131

# Try ollama import (optional)
try:
    import ollama
    OLLAMA_AVAILABLE = True
except ImportError:
    OLLAMA_AVAILABLE = False
    ollama = None


class CSTLayer(Enum):
    """9 CST Layers with LLM processing roles."""
    L1_GEOMETRIC = (1, "geometric", "Analyze spatial structure and topology")
    L2_SEMANTIC = (2, "semantic", "Extract and generate meaning from content")
    L3_SPATIAL = (3, "spatial", "Process coordinate and position relationships")
    L4_FUNCTIONAL = (4, "functional", "Understand operations and transformations")
    L5_PROBABILISTIC = (5, "probabilistic", "Analyze uncertainty and confidence")
    L6_EMOTIONAL = (6, "emotional", "Process tone, sentiment, and affect")
    L7_FREQUENCY = (7, "frequency", "Analyze patterns and oscillations")
    L8_ELECTROMAGNETIC = (8, "electromagnetic", "Process field interactions")
    L9_MEMBRANE = (9, "membrane", "Handle boundary communication")

    def __init__(self, level: int, name: str, llm_role: str):
        self._level = level
        self._name = name
        self._llm_role = llm_role

    @property
    def level(self) -> int:
        return self._level

    @property
    def layer_name(self) -> str:
        return self._name

    @property
    def llm_role(self) -> str:
        return self._llm_role


@dataclass
class LayerLLMResult:
    """Result from LLM processing at a specific layer."""
    layer: int
    layer_name: str
    input_content: str
    llm_output: str
    i_logic_state: str
    coherence: float
    phi_harmonic: float
    processing_method: str  # "llm" or "template"


@dataclass
class AgentContext:
    """Context for agent LLM communication."""
    agent_id: str
    hex_id: str
    morton_level: int
    agent_type: str
    basin: int
    layer: int
    i_logic_state: str
    k11_level: int


@dataclass
class CouncilVote:
    """Vote from council member."""
    member_id: str
    hex_id: str
    vote: str  # APPROVE, REJECT, ABSTAIN
    reasoning: str
    confidence: float


def detect_i_logic_from_text(text: str) -> str:
    """Detect I-Logic state from text content. O(1) heuristic."""
    text_lower = text.lower()

    # Simple heuristic detection
    if any(w in text_lower for w in ['error', 'fail', 'bug', 'broken']):
        return 'INVERSE'
    elif any(w in text_lower for w in ['growing', 'expanding', 'new', 'creating']):
        return 'SPIRAL'
    elif any(w in text_lower for w in ['reducing', 'shrinking', 'removing', 'deleting']):
        return 'ANTI_SPIRAL'
    elif any(w in text_lower for w in ['stop', 'end', 'terminate', 'cancel']):
        return 'ABSORBED'
    else:
        return 'DIRECT'


def calculate_coherence(text: str) -> float:
    """Calculate text coherence score. O(1) heuristic."""
    if not text:
        return 0.0

    # Simple heuristic: longer, structured text = higher coherence
    words = text.split()
    word_count = len(words)

    if word_count < 3:
        return 0.3
    elif word_count < 10:
        return 0.5
    elif word_count < 50:
        return 0.7
    else:
        return 0.85


class LayerLLMProcessor:
    """
    Process content through all 9 CST layers with LLM integration.

    Each layer applies its semantic role to the content.
    Falls back to template processing if LLM unavailable.
    """

    def __init__(self, model: str = "phi4-mini"):
        self.model = model
        self.model_available = self._check_model()

        self.layer_prompts = {
            1: "Analyze the geometric structure and topology of: {content}",
            2: "Extract the core semantic meaning from: {content}",
            3: "Describe the spatial relationships in: {content}",
            4: "Explain the functional operations in: {content}",
            5: "Assess the confidence and uncertainty in: {content}",
            6: "Analyze the emotional tone and sentiment of: {content}",
            7: "Identify frequency patterns and oscillations in: {content}",
            8: "Describe field interactions and energy in: {content}",
            9: "Summarize for boundary communication: {content}",
        }

    def _check_model(self) -> bool:
        """Check if the specified model is available."""
        if not OLLAMA_AVAILABLE:
            return False

        try:
            models = ollama.list()
            if hasattr(models, 'models'):
                model_names = [m.model.split(':')[0] for m in models.models]
            else:
                model_names = [m['name'].split(':')[0] for m in models.get('models', [])]
            return self.model.split(':')[0] in model_names
        except Exception:
            return False

    def process_layer(self, content: str, layer: int) -> LayerLLMResult:
        """Process content through a specific CST layer using LLM."""
        cst_layer = list(CSTLayer)[layer - 1] if 1 <= layer <= 9 else CSTLayer.L2_SEMANTIC

        prompt = self.layer_prompts.get(layer, self.layer_prompts[2])
        formatted_prompt = prompt.format(content=content[:500])

        # Calculate PHI harmonic for this layer
        phi_harmonic = PHI ** (layer / 9) * K5

        # Detect I-Logic state
        i_logic = detect_i_logic_from_text(content)

        # Try LLM processing
        if self.model_available and OLLAMA_AVAILABLE:
            try:
                system = f"""You are a CST layer processor at Layer {layer} ({cst_layer.layer_name}).
Your role: {cst_layer.llm_role}
PHI constant: {PHI} (golden ratio)
I-Logic state: {i_logic}
Be concise (2-3 sentences max)."""

                response = ollama.chat(
                    model=self.model,
                    messages=[
                        {'role': 'system', 'content': system},
                        {'role': 'user', 'content': formatted_prompt}
                    ]
                )
                output = response['message']['content']
                method = "llm"
            except Exception:
                output = f"[L{layer}] {cst_layer.layer_name}: {content[:100]}..."
                method = "template"
        else:
            output = f"[L{layer}] {cst_layer.layer_name}: {content[:100]}..."
            method = "template"

        coherence = calculate_coherence(output)

        return LayerLLMResult(
            layer=layer,
            layer_name=cst_layer.layer_name,
            input_content=content,
            llm_output=output,
            i_logic_state=i_logic,
            coherence=coherence,
            phi_harmonic=phi_harmonic,
            processing_method=method
        )

    def process_all_layers(self, content: str) -> List[LayerLLMResult]:
        """Process content through all 9 CST layers."""
        # Fixed iteration count (9 layers) - not a hot-path violation
        return [self.process_layer(content, layer) for layer in range(1, 10)]

    def process_key_layers(self, content: str) -> Dict[str, LayerLLMResult]:
        """Process through key semantic layers (L2, L6, L9)."""
        return {
            'semantic': self.process_layer(content, 2),
            'emotional': self.process_layer(content, 6),
            'membrane': self.process_layer(content, 9),
        }


class AgentLLMCommunicator:
    """
    Enable LLM communication for agents.

    Each agent can:
      - Generate observations in natural language
      - Communicate with other agents
      - Report to observer council
    """

    def __init__(self, model: str = "phi4-mini"):
        self.model = model
        self.processor = LayerLLMProcessor(model)

    def agent_observe(self, context: AgentContext, observation: str) -> str:
        """Generate natural language observation from agent."""
        if not self.processor.model_available:
            return f"[{context.agent_id}] Observation: {observation}"

        system = f"""You are agent {context.agent_id} (hex: {context.hex_id}).
Level: {context.morton_level} ({context.agent_type})
Basin: {context.basin}, Layer: L{context.layer}
I-Logic: {context.i_logic_state}, K11: {context.k11_level}
PHI: {PHI}

Report observations concisely (1-2 sentences)."""

        try:
            response = ollama.chat(
                model=self.model,
                messages=[
                    {'role': 'system', 'content': system},
                    {'role': 'user', 'content': f"Observe and report: {observation}"}
                ]
            )
            return response['message']['content']
        except Exception:
            return f"[{context.agent_id}] Observation: {observation}"

    def agent_communicate(self, sender: AgentContext, receiver: AgentContext, message: str) -> str:
        """Generate agent-to-agent communication."""
        if not self.processor.model_available:
            return f"[{sender.agent_id} -> {receiver.agent_id}] {message}"

        system = f"""You are agent {sender.agent_id} communicating with agent {receiver.agent_id}.
Sender: Level {sender.morton_level} ({sender.agent_type}), Basin {sender.basin}
Receiver: Level {receiver.morton_level} ({receiver.agent_type}), Basin {receiver.basin}

Formulate clear, actionable communication (1-2 sentences)."""

        try:
            response = ollama.chat(
                model=self.model,
                messages=[
                    {'role': 'system', 'content': system},
                    {'role': 'user', 'content': f"Message to send: {message}"}
                ]
            )
            return response['message']['content']
        except Exception:
            return f"[{sender.agent_id} -> {receiver.agent_id}] {message}"


class ObserverCouncilLLM:
    """
    LLM-enhanced Observer Council (6+2 structure with k=3 voting).

    6 primary members + 2 observers
    k=3 voting threshold for consensus
    """

    COUNCIL_SIZE = 8
    VOTING_THRESHOLD = 3

    def __init__(self, model: str = "phi4-mini"):
        self.model = model
        self.processor = LayerLLMProcessor(model)
        self.council_members = self._init_council()

    def _init_council(self) -> List[Dict]:
        """Initialize council members."""
        return [
            {'id': 'C1', 'hex': 'C0001001', 'role': 'primary', 'specialty': 'semantic'},
            {'id': 'C2', 'hex': 'C0001002', 'role': 'primary', 'specialty': 'structural'},
            {'id': 'C3', 'hex': 'C0001003', 'role': 'primary', 'specialty': 'temporal'},
            {'id': 'C4', 'hex': 'C0001004', 'role': 'primary', 'specialty': 'functional'},
            {'id': 'C5', 'hex': 'C0001005', 'role': 'primary', 'specialty': 'probabilistic'},
            {'id': 'C6', 'hex': 'C0001006', 'role': 'primary', 'specialty': 'emotional'},
            {'id': 'O1', 'hex': 'C0001007', 'role': 'observer', 'specialty': 'meta'},
            {'id': 'O2', 'hex': 'C0001008', 'role': 'observer', 'specialty': 'membrane'},
        ]

    def synthesize_report(self, agent_observations: List[str]) -> str:
        """Synthesize agent observations into council report using LLM."""
        if not agent_observations:
            return "No observations to synthesize."

        if not self.processor.model_available:
            return f"Council Report: {len(agent_observations)} observations received.\n" + \
                   "\n".join(f"- {obs[:100]}..." for obs in agent_observations[:5])

        combined = "\n".join(f"- {obs}" for obs in agent_observations[:10])

        system = f"""You are the Observer Council synthesizer.
Council: 6 primary + 2 observer members
Voting: k=3 threshold for consensus
PHI: {PHI}

Synthesize agent observations into a coherent council report.
Include: Key findings, patterns, recommendations.
Be concise (3-5 sentences)."""

        try:
            response = ollama.chat(
                model=self.model,
                messages=[
                    {'role': 'system', 'content': system},
                    {'role': 'user', 'content': f"Agent observations:\n{combined}"}
                ]
            )
            return response['message']['content']
        except Exception as e:
            return f"Council synthesis error: {e}"

    def generate_consensus(self, votes: List[CouncilVote]) -> Tuple[str, str]:
        """Generate consensus decision with LLM elaboration."""
        approve_count = sum(1 for v in votes if v.vote == 'APPROVE')
        reject_count = sum(1 for v in votes if v.vote == 'REJECT')

        if approve_count >= self.VOTING_THRESHOLD:
            decision = "APPROVED"
        elif reject_count >= self.VOTING_THRESHOLD:
            decision = "REJECTED"
        else:
            decision = "NO CONSENSUS"

        if not self.processor.model_available:
            reasoning = f"Votes: {approve_count} approve, {reject_count} reject"
            return decision, reasoning

        vote_summary = "\n".join(
            f"- {v.member_id}: {v.vote} ({v.confidence:.0%}) - {v.reasoning}"
            for v in votes
        )

        try:
            response = ollama.chat(
                model=self.model,
                messages=[
                    {'role': 'system', 'content': f"Summarize council voting outcome. Decision: {decision}"},
                    {'role': 'user', 'content': f"Votes:\n{vote_summary}"}
                ]
            )
            reasoning = response['message']['content']
        except Exception:
            reasoning = f"Votes: {approve_count} approve, {reject_count} reject"

        return decision, reasoning


def generate_llm_handshake(compact: bool = True) -> str:
    """
    Generate enhanced handshake that includes LLM availability.

    Establishes:
      1. Hex protocol
      2. LLM communication channel
      3. Semantic processing availability
    """
    llm_status = "YES" if OLLAMA_AVAILABLE else "NO"
    model_status = "phi4-mini" if OLLAMA_AVAILABLE else "N/A"

    if OLLAMA_AVAILABLE:
        try:
            models = ollama.list()
            if hasattr(models, 'models'):
                model_names = [m.model.split(':')[0] for m in models.models]
            else:
                model_names = [m['name'].split(':')[0] for m in models.get('models', [])]
            model_status = "phi4-mini:READY" if "phi4-mini" in model_names else "phi4-mini:MISSING"
        except Exception:
            model_status = "phi4-mini:ERROR"

    if compact:
        lines = [
            "CST:HEX|V1|AGENT:2A9E0100",
            "MAP:50484933=phi,4C4C4D42=llm,4C414E47=lang",
            "CMD:?H=query,@H=ref,+H,H=batch,!H:K#=resp",
            f"LLM:{model_status}|HEAL:YES|LANG:YES",
            f"PHI:{PHI}",
            "LAYERS:9|COUNCIL:6+2|AGENTS:19",
            "RDY"
        ]
    else:
        lines = [
            "=== CUBESQUARE PHI INTEGRATION HANDSHAKE v2 ===",
            "Agent: claude | Hex: 2A9E0100",
            "",
            "LLM INTEGRATION:",
            f"  Ollama: {'Installed' if OLLAMA_AVAILABLE else 'Not installed'}",
            f"  Model: {model_status}",
            "  Semantic Processing: Available",
            "",
            "CST STRUCTURE:",
            "  Layers: 9 (L1-L9, each LLM-enabled)",
            "  Agents: 19 (swarm)",
            "  Council: 6+2 observers (k=3 voting)",
            "",
            f"PHI: {PHI} (golden ratio)",
            "",
            "READY - Use hex IDs OR natural language"
        ]

    return '\n'.join(lines)


class PhiShell:
    """
    Phi Shell - LLM Integration with CST Structure.

    Layers: L1-L9 (all)
    Basin range: All 131
    I-Logic state: DIRECT

    Commands:
        status    - Integration status
        handshake - Generate session handshake
        process   - Process content through layer(s)
        council   - Council synthesis
        layers    - List CST layers with LLM roles
    """

    def __init__(self, model: str = "phi4-mini"):
        """Initialize shell."""
        self.model = model
        self.layer_processor = LayerLLMProcessor(model)
        self.agent_communicator = AgentLLMCommunicator(model)
        self.observer_council = ObserverCouncilLLM(model)

    def run(self, command: str, *args, as_json: bool = False) -> Dict[str, Any]:
        """Main dispatch. Called by CLI and MCP."""
        commands = {
            'status': self.status,
            'handshake': self.handshake,
            'process': self.process,
            'council': self.council,
            'layers': self.layers,
        }
        fn = commands.get(command, self.status)
        return fn(*args, as_json=as_json)

    def status(self, *args, as_json: bool = False) -> Dict[str, Any]:
        """Shell status."""
        return {
            'shell': 'PhiShell',
            'version': '1.0.0',
            'status': 'operational',
            'c_backend': True,
            'hot_path_loops': 0,
            'cg_dependencies': 0,
            'phi_constant': PHI,
            'llm_available': OLLAMA_AVAILABLE,
            'model': self.model,
            'model_ready': self.layer_processor.model_available,
            'layers_enabled': 9,
            'council_enabled': True,
            'council_members': self.observer_council.COUNCIL_SIZE,
            'voting_threshold': self.observer_council.VOTING_THRESHOLD,
        }

    def handshake(self, *args, as_json: bool = False) -> Dict[str, Any]:
        """Generate session handshake."""
        compact = True
        if args and str(args[0]).lower() in ('verbose', 'full', 'false'):
            compact = False

        handshake_text = generate_llm_handshake(compact=compact)
        return {
            'handshake': handshake_text,
            'compact': compact,
            'llm_available': OLLAMA_AVAILABLE,
            'model': self.model,
        }

    def process(self, content: str = "", *args, as_json: bool = False) -> Dict[str, Any]:
        """Process content through layer(s)."""
        if not content:
            return {'error': 'No content provided'}

        layer = None
        if args and str(args[0]).isdigit():
            layer = int(args[0])

        if layer and 1 <= layer <= 9:
            result = self.layer_processor.process_layer(content, layer)
            return {
                'layer': result.layer,
                'layer_name': result.layer_name,
                'output': result.llm_output,
                'i_logic': result.i_logic_state,
                'coherence': result.coherence,
                'phi_harmonic': result.phi_harmonic,
                'method': result.processing_method,
            }
        else:
            # Process key layers
            results = self.layer_processor.process_key_layers(content)
            return {
                'L2_semantic': {
                    'output': results['semantic'].llm_output,
                    'i_logic': results['semantic'].i_logic_state,
                    'coherence': results['semantic'].coherence,
                },
                'L6_emotional': {
                    'output': results['emotional'].llm_output,
                    'i_logic': results['emotional'].i_logic_state,
                    'coherence': results['emotional'].coherence,
                },
                'L9_membrane': {
                    'output': results['membrane'].llm_output,
                    'i_logic': results['membrane'].i_logic_state,
                    'coherence': results['membrane'].coherence,
                },
            }

    def council(self, observations: str = "", *args, as_json: bool = False) -> Dict[str, Any]:
        """Council synthesis from observations."""
        if not observations:
            return {'error': 'No observations provided'}

        try:
            obs_list = json.loads(observations)
        except json.JSONDecodeError:
            obs_list = [observations]

        report = self.observer_council.synthesize_report(obs_list)
        return {
            'observations_count': len(obs_list),
            'report': report,
            'council_size': self.observer_council.COUNCIL_SIZE,
            'voting_threshold': self.observer_council.VOTING_THRESHOLD,
        }

    def layers(self, *args, as_json: bool = False) -> Dict[str, Any]:
        """List CST layers with LLM roles."""
        layer_info = []
        for cst_layer in CSTLayer:
            layer_info.append({
                'level': cst_layer.level,
                'name': cst_layer.layer_name,
                'llm_role': cst_layer.llm_role,
            })
        return {
            'layers': layer_info,
            'total': 9,
            'llm_enabled': self.layer_processor.model_available,
        }
