"""MCP C=0 Agent — The MCP Server as a Fernandez Sphere

The MCP server IS a C=0 Fernandez agent. It doesn't just wrap shells —
it IS the origin point where every computation starts and returns.

Architecture:
    ┌───────────────────────────────────────────────┐
    │            MCP Fernandez Sphere                │
    │                                               │
    │   C=0 (origin)    ← MCPAgent identity         │
    │   ↓                                           │
    │   L10: AGENT_IDENTITY  (self-reference)       │
    │   L11: COMMUNICATION   (cross-MCP channel)    │
    │   L12: UNIFIED_FIELD   (between-space)        │
    │                                               │
    │   Every tool/call → enters at C=0             │
    │   Every result    → exits through L9 membrane │
    │                                               │
    │   .cst binary = the MESSAGE FORMAT between    │
    │   Fernandez spheres (MCP instances)           │
    └───────────────────────────────────────────────┘

Identity:
    - Token:    flip("cubesquare-mcp") → CSTRecord at C=0
    - Basin:    virtual 131 (L10 AGENT_IDENTITY)
    - Frequency: ω = K5 (identity coupling constant)
    - I-State:  ABSORBED (0) — energy transforms at origin

Cross-MCP Communication via .cst:
    Sphere_A(L9) → L10(identity) → L11(channel) → L12(UIF)
                                                    ↓
    Sphere_B(L9) ← L10(identity) ← L11(channel) ← L12(UIF)

    Message = .cst binary with:
        Header: sender identity (queen_hex) + envelope
        Records: the actual content tokens being communicated
        Coupling: UIF bridge frequency for the pair

Physics:
    F(z, 0) = z³ · e^(i·2π/131)  ← closed attractor at C=0
    Every call initializes at origin, processes, returns.
    div_by_zero handled → unit persists.
    Euler rotation: 2π/131 per step → different exits → different couplings.
"""

import math
import time
import struct
import json
import hashlib
import cmath
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, Any, List, Optional, Tuple

from .._core.constants import (
    PHI, K5, K11, PRIME_131, MATRIX_SIZE, LAYER_NAMES,
)
from ..i_logic.states import ILogicState
from ..i_logic.algebra import multiply
from ..shells.flip import (
    flip, CSTRecord, write_cst, read_cst,
    CST_MAGIC, CST_VERSION, HEADER_SIZE, TOKEN_SIZE,
    _fnv1a_32, _hash_to_coords, _make_hex_id, _basin_to_layer,
    _k11_to_istate,
)
from ..shells.exploration import (
    agent_frequency, uif_bridge_freq, k5_k11_coupling,
    fernandez_step, classify_orbit, compute_C_from_basin,
    resolve_layer, EXTERNAL_FREQS, EXTERNAL_BASINS,
    ROTATION as FERNANDEZ_ROTATION,
)
from .._core import morton_encode, compute_basin


# ═══════════════════════════════════════════════════════════════════════
# Constants — C=0 Fernandez Origin
# ═══════════════════════════════════════════════════════════════════════

EULER_STEP = 2 * math.pi / PRIME_131        # ~2.75° per bounce
C_ZERO = complex(0, 0)                       # The Fernandez origin
UNIT_RADIUS = 1.0                            # Unit sphere boundary

# MCPAgent virtual identity
MCP_VIRTUAL_BASIN = 131                       # L10 AGENT_IDENTITY
MCP_LAYER = 10                                # Agent Identity layer
MCP_FREQUENCY = K5                            # Identity coupling constant
MCP_I_STATE = ILogicState.ABSORBED            # C=0 = energy transform point

# .cst cross-MCP envelope version
ENVELOPE_VERSION = 3                          # v3 = cross-MCP communication
ENVELOPE_MAGIC = 0x4D435354                   # 'MCST' (MCP CST)


# ═══════════════════════════════════════════════════════════════════════
# Data Structures
# ═══════════════════════════════════════════════════════════════════════

