"""
helix_cdc/capsules/crypto_regen.py
==================================

CDC + FibPi3D Growth Pipeline

Converts Crypto-DNA phenotype integers into actual tensors, bytes,
and other regenerated outputs using FibPi3D spatial organization
and CDC growth operators.

WO#50: Crypto-DNA Regenerative Substrate
"""

import hashlib
import struct
import math
import numpy as np
from dataclasses import dataclass
from typing import Tuple, List, Dict, Any, Optional, Union
from pathlib import Path

# Try to import existing modules
try:
    from ..scheduler.fibpi_nd import fibpi_nd_points
    HAS_FIBPI = True
except ImportError:
    HAS_FIBPI = False


@dataclass
class GrowthState:
    """State during growth process."""
    values: np.ndarray
    depth: int
    branch_history: List[str]
    anchor_coords: Tuple[float, float, float]


@dataclass
class PhenotypeOutput:
    """Final phenotype output."""
    data: Union[np.ndarray, bytes]
    output_type: str  # 'tensor', 'bytes', 'structure'
    shape: Tuple[int, ...]
    dtype: str
    growth_steps: int
    sha256: str


# ============================================
# FibPi3D Integration
# ============================================

PHI = (1 + math.sqrt(5)) / 2  # Golden ratio
PI = math.pi


def fibpi_point(index: int, total: int, dims: int = 3) -> Tuple[float, ...]:
    """
    Generate a single FibPi point in N-dimensional space.

    Uses golden ratio and pi for low-discrepancy positioning.
    """
    if dims == 3:
        # Fibonacci sphere mapping
        theta = 2 * PI * index / PHI
        phi_coord = math.acos(1 - 2 * (index + 0.5) / total)

        x = math.sin(phi_coord) * math.cos(theta)
        y = math.sin(phi_coord) * math.sin(theta)
        z = math.cos(phi_coord)

        return (x, y, z)
    else:
        # Higher dimensional: use multiplicative sequence
        coords = []
        for d in range(dims):
            val = (index * (PHI ** (d + 1))) % 1.0
            # Map to [-1, 1]
            val = val * 2 - 1
            coords.append(val)
        return tuple(coords)


def fibpi_lattice(n_points: int, dims: int = 3) -> np.ndarray:
    """
    Generate FibPi3D lattice of n points in dims dimensions.
    """
    points = np.zeros((n_points, dims))
    for i in range(n_points):
        coords = fibpi_point(i, n_points, dims)
        points[i, :len(coords)] = coords[:dims]
    return points


def map_to_fibpi3d(phenotype_int: int, n_points: int = 1024) -> np.ndarray:
    """
    Map phenotype integer to FibPi3D coordinate space.

    The phenotype integer is used to select and weight points
    in the FibPi3D lattice.

    Args:
        phenotype_int: Integer from CRT recombination
        n_points: Number of lattice points

    Returns:
        Weighted coordinate vector
    """
    lattice = fibpi_lattice(n_points, dims=3)

    # Use phenotype to derive weights
    weights = np.zeros(n_points)
    pheno_bytes = phenotype_int.to_bytes(
        (phenotype_int.bit_length() + 7) // 8 or 1,
        byteorder='big'
    )

    # Hash-based weight derivation
    for i in range(n_points):
        h = hashlib.sha256(pheno_bytes + i.to_bytes(4, 'big')).digest()
        weights[i] = struct.unpack('>f', h[:4])[0]

    # Normalize weights
    weights = np.abs(weights)
    weights = weights / (weights.sum() + 1e-10)

    # Weighted combination of lattice points
    result = np.zeros(3)
    for i in range(n_points):
        result += weights[i] * lattice[i]

    return result


# ============================================
# CDC Growth Operators
# ============================================

def grow_rotate(state: np.ndarray, param: int) -> np.ndarray:
    """Rotate array by param positions."""
    return np.roll(state, param)


def grow_scale(state: np.ndarray, param: int) -> np.ndarray:
    """Scale array by factor derived from param."""
    factor = 1.0 + (param / 255.0) * 0.5
    return state * factor


