#!/usr/bin/env python3
"""
Gene Shell - CST Matrix Identity and Reproduction Engine
=========================================================
Gene Model Shell for cubesquare_standalone.

This shell provides gene theory operations for the 48x48x48 Matrix system,
including identity generation, breeding, and lineage tracking.

Commands:
    status          - Show system status
    generate        - Generate identity for a token string
    breed           - Breed two identities
    profile         - Get identity profile at coordinates or by name
    validate        - Validate matrix cells for palindromic consistency
    lineage         - Test lineage between two tokens

The matrix is 48x48x48 = 110,592 cells, each with deterministic identity.
"""

import math
import hashlib
import json
from pathlib import Path
from dataclasses import dataclass, asdict
from datetime import datetime
from typing import Dict, List, Any, Optional, Tuple

# Import from C backend (0.009ns) with Python fallback
from .._core import PHI, K5, K11, LIGHT_UNIT
from .._core import morton_encode

MATRIX_SIZE = 48  # 48x48x48 = 110,592 cells


@dataclass
class MatrixIdentity:
    """Identity for a cell in the 48x48x48 matrix."""
    x: int
    y: int
    z: int
    morton: int
    hex_id: str
    basin: int
    layer: int
    diagonal: int
    phi_ratio: float
    k5_value: float
    k11_value: float
    palindrome: bool = False

    def to_dict(self) -> Dict:
        """Convert to dictionary."""
        return asdict(self)


@dataclass
class GeneProfile:
    """Gene profile for a token."""
    token: str
    hex_id: str
    identity: MatrixIdentity
    generation: int = 0
    parent_a: Optional[str] = None
    parent_b: Optional[str] = None
    created_at: str = None

    def __post_init__(self):
        if self.created_at is None:
            self.created_at = datetime.now().isoformat()

    def to_dict(self) -> Dict:
        """Convert to dictionary."""
        d = asdict(self)
        d['identity'] = self.identity.to_dict()
        return d


def token_to_coords(token: str) -> Tuple[int, int, int]:
    """Convert token string to matrix coordinates."""
    h = hashlib.sha256(token.encode()).hexdigest()
    x = int(h[0:2], 16) % MATRIX_SIZE
    y = int(h[2:4], 16) % MATRIX_SIZE
    z = int(h[4:6], 16) % MATRIX_SIZE
    return x, y, z


def coords_to_hex_id(x: int, y: int, z: int) -> str:
    """Convert coordinates to hex ID."""
    # Normalize to 8-bit range
    xx = (x * 5) & 0xFF  # Scale up for better distribution
    yy = (y * 5) & 0xFF
    zz = (z * 5) & 0xFF
    return f"{xx:02X}{yy:02X}{zz:02X}00"


def is_palindrome(n: int) -> bool:
    """Check if a number is palindromic in decimal."""
    s = str(n)
    return s == s[::-1]


def get_matrix_identity(x: int, y: int, z: int) -> MatrixIdentity:
    """
    Get the identity for a matrix cell.
    Each cell has deterministic identity based on position.
    """
    # Bound to matrix size
    x = x % MATRIX_SIZE
    y = y % MATRIX_SIZE
    z = z % MATRIX_SIZE

    # Calculate Morton code
    morton = morton_encode(x, y, z)

    # Derive hex ID
    hex_id = coords_to_hex_id(x, y, z)

    # Calculate basin and layer
    basin = morton % LIGHT_UNIT
    diagonal = int((morton * PHI) % 1000)

    # Classify layer
    if diagonal < 50:
        layer = 1
    elif basin < 26:
        layer = 2
    elif diagonal < 200:
        layer = 3
    elif basin < 65:
        layer = 4
    elif basin < 100:
        layer = 5
    elif diagonal < 500:
        layer = 6
    elif diagonal < 700:
        layer = 7
    elif diagonal < 900:
        layer = 8
    else:
        layer = 9

    # Calculate phi ratio (golden ratio positioning)
    cell_index = x + y * MATRIX_SIZE + z * MATRIX_SIZE * MATRIX_SIZE
    phi_ratio = (cell_index * PHI) % 1.0

    # K5 and K11 values
    k5_value = K5 * (1 + phi_ratio * 0.1)
    k11_value = K11 * (basin / LIGHT_UNIT)

    # Check palindrome property
    palindrome = is_palindrome(morton)

    return MatrixIdentity(
        x=x, y=y, z=z,
        morton=morton,
        hex_id=hex_id,
        basin=basin,
        layer=layer,
        diagonal=diagonal,
        phi_ratio=phi_ratio,
        k5_value=k5_value,
        k11_value=k11_value,
        palindrome=palindrome,
    )