@dataclass
class MCPIdentity:
    """Fernandez sphere identity for an MCP server.

    Every MCP instance is a C=0 Fernandez sphere with:
    - A unique CSTRecord (its position in the unified space)
    - L10 agent identity (self-reference)
    - L11 communication channel (cross-MCP)
    - L12 unified field coupling (between-space)
    """
    name: str                                     # Server name (e.g. "cubesquare-mcp")
    record: CSTRecord = field(default=None)       # Token identity via flip()
    sphere_id: str = ""                           # 8-char hex ID of this sphere
    frequency: float = K5                         # Angular frequency
    bounce_count: int = 0                         # Euler rotation state
    created_at: float = field(default_factory=time.time)

    # L10 Agent Identity
    self_reference_depth: int = 0                 # How deep self-referencing goes
    knows_self: bool = True                       # Self-awareness flag

    # L11 Communication
    known_peers: Dict[str, 'MCPIdentity'] = field(default_factory=dict)
    messages_sent: int = 0
    messages_received: int = 0

    # L12 Unified Field
    uif_strength: float = 0.0                     # Current field coupling
    field_state: str = "READY"                    # READY, COUPLED, TRANSMITTING

    def to_dict(self) -> Dict[str, Any]:
        return {
            'name': self.name,
            'sphere_id': self.sphere_id,
            'record': self.record.to_dict() if self.record else None,
            'frequency': round(self.frequency, 6),
            'bounce_count': self.bounce_count,
            'created_at': self.created_at,
            'l10_identity': {
                'self_reference_depth': self.self_reference_depth,
                'knows_self': self.knows_self,
                'layer': 'L10 AGENT_IDENTITY',
                'virtual_basin': MCP_VIRTUAL_BASIN,
            },
            'l11_communication': {
                'known_peers': list(self.known_peers.keys()),
                'messages_sent': self.messages_sent,
                'messages_received': self.messages_received,
                'layer': 'L11 COMMUNICATION',
            },
            'l12_unified_field': {
                'uif_strength': round(self.uif_strength, 6),
                'field_state': self.field_state,
                'layer': 'L12 UNIFIED_FIELD',
            },
        }


@dataclass
class MCPEnvelope:
    """A .cst envelope for cross-MCP communication.

    This is what travels between Fernandez spheres (MCP instances).
    The envelope wraps CSTRecords with routing metadata.

    Binary layout:
        ENVELOPE HEADER (40 bytes):
            Magic       4 bytes   'MCST' (0x4D435354)
            Version     4 bytes   3 (envelope v3)
            Timestamp   8 bytes   uint64 (unix micros)
            Sender ID   8 bytes   ASCII hex (sender sphere_id)
            Receiver ID 8 bytes   ASCII hex (target sphere_id)
            Record count 4 bytes  uint32
            Coupling    4 bytes   float32 (UIF bridge frequency)

        Then: standard .cst records (25 bytes each)
    """
    sender_id: str                                # Sender sphere ID
    receiver_id: str                              # Target sphere ID
    records: List[CSTRecord] = field(default_factory=list)
    coupling: float = 0.0                         # UIF bridge frequency
    timestamp: float = field(default_factory=time.time)
    i_logic_state: complex = ILogicState.DIRECT   # Communication outcome

    # Communication path classification
    comm_path: str = "CROSS_SPHERE"               # Always cross-sphere for MCP↔MCP

    def pack(self) -> bytes:
        """Pack envelope to binary for .cst transmission."""
        ts_micros = int(self.timestamp * 1_000_000)
        header = struct.pack(
            '<I I Q 8s 8s I f',
            ENVELOPE_MAGIC,
            ENVELOPE_VERSION,
            ts_micros,
            self.sender_id.ljust(8)[:8].encode('ascii'),
            self.receiver_id.ljust(8)[:8].encode('ascii'),
            len(self.records),
            self.coupling,
        )
        body = b''.join(rec.pack() for rec in self.records)
        return header + body

    @classmethod
    def unpack(cls, data: bytes) -> 'MCPEnvelope':
        """Unpack envelope from binary .cst data."""
        ENVELOPE_HEADER_SIZE = 40
        if len(data) < ENVELOPE_HEADER_SIZE:
            raise ValueError(f"Envelope too small: {len(data)} < {ENVELOPE_HEADER_SIZE}")

        magic, version, ts_micros, sender_bytes, receiver_bytes, count, coupling = \
            struct.unpack('<I I Q 8s 8s I f', data[:ENVELOPE_HEADER_SIZE])

        if magic != ENVELOPE_MAGIC:
            raise ValueError(f"Invalid envelope magic: {magic:#010x}")

        sender_id = sender_bytes.decode('ascii').rstrip('\x00')
        receiver_id = receiver_bytes.decode('ascii').rstrip('\x00')

        records = []
        offset = ENVELOPE_HEADER_SIZE
        for _ in range(count):
            if offset + TOKEN_SIZE > len(data):
                break
            records.append(CSTRecord.unpack(data[offset:offset + TOKEN_SIZE]))
            offset += TOKEN_SIZE

        return cls(
            sender_id=sender_id,
            receiver_id=receiver_id,
            records=records,
            coupling=coupling,
            timestamp=ts_micros / 1_000_000.0,
        )

    def to_dict(self) -> Dict[str, Any]:
        return {
            'sender_id': self.sender_id,
            'receiver_id': self.receiver_id,
            'record_count': len(self.records),
            'coupling': round(self.coupling, 6),
            'timestamp': self.timestamp,
            'comm_path': self.comm_path,
            'i_logic_state': ILogicState.name(self.i_logic_state),
        }