def grow_diffuse(state: np.ndarray, param: int) -> np.ndarray:
    """Diffuse values with neighboring elements."""
    kernel_size = max(1, param % 5)
    result = state.copy()
    for _ in range(kernel_size):
        left = np.roll(result, 1)
        right = np.roll(result, -1)
        result = (result + left + right) / 3.0
    return result


def grow_fold(state: np.ndarray, param: int) -> np.ndarray:
    """Apply nonlinear folding (tanh)."""
    scale = 0.1 + (param / 255.0) * 2.0
    return np.tanh(state * scale)


def grow_branch(state: np.ndarray, param: int) -> np.ndarray:
    """Branch array (duplicate with variation)."""
    variation = (param / 255.0) * 0.1
    branch = state[::-1] * (1 + variation)
    return np.concatenate([state, branch])


def grow_permute(state: np.ndarray, param: int) -> np.ndarray:
    """Permute array elements deterministically."""
    rng = np.random.RandomState(param)
    indices = rng.permutation(len(state))
    return state[indices]


def grow_modulate(state: np.ndarray, param: int) -> np.ndarray:
    """Modulate with sinusoidal wave."""
    freq = 0.01 + (param / 255.0) * 0.5
    phase = np.arange(len(state)) * freq
    return state * (1 + 0.5 * np.sin(phase))


def grow_anchor(state: np.ndarray, param: int) -> np.ndarray:
    """Apply FibPi3D anchor transformation."""
    anchor_idx = param % 1024
    anchor = fibpi_point(anchor_idx, 1024, dims=3)

    # Scale state by anchor magnitude
    mag = math.sqrt(sum(x**2 for x in anchor))
    return state * (0.5 + mag)


GROWTH_OPS = {
    0: ('rotate', grow_rotate),
    1: ('scale', grow_scale),
    2: ('diffuse', grow_diffuse),
    3: ('fold', grow_fold),
    4: ('branch', grow_branch),
    5: ('permute', grow_permute),
    6: ('modulate', grow_modulate),
    7: ('anchor', grow_anchor),
}


def apply_cdc_growth(
    initial_state: np.ndarray,
    codon_stream: List[bytes],
    max_size: int = 1024 * 1024,  # 1MB max
) -> GrowthState:
    """
    Apply CDC growth operators from codon stream.

    Args:
        initial_state: Starting state vector
        codon_stream: List of 3-byte codons
        max_size: Maximum output size

    Returns:
        GrowthState with grown values
    """
    state = initial_state.copy()
    history = []
    anchor_coords = (0.0, 0.0, 0.0)

    for depth, codon in enumerate(codon_stream):
        if len(codon) < 3:
            codon = codon + bytes(3 - len(codon))

        opcode = codon[0] % 8
        param1 = codon[1]
        param2 = codon[2]

        op_name, op_fn = GROWTH_OPS[opcode]
        history.append(op_name)

        # Apply operation
        state = op_fn(state, param1)

        # Track anchor if anchor op
        if opcode == 7:
            anchor_coords = fibpi_point(param1, 1024, dims=3)

        # Size check for branch operations
        if len(state) > max_size:
            state = state[:max_size]
            break

    return GrowthState(
        values=state,
        depth=len(history),
        branch_history=history,
        anchor_coords=anchor_coords,
    )


# ============================================
# Phenotype Output Generation
# ============================================

def phenotype_to_tensor(
    phenotype_int: int,
    shape: Tuple[int, ...],
    dtype: str = 'float32',
) -> PhenotypeOutput:
    """
    Convert phenotype integer to tensor.

    Args:
        phenotype_int: CRT-combined phenotype
        shape: Desired output shape
        dtype: NumPy dtype string

    Returns:
        PhenotypeOutput with tensor data
    """
    total_elements = 1
    for dim in shape:
        total_elements *= dim

    # Generate initial state from phenotype
    pheno_bytes = phenotype_int.to_bytes(
        (phenotype_int.bit_length() + 7) // 8 or 1,
        byteorder='big'
    )

    # Use SHAKE256 for arbitrary length expansion
    shake = hashlib.shake_256(pheno_bytes)
    expanded = shake.digest(total_elements * 4)  # 4 bytes per float

    # Convert to array
    values = np.frombuffer(expanded, dtype=np.uint32)
    values = values.astype(np.float32) / (2**32)  # Normalize to [0, 1]
    values = values * 2 - 1  # Map to [-1, 1]

    # Reshape
    tensor = values[:total_elements].reshape(shape)

    # Convert to target dtype
    if dtype != 'float32':
        tensor = tensor.astype(dtype)

    sha256 = hashlib.sha256(tensor.tobytes()).hexdigest()

    return PhenotypeOutput(
        data=tensor,
        output_type='tensor',
        shape=shape,
        dtype=dtype,
        growth_steps=0,
        sha256=sha256,
    )


