"""
helix_cdc/capsules/crypto_dna.py
================================

Crypto-DNA Genotype Constructor

Converts Base64 seeds to bigint genome, applies regulatory padding,
performs factorization for developmental bifurcation, and generates
codon streams for growth operations.

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

import base64
import hashlib
import struct
from dataclasses import dataclass, field
from typing import Tuple, List, Dict, Any, Optional
import math

# Try to import sympy for factorization, fall back to simple method
try:
    from sympy import factorint, isprime, nextprime
    HAS_SYMPY = True
except ImportError:
    HAS_SYMPY = False


@dataclass
class CodonStream:
    """Stream of codons derived from a genome branch."""
    codons: List[bytes]
    branch_id: str  # 'p' or 'q'
    anchor_offset: Tuple[float, float, float]
    growth_params: Dict[str, float]


@dataclass
class GenomeFactors:
    """Result of genome factorization."""
    p: int
    q: int
    p_stream: CodonStream
    q_stream: CodonStream
    bifurcation_point: int


def b64_to_bigint(seed_b64: str) -> int:
    """
    Convert Base64 seed to bigint genome.

    The Base64 string is decoded to bytes, then interpreted as a
    big-endian unsigned integer.

    Args:
        seed_b64: Base64-encoded seed string

    Returns:
        BigInt representation of the seed
    """
    # Handle URL-safe base64 variants
    seed_b64 = seed_b64.replace('-', '+').replace('_', '/')

    # Add padding if needed
    padding = 4 - (len(seed_b64) % 4)
    if padding != 4:
        seed_b64 += '=' * padding

    seed_bytes = base64.b64decode(seed_b64)

    # Convert to bigint (big-endian)
    return int.from_bytes(seed_bytes, byteorder='big')


def bigint_to_b64(n: int) -> str:
    """
    Convert bigint back to Base64.

    Args:
        n: BigInt value

    Returns:
        Base64-encoded string
    """
    # Calculate byte length needed
    byte_len = (n.bit_length() + 7) // 8
    if byte_len == 0:
        byte_len = 1

    n_bytes = n.to_bytes(byte_len, byteorder='big')
    return base64.b64encode(n_bytes).decode('ascii')


def _simple_factorize(n: int) -> Tuple[int, int]:
    """
    Simple factorization for when sympy is not available.

    For composite numbers, finds a non-trivial factor.
    For primes, returns (n, 1).

    This is NOT cryptographically secure factorization -
    it's intentionally simple for regenerative purposes.
    """
    if n < 4:
        return (n, 1)

    if n % 2 == 0:
        return (2, n // 2)

    # Trial division up to sqrt(n)
    i = 3
    limit = int(math.isqrt(n)) + 1
    while i <= limit:
        if n % i == 0:
            return (i, n // i)
        i += 2

    # n is prime
    return (n, 1)


def _sympy_factorize(n: int) -> Tuple[int, int]:
    """
    Factorize using sympy for larger numbers.

    Returns the two largest prime factors, or (n, 1) if prime.
    """
    factors = factorint(n)
    primes = sorted(factors.keys(), reverse=True)

    if len(primes) == 0:
        return (n, 1)
    elif len(primes) == 1:
        p = primes[0]
        exp = factors[p]
        if exp >= 2:
            # Split exponent: p^e = p^(e//2) * p^(e - e//2)
            return (p ** (exp // 2), p ** (exp - exp // 2))
        return (p, 1)
    else:
        # Return two largest distinct primes (with their exponents)
        p = primes[0] ** factors[primes[0]]
        q = primes[1] ** factors[primes[1]]
        return (p, q)


def factorize_genome(n_padded: int) -> Tuple[int, int]:
    """
    Factorize padded genome into developmental branches.

    This is NOT for security - it's for bifurcation.
    The factors (p, q) become parallel growth channels.

    Args:
        n_padded: Padded bigint genome

    Returns:
        (p, q) factors for bifurcation
    """
    if HAS_SYMPY:
        return _sympy_factorize(n_padded)
    else:
        return _simple_factorize(n_padded)


def _int_to_codon_bytes(n: int, codon_size: int = 3) -> List[bytes]:
    """
    Convert integer to stream of codon bytes.

    Each codon is `codon_size` bytes.
    """
    byte_len = (n.bit_length() + 7) // 8
    if byte_len == 0:
        byte_len = 1

    n_bytes = n.to_bytes(byte_len, byteorder='big')

    # Pad to multiple of codon_size
    padding = (codon_size - (len(n_bytes) % codon_size)) % codon_size
    n_bytes = bytes(padding) + n_bytes

    codons = []
    for i in range(0, len(n_bytes), codon_size):
        codons.append(n_bytes[i:i+codon_size])

    return codons


def _derive_anchor_offset(n: int, branch_id: str) -> Tuple[float, float, float]:
    """
    Derive FibPi3D anchor offset from branch value.

    Uses golden ratio and pi for low-discrepancy positioning.
    """
    PHI = (1 + math.sqrt(5)) / 2
    PI = math.pi

    # Hash the branch for deterministic offset
    h = hashlib.sha256(f"{n}:{branch_id}".encode()).digest()

    # Extract 3 floats from hash
    x = struct.unpack('>I', h[0:4])[0] / (2**32)
    y = struct.unpack('>I', h[4:8])[0] / (2**32)
    z = struct.unpack('>I', h[8:12])[0] / (2**32)

    # Apply FibPi3D transformation
    x = (x * PHI) % 1.0
    y = (y * PI) % 1.0
    z = (z * PHI * PI) % 1.0

    return (x, y, z)


def _derive_growth_params(n: int, branch_id: str) -> Dict[str, float]:
    """
    Derive growth parameters from branch value.
    """
    h = hashlib.sha256(f"growth:{n}:{branch_id}".encode()).digest()

    return {
        'rate': struct.unpack('>f', h[0:4])[0] % 1.0,
        'branch_factor': 1 + (h[4] % 4),
        'depth_limit': 8 + (h[5] % 24),
        'diffusion': struct.unpack('>f', h[6:10])[0] % 0.5,
    }


def genome_to_codon_streams(p: int, q: int) -> Tuple[CodonStream, CodonStream]:
    """
    Convert factored genome to parallel codon streams.

    Args:
        p: First factor (growth channel A)
        q: Second factor (growth channel B)

    Returns:
        (p_stream, q_stream) codon streams
    """
    p_codons = _int_to_codon_bytes(p)
    q_codons = _int_to_codon_bytes(q)

    p_stream = CodonStream(
        codons=p_codons,
        branch_id='p',
        anchor_offset=_derive_anchor_offset(p, 'p'),
        growth_params=_derive_growth_params(p, 'p'),
    )

    q_stream = CodonStream(
        codons=q_codons,
        branch_id='q',
        anchor_offset=_derive_anchor_offset(q, 'q'),
        growth_params=_derive_growth_params(q, 'q'),
    )

    return (p_stream, q_stream)


def dna_to_codon_stream(seed_b64: str) -> List[bytes]:
    """
    Convert Base64 DNA directly to codon stream (without factorization).

    Used for simple cases where bifurcation isn't needed.
    """
    n = b64_to_bigint(seed_b64)
    return _int_to_codon_bytes(n)


# Codon operation mapping
CODON_OPS = {
    0: 'rotate',
    1: 'scale',
    2: 'diffuse',
    3: 'fold',
    4: 'branch',
    5: 'permute',
    6: 'modulate',
    7: 'anchor',
}


def codon_to_growth_op(codon: bytes) -> Tuple[str, int, int]:
    """
    Decode codon to growth operation.

    Returns:
        (op_name, param1, param2)
    """
    if len(codon) < 3:
        codon = codon + bytes(3 - len(codon))

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

    return (CODON_OPS[opcode], param1, param2)


def codon_stream_to_growth_ops(codons: List[bytes]) -> List[Tuple[str, int, int]]:
    """
    Convert entire codon stream to growth operation sequence.
    """
    return [codon_to_growth_op(c) for c in codons]


def full_genome_pipeline(seed_b64: str) -> GenomeFactors:
    """
    Full pipeline: Base64 → BigInt → Pad → Factor → Codon Streams

    Args:
        seed_b64: Base64-encoded seed

    Returns:
        GenomeFactors with all derived data
    """
    from .crypto_padding import apply_crypto_padding

    # Step 1: Base64 → BigInt
    n = b64_to_bigint(seed_b64)

    # Step 2: Apply regulatory padding
    n_padded = apply_crypto_padding(n)

    # Step 3: Factorize for bifurcation
    p, q = factorize_genome(n_padded)

    # Step 4: Generate codon streams
    p_stream, q_stream = genome_to_codon_streams(p, q)

    # Bifurcation point is where padding ends
    bifurc = n.bit_length()

    return GenomeFactors(
        p=p,
        q=q,
        p_stream=p_stream,
        q_stream=q_stream,
        bifurcation_point=bifurc,
    )