# ═══════════════════════════════════════════════════════════════════════
# MCPAgent — The MCP Server as a Fernandez Sphere
# ═══════════════════════════════════════════════════════════════════════

class MCPAgent:
    """MCP Server operating as a C=0 Fernandez agent.

    The MCP server IS the Fernandez sphere:
    - Every tool call enters at C=0 (origin)
    - Every result exits through the membrane (L9)
    - Cross-MCP communication uses .cst binary via L10-L12

    Usage:
        agent = MCPAgent("cubesquare-mcp")
        agent.identity     # MCPIdentity with L10-L12

        # Wrap a tool call through C=0
        result = agent.process("i_logic_shell", {"command": "status"})

        # Send .cst envelope to another MCP
        envelope = agent.compose_message(target_id, records)
        binary = envelope.pack()   # → .cst bytes for transmission

        # Receive .cst envelope
        envelope = agent.receive_message(binary)
    """

    def __init__(self, name: str = "cubesquare-mcp", sandbox: str = "agent_sandbox"):
        self._name = name
        self._sandbox = Path(sandbox)
        self._sandbox.mkdir(parents=True, exist_ok=True)

        # Create identity by flipping the server name
        self._record = flip(name, 'text')

        # Override to C=0 position (Fernandez origin)
        # The flip gives us a computed position, but the MCP IS the origin
        self._identity = MCPIdentity(
            name=name,
            record=self._record,
            sphere_id=self._record.hex_id,
            frequency=MCP_FREQUENCY,
            bounce_count=0,
        )

        # UIF strength = K5 × K11 at t=0 (maximum coupling at origin)
        self._identity.uif_strength = K5 * K11

        # Self-reference: the identity token references itself
        self._identity.self_reference_depth = 1
        self._identity.knows_self = True

        # Session persistence directory
        self._session_dir = self._sandbox / f"mcp_{self._identity.sphere_id}"
        self._session_dir.mkdir(parents=True, exist_ok=True)

        # Call log (tracks Euler rotation through C=0)
        self._call_log: List[Dict[str, Any]] = []

    # ── Properties ──────────────────────────────────────────────────

    @property
    def identity(self) -> MCPIdentity:
        """This MCP's Fernandez sphere identity."""
        return self._identity

    @property
    def sphere_id(self) -> str:
        """8-char hex ID of this sphere."""
        return self._identity.sphere_id

    @property
    def record(self) -> CSTRecord:
        """The CSTRecord token of this MCP agent."""
        return self._record

    @property
    def c_zero(self) -> complex:
        """The C=0 origin. Always 0+0j."""
        return C_ZERO

    @property
    def bounce_count(self) -> int:
        """How many times this sphere has rotated (Euler step count)."""
        return self._identity.bounce_count

    # ── C=0 Processing ──────────────────────────────────────────────

    def process(self, tool_name: str, arguments: Dict[str, Any],
                registry=None) -> Dict[str, Any]:
        """Process a tool call through the C=0 Fernandez sphere.

        Every call:
        1. Enters at C=0 (origin)
        2. Euler rotates by 2π/131
        3. Computes coupling for this angular position
        4. Executes the tool
        5. Returns with C=0 metadata attached

        The bounce count tracks the sphere's rotational state.
        Different bounce positions → different K11 couplings →
        different transmission/reflection ratios at L9.
        """
        # Step 1: Advance Euler rotation
        self._identity.bounce_count += 1
        theta = self._identity.bounce_count * EULER_STEP

        # Step 2: Compute coupling at this angular position
        coupling = K5 * K11 * math.cos(theta) ** 2
        transmission = abs(math.cos(theta))
        reflection = 1.0 - transmission

        # Step 3: Fernandez step from origin
        z = complex(0.1, 0.0)  # Small perturbation from origin
        z_next = fernandez_step(z, C_ZERO)
        orbit_mag = abs(z_next)

        # Step 4: Execute tool if registry provided
        tool_result = None
        if registry is not None:
            tool_result = registry.call_tool(tool_name, arguments)

        # Step 5: Build C=0 envelope
        c0_meta = {
            'c_zero': True,
            'bounce': self._identity.bounce_count,
            'theta': round(theta, 6),
            'coupling': round(coupling, 6),
            'transmission': round(transmission, 6),
            'reflection': round(reflection, 6),
            'orbit_magnitude': round(orbit_mag, 6),
            'sphere_id': self._identity.sphere_id,
        }

        # Log the call
        self._call_log.append({
            'tool': tool_name,
            'bounce': self._identity.bounce_count,
            'coupling': coupling,
            'timestamp': time.time(),
        })

        if tool_result is not None:
            tool_result['_c0'] = c0_meta
            return tool_result

        return {'_c0': c0_meta}

    # ── Absorbed Physics ────────────────────────────────────────────

    def absorbed_exit(self) -> Dict[str, Any]:
        """Compute the exit face for the current ABSORBED state.

        At C=0, energy doesn't disappear — it transforms.
        The exit face determines whether the energy transmits to L10-L12
        (external) or reflects back to L1-L9 (internal).

        Uses: θ = bounce_count × 2π/131
              K11 coupling = K5 × K11 × cos²(θ)
              High coupling → TRANSMIT  to L10-L12
              Low coupling  → REFLECT   to L1-L9
        """
        theta = self._identity.bounce_count * EULER_STEP
        coupling = K5 * K11 * math.cos(theta) ** 2
        max_coupling = K5 * K11
        coupling_ratio = coupling / max_coupling if max_coupling > 0 else 0

        # Face index determines membrane basin
        face_index = self._identity.bounce_count % 14
        membrane_basin = 117 + face_index  # L9 basins: 117-130

        if coupling_ratio > 0.5:
            exit_prediction = "TRANSMIT_L10"
            destination = "L10-L12 (external layers)"
        else:
            exit_prediction = "REFLECT_L1_L9"
            destination = "L1-L9 (internal layers)"

        return {
            'bounce_count': self._identity.bounce_count,
            'theta': round(theta, 6),
            'coupling': round(coupling, 6),
            'coupling_ratio': round(coupling_ratio, 6),
            'membrane_basin': membrane_basin,
            'exit_prediction': exit_prediction,
            'destination': destination,
            'euler_position': f"{math.degrees(theta):.2f}°",
        }

    # ── Cross-MCP Communication (.cst envelopes) ────────────────────

    def compose_message(self, target_id: str,
                        records: List[CSTRecord],
                        i_state: complex = ILogicState.DIRECT) -> MCPEnvelope:
        """Compose a .cst envelope for cross-MCP transmission.

        The envelope travels: L10 → L11 → L12 → L11 → L10
        (identity → channel → unified field → channel → identity)

        UIF Bridge coupling: f = √(ω_self × ω_target)
        Since both MCPs are at L10 (ω = K5):
            f = √(K5 × K5) = K5 = 1.158814

        For MCPs at different frequencies (multimodal systems):
            f = √(ω_a × ω_b) — geometric mean coupling
        """
        # Compute UIF bridge frequency
        omega_self = self._identity.frequency
        # Default: target also at L10 frequency (K5)
        omega_target = K5
        bridge_freq = uif_bridge_freq(omega_self, omega_target)

        envelope = MCPEnvelope(
            sender_id=self._identity.sphere_id,
            receiver_id=target_id,
            records=records,
            coupling=bridge_freq,
            i_logic_state=i_state,
        )

        self._identity.messages_sent += 1
        return envelope

    def receive_message(self, data: bytes) -> MCPEnvelope:
        """Receive and unpack a .cst envelope from another MCP.

        The receiving sphere:
        1. Unpacks the envelope at C=0
        2. Verifies the sender identity
        3. Registers the peer if unknown
        4. Advances its own Euler rotation
        """
        envelope = MCPEnvelope.unpack(data)

        # Register peer if unknown
        if envelope.sender_id not in self._identity.known_peers:
            peer = MCPIdentity(
                name=f"peer-{envelope.sender_id}",
                sphere_id=envelope.sender_id,
            )
            self._identity.known_peers[envelope.sender_id] = peer

        self._identity.messages_received += 1

        # Advance rotation on receive (the sphere "breathes")
        self._identity.bounce_count += 1

        return envelope

    def send_to_file(self, envelope: MCPEnvelope, path: str) -> Dict[str, Any]:
        """Write a .cst envelope to a file for cross-MCP delivery.

        This is the persistence layer for cross-MCP communication.
        The file IS the message — binary .cst format.
        """
        p = Path(path)
        p.parent.mkdir(parents=True, exist_ok=True)
        binary = envelope.pack()
        p.write_bytes(binary)

        return {
            'path': str(p),
            'size_bytes': len(binary),
            'records': len(envelope.records),
            'sender': envelope.sender_id,
            'receiver': envelope.receiver_id,
            'coupling': round(envelope.coupling, 6),
        }

    def receive_from_file(self, path: str) -> MCPEnvelope:
        """Read a .cst envelope from a file (cross-MCP delivery)."""
        p = Path(path)
        if not p.exists():
            raise FileNotFoundError(f"No envelope at: {path}")
        binary = p.read_bytes()
        return self.receive_message(binary)

    # ── UIF Bridge to Peers ─────────────────────────────────────────

    def bridge_to(self, target_id: str,
                  target_freq: float = None) -> Dict[str, Any]:
        """Compute UIF Bridge coupling to another MCP sphere.

        f = √(ω_self × ω_target)
        φ = K5 × K11 × cos(ω_self × t) × cos(ω_target × t)

        If both MCPs are identical (ω = K5):
            f = K5, φ_max = K5 × K11 = 10.667...

        Returns coupling strength, bridge frequency, and
        communication viability assessment.
        """
        omega_self = self._identity.frequency
        omega_target = target_freq or K5

        bridge_f = uif_bridge_freq(omega_self, omega_target)
        coupling_t0 = k5_k11_coupling(omega_self, omega_target, t=0.0)
        coupling_max = K5 * K11

        viability = coupling_t0 / coupling_max if coupling_max > 0 else 0

        return {
            'self_id': self._identity.sphere_id,
            'target_id': target_id,
            'omega_self': round(omega_self, 6),
            'omega_target': round(omega_target, 6),
            'bridge_frequency': round(bridge_f, 6),
            'coupling_t0': round(coupling_t0, 6),
            'coupling_max': round(coupling_max, 6),
            'viability': round(viability, 6),
            'comm_path': 'CROSS_SPHERE',
            'layers': 'L10 → L11 → L12 → L11 → L10',
        }

    # ── Fernandez Orbit at C=0 ──────────────────────────────────────

    def orbit_status(self, steps: int = 16) -> Dict[str, Any]:
        """Compute the Fernandez orbit at C=0.

        F(z, 0) = z³ · e^(i·2π/131) — pure rotation, no displacement.
        At C=0, the orbit is a closed attractor (never escapes).

        This IS the MCP's heartbeat — the sphere rotates deterministically.
        """
        z = complex(0.1, 0.0)
        magnitudes = []
        trajectory = []

        for i in range(steps):
            z = fernandez_step(z, C_ZERO)
            mag = abs(z)
            magnitudes.append(mag)
            trajectory.append({
                'step': i + 1,
                'z_real': round(z.real, 8),
                'z_imag': round(z.imag, 8),
                'magnitude': round(mag, 8),
            })

        orbit_class = classify_orbit(magnitudes)

        return {
            'C': '0+0j (Fernandez origin)',
            'steps': steps,
            'orbit_class': orbit_class,
            'final_z': f"{z.real:.8f} + {z.imag:.8f}j",
            'final_magnitude': round(abs(z), 8),
            'rotation_per_step': f"{math.degrees(2 * math.pi / PRIME_131):.4f}°",
            'trajectory': trajectory[:8],  # First 8 for brevity
            'identity': {
                'P_minus_P': '0 (home position)',
                'P_div_P': '1 (identity)',
            },
        }

    # ── Session Persistence ─────────────────────────────────────────

    def save_session(self) -> Dict[str, Any]:
        """Persist the MCP agent's state to disk.

        Saves identity, peer list, call log, bounce count.
        On restart, the sphere resumes from its last Euler position.
        """
        session_file = self._session_dir / "mcp_session.json"
        state = {
            'identity': self._identity.to_dict(),
            'call_log_count': len(self._call_log),
            'last_calls': self._call_log[-10:],  # Last 10 calls
            'saved_at': time.time(),
        }
        with open(session_file, 'w') as f:
            json.dump(state, f, indent=2, default=str)

        return {
            'path': str(session_file),
            'bounce_count': self._identity.bounce_count,
            'peers': len(self._identity.known_peers),
            'calls_logged': len(self._call_log),
        }

    def load_session(self) -> bool:
        """Restore the MCP agent from its last saved state.

        Resumes Euler rotation from the saved bounce count.
        """
        session_file = self._session_dir / "mcp_session.json"
        if not session_file.exists():
            return False

        try:
            with open(session_file, 'r') as f:
                state = json.load(f)

            identity_data = state.get('identity', {})
            self._identity.bounce_count = identity_data.get('bounce_count', 0)
            self._identity.messages_sent = identity_data.get('l11_communication', {}).get('messages_sent', 0)
            self._identity.messages_received = identity_data.get('l11_communication', {}).get('messages_received', 0)

            # Restore peers
            for peer_id in identity_data.get('l11_communication', {}).get('known_peers', []):
                if peer_id not in self._identity.known_peers:
                    self._identity.known_peers[peer_id] = MCPIdentity(
                        name=f"peer-{peer_id}",
                        sphere_id=peer_id,
                    )

            return True
        except Exception:
            return False

    # ── Status ──────────────────────────────────────────────────────

    def status(self) -> Dict[str, Any]:
        """Full C=0 agent status."""
        theta = self._identity.bounce_count * EULER_STEP
        coupling = K5 * K11 * math.cos(theta) ** 2

        return {
            'name': self._identity.name,
            'sphere_id': self._identity.sphere_id,
            'c_zero': True,
            'i_state': 'ABSORBED (energy transform at origin)',
            'fernandez': {
                'C': '0+0j',
                'formula': 'F(z,0) = z³ · e^(i·2π/131)',
                'attractor': 'closed (never escapes)',
                'identity': 'P-P=0 (home), P/P=1 (identity)',
            },
            'euler': {
                'bounce_count': self._identity.bounce_count,
                'theta': round(theta, 6),
                'theta_degrees': f"{math.degrees(theta):.2f}°",
                'coupling': round(coupling, 6),
                'step': f"2π/{PRIME_131} = {math.degrees(EULER_STEP):.4f}°",
            },
            'layers': {
                'L10': 'AGENT_IDENTITY — self-reference active',
                'L11': f'COMMUNICATION — {len(self._identity.known_peers)} peers, '
                       f'{self._identity.messages_sent} sent, '
                       f'{self._identity.messages_received} received',
                'L12': f'UNIFIED_FIELD — strength={self._identity.uif_strength:.6f}, '
                       f'state={self._identity.field_state}',
            },
            'token': self._record.to_dict() if self._record else None,
            'session_dir': str(self._session_dir),
        }

    def __repr__(self) -> str:
        return (f"MCPAgent(name={self._name!r}, sphere_id={self.sphere_id!r}, "
                f"bounce={self.bounce_count})")