def phenotype_to_bytes(
    phenotype_int: int,
    size: int,
) -> PhenotypeOutput:
    """
    Convert phenotype integer to raw bytes.

    Args:
        phenotype_int: CRT-combined phenotype
        size: Number of bytes to generate

    Returns:
        PhenotypeOutput with bytes data
    """
    pheno_bytes = phenotype_int.to_bytes(
        (phenotype_int.bit_length() + 7) // 8 or 1,
        byteorder='big'
    )

    shake = hashlib.shake_256(pheno_bytes)
    data = shake.digest(size)

    sha256 = hashlib.sha256(data).hexdigest()

    return PhenotypeOutput(
        data=data,
        output_type='bytes',
        shape=(size,),
        dtype='uint8',
        growth_steps=0,
        sha256=sha256,
    )


def crypto_dna_to_cdc_ops(
    seed_b64: str,
) -> List[Tuple[str, int, int]]:
    """
    Convert Crypto-DNA seed to CDC operation sequence.

    Args:
        seed_b64: Base64-encoded seed

    Returns:
        List of (op_name, param1, param2) tuples
    """
    from .crypto_dna import dna_to_codon_stream, codon_stream_to_growth_ops

    codons = dna_to_codon_stream(seed_b64)
    return codon_stream_to_growth_ops(codons)


def full_crypto_regen_pipeline(
    seed_b64: str,
    output_shape: Tuple[int, ...] = (1024,),
    output_dtype: str = 'float32',
) -> PhenotypeOutput:
    """
    Full Crypto-DNA to phenotype pipeline.

    Pipeline:
    1. Base64 → BigInt
    2. Apply padding
    3. Factorize (p, q)
    4. Generate codon streams
    5. Apply CDC growth
    6. CRT recombine
    7. Generate output

    Args:
        seed_b64: Base64-encoded seed
        output_shape: Desired output tensor shape
        output_dtype: Output dtype

    Returns:
        PhenotypeOutput with final data
    """
    from .crypto_dna import full_genome_pipeline
    from .crypto_crt import crt_growth_pipeline

    # Run genome pipeline
    genome = full_genome_pipeline(seed_b64)

    # CRT recombine
    crt_result = crt_growth_pipeline(genome.p, genome.q)

    # Generate initial state
    phenotype = crt_result.phenotype_int
    initial_state = np.array([
        float((phenotype >> (i * 8)) & 0xFF) / 255.0
        for i in range(min(1024, phenotype.bit_length() // 8 + 1))
    ])

    if len(initial_state) == 0:
        initial_state = np.array([0.5])

    # Apply CDC growth from both branches
    p_codons = genome.p_stream.codons
    q_codons = genome.q_stream.codons

    # Interleave codon streams
    combined_codons = []
    for i in range(max(len(p_codons), len(q_codons))):
        if i < len(p_codons):
            combined_codons.append(p_codons[i])
        if i < len(q_codons):
            combined_codons.append(q_codons[i])

    # Grow
    growth_state = apply_cdc_growth(initial_state, combined_codons)

    # Map to FibPi3D
    anchor = map_to_fibpi3d(phenotype)

    # Scale by anchor
    scale_factor = np.linalg.norm(anchor) + 0.5
    final_values = growth_state.values * scale_factor

    # Generate output tensor
    output = phenotype_to_tensor(phenotype, output_shape, output_dtype)
    output.growth_steps = growth_state.depth

    return output
