"""
Text-Mapped Communication Shell

Provides bidirectional mapping between human-readable text and C=0 coordinates.

Usage:
    from cubesquare.shells.text_comm import TextComm

    # Create communicator
    comm = TextComm()

    # Send message (returns hex ID)
    hex_id = comm.send("ALPHA", "BETA", "Hello, how are you?")

    # Decode hex back to text
    text = comm.decode(hex_id)  # "ALPHA to BETA: Hello, how are you?"

    # Save conversation
    comm.save("data/my_conversation")

    # Load conversation
    comm.load("data/my_conversation")

    # Iterate messages
    for msg in comm.messages:
        print(f"{msg['sender']} -> {msg['receiver']}: {msg['content']}")
"""

import json
from pathlib import Path
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, asdict

from ..shells.flip import flip, flip_batch, write_cst, read_cst
from ..shells.content import hex_to_identity
from .._core.constants import K5, K11
import math


@dataclass
class Message:
    """A single message in the communication system."""
    hex_id: str
    sender: str
    receiver: str
    content: str
    index: int
    basin: int
    layer: int
    timestamp: float = 0.0

    def to_text(self) -> str:
        """Convert to human-readable text."""
        return f"{self.sender} to {self.receiver}: {self.content}"

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary."""
        return asdict(self)


class TextComm:
    """
    Text-Mapped Communication System.

    Provides bidirectional mapping:
    - Forward: text -> hex (deterministic via FNV-1a hash)
    - Reverse: hex -> text (stored lookup table)

    All communications exist as C=0 coordinates in the manifold.
    """

    def __init__(self):
        self.hex_to_msg: Dict[str, Message] = {}
        self.messages: List[Message] = []
        self._message_counter = 0

    def send(self, sender: str, receiver: str, content: str, timestamp: float = 0.0) -> str:
        """
        Send a message, storing text mapping.

        Args:
            sender: Sender identifier
            receiver: Receiver identifier
            content: Message content (human-readable text)
            timestamp: Optional timestamp

        Returns:
            hex_id: The coordinate address of the message
        """
        import time

        # Create unique message identifier for hashing
        msg_id = f"{sender}_TO_{receiver}_{self._message_counter}"
        self._message_counter += 1

        # Flip to coordinates
        rec = flip(msg_id, 'text')

        # Create message object
        msg = Message(
            hex_id=rec.hex_id,
            sender=sender,
            receiver=receiver,
            content=content,
            index=len(self.messages),
            basin=rec.basin,
            layer=rec.layer,
            timestamp=timestamp or time.time()
        )

        # Store in lookup tables
        self.hex_to_msg[rec.hex_id] = msg
        self.messages.append(msg)

        return rec.hex_id

    def receive(self, hex_id: str) -> Optional[Message]:
        """
        Receive a message by hex ID.

        Args:
            hex_id: The coordinate address

        Returns:
            Message object or None if not found
        """
        return self.hex_to_msg.get(hex_id)

    def decode(self, hex_id: str) -> str:
        """
        Decode hex ID to human-readable text.

        Args:
            hex_id: The coordinate address

        Returns:
            Human-readable string
        """
        msg = self.hex_to_msg.get(hex_id)
        if msg:
            return msg.to_text()
        return f"[Unknown: {hex_id}]"

    def get_coupling(self, hex_id_a: str, hex_id_b: str) -> float:
        """
        Compute UIF Bridge coupling between two messages.

        Args:
            hex_id_a: First message coordinate
            hex_id_b: Second message coordinate

        Returns:
            Coupling strength
        """
        msg_a = self.hex_to_msg.get(hex_id_a)
        msg_b = self.hex_to_msg.get(hex_id_b)

        if not msg_a or not msg_b:
            return 0.0

        freq_a = (msg_a.basin + 1) * K5
        freq_b = (msg_b.basin + 1) * K11 / 8
        import time
        t = time.time() % (2 * math.pi)

        return K5 * K11 * math.cos(freq_a * t) * math.cos(freq_b * t)

    def get_conversation(self, participant: str = None) -> List[Message]:
        """
        Get conversation, optionally filtered by participant.

        Args:
            participant: Filter by sender or receiver (optional)

        Returns:
            List of messages
        """
        if participant:
            return [m for m in self.messages
                    if m.sender == participant or m.receiver == participant]
        return self.messages

    def save(self, base_path: str) -> Dict[str, str]:
        """
        Save conversation to CST + JSON files.

        Args:
            base_path: Base path (without extension)

        Returns:
            Dict with paths to created files
        """
        base = Path(base_path)

        # Create records for CST
        records = []
        for msg in self.messages:
            msg_id = f"{msg.sender}_TO_{msg.receiver}_{msg.index}"
            rec = flip(msg_id, 'text')
            records.append(rec)

        # Save CST (coordinates)
        cst_path = base.with_suffix('.cst')
        if records:
            write_cst(records, cst_path)

        # Save JSON (text mapping)
        json_path = base.with_suffix('.json')
        text_map = {msg.hex_id: msg.to_dict() for msg in self.messages}
        json_path.write_text(json.dumps(text_map, indent=2), encoding='utf-8')

        return {
            'cst': str(cst_path),
            'json': str(json_path),
            'messages': len(self.messages)
        }

    def load(self, base_path: str) -> int:
        """
        Load conversation from CST + JSON files.

        Args:
            base_path: Base path (without extension)

        Returns:
            Number of messages loaded
        """
        base = Path(base_path)
        json_path = base.with_suffix('.json')

        if not json_path.exists():
            return 0

        # Load JSON mapping
        text_map = json.loads(json_path.read_text(encoding='utf-8'))

        # Reconstruct messages
        self.messages = []
        self.hex_to_msg = {}

        for hex_id, data in text_map.items():
            msg = Message(
                hex_id=data['hex_id'],
                sender=data['sender'],
                receiver=data['receiver'],
                content=data['content'],
                index=data['index'],
                basin=data['basin'],
                layer=data['layer'],
                timestamp=data.get('timestamp', 0.0)
            )
            self.hex_to_msg[hex_id] = msg
            self.messages.append(msg)

        # Sort by index
        self.messages.sort(key=lambda m: m.index)
        self._message_counter = len(self.messages)

        return len(self.messages)

    def print_conversation(self, participant: str = None):
        """Print conversation in human-readable format."""
        msgs = self.get_conversation(participant)
        for msg in msgs:
            print(f"[{msg.index}] {msg.sender} -> {msg.receiver}:")
            print(f"    \"{msg.content}\"")
            print(f"    (Hex: {msg.hex_id}, Basin: {msg.basin}, Layer: {msg.layer})")
            print()


# Shell interface
class TextCommShell:
    """Shell interface for text-mapped communication."""

    name = "text_comm"
    description = "Text-mapped communication with bidirectional hex/text conversion"

    def __init__(self):
        self.comm = TextComm()

    def execute(self, command: str, args: dict) -> dict:
        """Execute shell command."""
        cmd = args.get('command', command)

        if cmd == 'send':
            sender = args.get('sender', 'SYSTEM')
            receiver = args.get('receiver', 'USER')
            content = args.get('content', '')
            hex_id = self.comm.send(sender, receiver, content)
            return {
                'hex_id': hex_id,
                'decoded': self.comm.decode(hex_id)
            }

        elif cmd == 'decode':
            hex_id = args.get('hex_id', '')
            return {
                'text': self.comm.decode(hex_id)
            }

        elif cmd == 'list':
            participant = args.get('participant')
            msgs = self.comm.get_conversation(participant)
            return {
                'messages': [m.to_dict() for m in msgs],
                'count': len(msgs)
            }

        elif cmd == 'save':
            path = args.get('path', 'data/conversation')
            result = self.comm.save(path)
            return result

        elif cmd == 'load':
            path = args.get('path', 'data/conversation')
            count = self.comm.load(path)
            return {'loaded': count}

        elif cmd == 'coupling':
            hex_a = args.get('hex_a', '')
            hex_b = args.get('hex_b', '')
            coupling = self.comm.get_coupling(hex_a, hex_b)
            return {'coupling': coupling}

        else:
            return {
                'commands': ['send', 'decode', 'list', 'save', 'load', 'coupling'],
                'usage': {
                    'send': {'sender': 'str', 'receiver': 'str', 'content': 'str'},
                    'decode': {'hex_id': 'str'},
                    'list': {'participant': 'str (optional)'},
                    'save': {'path': 'str'},
                    'load': {'path': 'str'},
                    'coupling': {'hex_a': 'str', 'hex_b': 'str'}
                }
            }


# Convenience functions
def send(sender: str, receiver: str, content: str) -> str:
    """Quick send function."""
    comm = TextComm()
    return comm.send(sender, receiver, content)


def decode(hex_id: str, comm: TextComm = None) -> str:
    """Quick decode function."""
    if comm:
        return comm.decode(hex_id)
    return f"[No context: {hex_id}]"