def get_token_identity(token: str) -> MatrixIdentity:
    """Get matrix identity for a token string."""
    x, y, z = token_to_coords(token)
    return get_matrix_identity(x, y, z)


def breed_identities(identity_a: MatrixIdentity, identity_b: MatrixIdentity,
                    mode: str = 'expand') -> MatrixIdentity:
    """
    Breed two identities to create a child.

    Modes:
        expand: Child coordinates are midpoint + expansion
        contract: Child coordinates are midpoint - contraction
        spiral: Child coordinates follow phi spiral
    """
    if mode == 'expand':
        # Midpoint with expansion
        x = (identity_a.x + identity_b.x) // 2
        y = (identity_a.y + identity_b.y) // 2
        z = (identity_a.z + identity_b.z) // 2
        # Expand by phi ratio
        x = int((x + PHI) % MATRIX_SIZE)
        y = int((y + PHI * 2) % MATRIX_SIZE)
        z = int((z + PHI * 3) % MATRIX_SIZE)

    elif mode == 'contract':
        # Midpoint with contraction
        mid_x = (identity_a.x + identity_b.x) // 2
        mid_y = (identity_a.y + identity_b.y) // 2
        mid_z = (identity_a.z + identity_b.z) // 2
        # Contract toward center
        center = MATRIX_SIZE // 2
        x = (mid_x + center) // 2
        y = (mid_y + center) // 2
        z = (mid_z + center) // 2

    elif mode == 'spiral':
        # Follow phi spiral from parent A toward parent B
        dx = identity_b.x - identity_a.x
        dy = identity_b.y - identity_a.y
        dz = identity_b.z - identity_a.z
        # Apply rotation
        angle = math.atan2(dy, dx)
        dist = math.sqrt(dx*dx + dy*dy + dz*dz)
        new_angle = angle + math.pi / PHI
        new_dist = dist / PHI
        x = int(identity_a.x + new_dist * math.cos(new_angle)) % MATRIX_SIZE
        y = int(identity_a.y + new_dist * math.sin(new_angle)) % MATRIX_SIZE
        z = (identity_a.z + identity_b.z) // 2

    else:
        # Default: simple midpoint
        x = (identity_a.x + identity_b.x) // 2
        y = (identity_a.y + identity_b.y) // 2
        z = (identity_a.z + identity_b.z) // 2

    return get_matrix_identity(x, y, z)


def validate_matrix(sample_size: int = 1000) -> Dict[str, Any]:
    """
    Validate matrix cells for consistency.
    Checks that coordinates map deterministically to identities.
    """
    import random

    validated = 0
    palindrome_count = 0
    layer_distribution = {i: 0 for i in range(1, 10)}
    basin_counts = {}

    # Sample random cells
    for _ in range(sample_size):
        x = random.randint(0, MATRIX_SIZE - 1)
        y = random.randint(0, MATRIX_SIZE - 1)
        z = random.randint(0, MATRIX_SIZE - 1)

        identity = get_matrix_identity(x, y, z)

        # Verify determinism
        identity2 = get_matrix_identity(x, y, z)
        if identity.morton == identity2.morton:
            validated += 1

        if identity.palindrome:
            palindrome_count += 1

        layer_distribution[identity.layer] += 1
        basin_counts[identity.basin] = basin_counts.get(identity.basin, 0) + 1

    return {
        'total_cells': MATRIX_SIZE ** 3,
        'sampled': sample_size,
        'validated': validated,
        'determinism': validated / sample_size,
        'palindrome_ratio': palindrome_count / sample_size,
        'layer_distribution': layer_distribution,
        'unique_basins': len(basin_counts),
    }


class GeneShell:
    """
    Gene shell for CST Matrix identity operations.

    Provides identity generation, breeding, and lineage tracking
    for the 48x48x48 matrix system.
    """

    def __init__(self, profiles_dir: Optional[Path] = None):
        """Initialize gene shell."""
        self.name = "gene"
        self.description = "CST Matrix identity and reproduction"
        self.profiles_dir = profiles_dir

        # In-memory profile storage
        self._profiles: Dict[str, GeneProfile] = {}

        self.commands = {
            'status': 'Show system status',
            'generate': 'Generate identity for a token',
            'breed': 'Breed two identities',
            'profile': 'Get identity profile',
            'validate': 'Validate matrix consistency',
            'lineage': 'Show lineage between tokens',
        }

    def execute(self, command: str, args: Dict[str, Any] = None) -> Dict[str, Any]:
        """Execute a gene shell command."""
        args = args or {}

        handlers = {
            'status': self._cmd_status,
            'generate': self._cmd_generate,
            'breed': self._cmd_breed,
            'profile': self._cmd_profile,
            'validate': self._cmd_validate,
            'lineage': self._cmd_lineage,
        }

        handler = handlers.get(command)
        if not handler:
            return {'success': False, 'error': f'Unknown command: {command}'}

        try:
            return handler(args)
        except Exception as e:
            return {'success': False, 'error': str(e)}

    def _cmd_status(self, args: Dict) -> Dict:
        """Show system status."""
        return {
            'success': True,
            'matrix_size': f'{MATRIX_SIZE}x{MATRIX_SIZE}x{MATRIX_SIZE}',
            'total_cells': MATRIX_SIZE ** 3,
            'stored_profiles': len(self._profiles),
            'constants': {
                'phi': PHI,
                'k5': K5,
                'k11': K11,
                'light_unit': LIGHT_UNIT,
            },
            'commands': list(self.commands.keys()),
        }

    def _cmd_generate(self, args: Dict) -> Dict:
        """
        Generate identity for a token.

        Args:
            token: Token string to generate identity for
        """
        token = args.get('token')
        if not token:
            return {'success': False, 'error': 'token required'}

        identity = get_token_identity(token)
        hex_id = identity.hex_id

        # Create and store profile
        profile = GeneProfile(
            token=token,
            hex_id=hex_id,
            identity=identity,
            generation=0,
        )
        self._profiles[token] = profile

        return {
            'success': True,
            'token': token,
            'identity': identity.to_dict(),
            'profile_stored': True,
        }

    def _cmd_breed(self, args: Dict) -> Dict:
        """
        Breed two identities.

        Args:
            parent_a: First parent token
            parent_b: Second parent token
            mode: Breeding mode (expand, contract, spiral)
        """
        parent_a = args.get('parent_a')
        parent_b = args.get('parent_b')
        mode = args.get('mode', 'expand')

        if not parent_a or not parent_b:
            return {'success': False, 'error': 'parent_a and parent_b required'}

        # Get or generate parent identities
        if parent_a in self._profiles:
            identity_a = self._profiles[parent_a].identity
        else:
            identity_a = get_token_identity(parent_a)

        if parent_b in self._profiles:
            identity_b = self._profiles[parent_b].identity
        else:
            identity_b = get_token_identity(parent_b)

        # Breed
        child_identity = breed_identities(identity_a, identity_b, mode)

        # Generate child token name
        child_token = f"{parent_a}_{parent_b}_child"

        # Calculate generation
        gen_a = self._profiles.get(parent_a, GeneProfile(parent_a, '', identity_a)).generation
        gen_b = self._profiles.get(parent_b, GeneProfile(parent_b, '', identity_b)).generation
        child_gen = max(gen_a, gen_b) + 1

        # Create child profile
        child_profile = GeneProfile(
            token=child_token,
            hex_id=child_identity.hex_id,
            identity=child_identity,
            generation=child_gen,
            parent_a=parent_a,
            parent_b=parent_b,
        )
        self._profiles[child_token] = child_profile

        return {
            'success': True,
            'mode': mode,
            'parent_a': {
                'token': parent_a,
                'coords': (identity_a.x, identity_a.y, identity_a.z),
            },
            'parent_b': {
                'token': parent_b,
                'coords': (identity_b.x, identity_b.y, identity_b.z),
            },
            'child': child_profile.to_dict(),
        }

    def _cmd_profile(self, args: Dict) -> Dict:
        """
        Get identity profile.

        Args:
            token: Token name
            OR coords: Comma-separated coordinates (x,y,z)
        """
        token = args.get('token')
        coords = args.get('coords')

        if coords:
            # Parse coordinates
            try:
                parts = coords.split(',')
                x, y, z = int(parts[0]), int(parts[1]), int(parts[2])
                identity = get_matrix_identity(x, y, z)
                return {
                    'success': True,
                    'coords': (x, y, z),
                    'identity': identity.to_dict(),
                }
            except (ValueError, IndexError):
                return {'success': False, 'error': 'Invalid coords format. Use x,y,z'}

        if token:
            if token in self._profiles:
                return {
                    'success': True,
                    'profile': self._profiles[token].to_dict(),
                    'stored': True,
                }
            else:
                identity = get_token_identity(token)
                return {
                    'success': True,
                    'token': token,
                    'identity': identity.to_dict(),
                    'stored': False,
                }

        return {'success': False, 'error': 'token or coords required'}

    def _cmd_validate(self, args: Dict) -> Dict:
        """
        Validate matrix consistency.

        Args:
            sample_size: Number of cells to sample (default 1000)
        """
        sample_size = int(args.get('sample_size', 1000))
        result = validate_matrix(sample_size)
        result['success'] = True
        result['status'] = 'PASS' if result['determinism'] == 1.0 else 'FAIL'
        return result

    def _cmd_lineage(self, args: Dict) -> Dict:
        """
        Show lineage between two tokens.

        Args:
            token_a: First token
            token_b: Second token
            generations: Number of generations to trace (default 5)
        """
        token_a = args.get('token_a')
        token_b = args.get('token_b')
        generations = int(args.get('generations', 5))

        if not token_a or not token_b:
            return {'success': False, 'error': 'token_a and token_b required'}

        lineage = []

        # Get starting identities
        current_a = get_token_identity(token_a)
        current_b = get_token_identity(token_b)

        lineage.append({
            'generation': 0,
            'token_a': token_a,
            'token_b': token_b,
            'coords_a': (current_a.x, current_a.y, current_a.z),
            'coords_b': (current_b.x, current_b.y, current_b.z),
        })

        # Trace through generations
        for gen in range(1, generations + 1):
            child = breed_identities(current_a, current_b, 'expand')
            lineage.append({
                'generation': gen,
                'child_coords': (child.x, child.y, child.z),
                'child_hex': child.hex_id,
                'child_basin': child.basin,
                'child_layer': child.layer,
            })
            # Next generation: child becomes parent_a, original_b becomes parent_b
            current_a = child

        return {
            'success': True,
            'token_a': token_a,
            'token_b': token_b,
            'generations': generations,
            'lineage': lineage,
        }

    # Convenience methods
    def generate(self, token: str) -> Dict:
        """Convenience method to generate identity."""
        return self._cmd_generate({'token': token})

    def breed(self, parent_a: str, parent_b: str, mode: str = 'expand') -> Dict:
        """Convenience method to breed identities."""
        return self._cmd_breed({
            'parent_a': parent_a,
            'parent_b': parent_b,
            'mode': mode,
        })

    def run(self, command: str, *args, as_json: bool = False) -> Any:
        """Run a shell command (CLI interface)."""
        commands = {
            'status': lambda: self.execute('status'),
            'generate': lambda: self.execute('generate', {'token': args[0]} if args else {}),
            'breed': lambda: self.execute('breed', {
                'parent_a': args[0] if len(args) > 0 else '',
                'parent_b': args[1] if len(args) > 1 else '',
                'mode': args[2] if len(args) > 2 else 'expand'
            }),
            'profile': lambda: self.execute('profile', {
                'token': args[0] if args and not args[0].replace(',', '').isdigit() else None,
                'coords': args[0] if args and ',' in args[0] else None
            }),
            'validate': lambda: self.execute('validate', {'sample_size': args[0]} if args else {}),
            'lineage': lambda: self.execute('lineage', {
                'token_a': args[0] if len(args) > 0 else '',
                'token_b': args[1] if len(args) > 1 else '',
                'generations': args[2] if len(args) > 2 else 5
            }),
        }
        if command in commands:
            result = commands[command]()
            if as_json and isinstance(result, dict):
                return json.dumps(result, indent=2, default=str)
            return result
        return {'error': f'Unknown command: {command}', 'available': list(commands.keys())}


def main():
    """CLI entry point."""
    import sys

    shell = GeneShell()

    if len(sys.argv) < 2:
        print("Gene Shell - CST Matrix Identity and Reproduction")
        print("=" * 50)
        print(f"\nMatrix Size: {MATRIX_SIZE}x{MATRIX_SIZE}x{MATRIX_SIZE} = {MATRIX_SIZE**3:,} cells")
        print("\nCommands:")
        for cmd, desc in shell.commands.items():
            print(f"  {cmd:<15} - {desc}")
        print("\nExamples:")
        print("  python gene.py status")
        print("  python gene.py generate token=my_token")
        print("  python gene.py breed parent_a=token1 parent_b=token2 mode=expand")
        print("  python gene.py profile coords=10,20,30")
        print("  python gene.py validate sample_size=5000")
        return

    command = sys.argv[1]
    args = {}

    # Parse key=value arguments
    for arg in sys.argv[2:]:
        if '=' in arg:
            k, v = arg.split('=', 1)
            args[k] = v

    result = shell.execute(command, args)
    print(json.dumps(result, indent=2, default=str))


if __name__ == "__main__":
    main()
