"""
helix_cdc/capsules/crypto_crt.py
================================

Chinese Remainder Theorem Recombination Engine

Uses CRT to recombine bifurcated developmental branches (p, q) into
a unified phenotype. This is the mathematical "fusion" of parallel
growth channels.

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

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


@dataclass
class CRTLayout:
    """CRT configuration and state."""
    moduli: Tuple[int, int]  # (p, q)
    residues: Tuple[int, int]  # (r_p, r_q)
    product: int  # p * q
    phenotype_int: int  # Reconstructed value


def extended_gcd(a: int, b: int) -> Tuple[int, int, int]:
    """
    Extended Euclidean Algorithm.

    Returns (gcd, x, y) such that a*x + b*y = gcd(a, b)
    """
    if a == 0:
        return b, 0, 1

    gcd, x1, y1 = extended_gcd(b % a, a)

    x = y1 - (b // a) * x1
    y = x1

    return gcd, x, y


def mod_inverse(a: int, m: int) -> int:
    """
    Compute modular multiplicative inverse of a mod m.

    Returns x such that (a * x) % m == 1
    """
    gcd, x, _ = extended_gcd(a % m, m)

    if gcd != 1:
        raise ValueError(f"Modular inverse does not exist for {a} mod {m}")

    return (x % m + m) % m


def crt_two(r1: int, m1: int, r2: int, m2: int) -> int:
    """
    Chinese Remainder Theorem for two moduli.

    Finds x such that:
        x ≡ r1 (mod m1)
        x ≡ r2 (mod m2)

    Requires gcd(m1, m2) = 1 (coprime moduli).

    Args:
        r1: Residue mod m1
        m1: First modulus
        r2: Residue mod m2
        m2: Second modulus

    Returns:
        x in range [0, m1*m2)
    """
    M = m1 * m2

    # Compute coefficients
    M1 = M // m1  # = m2
    M2 = M // m2  # = m1

    # Compute modular inverses
    y1 = mod_inverse(M1, m1)
    y2 = mod_inverse(M2, m2)

    # Combine
    x = (r1 * M1 * y1 + r2 * M2 * y2) % M

    return x


def crt_multi(residues: List[int], moduli: List[int]) -> int:
    """
    Chinese Remainder Theorem for multiple moduli.

    Finds x such that x ≡ r_i (mod m_i) for all i.

    Args:
        residues: List of residues [r1, r2, ...]
        moduli: List of moduli [m1, m2, ...] (must be pairwise coprime)

    Returns:
        x in range [0, product of moduli)
    """
    if len(residues) != len(moduli):
        raise ValueError("Residues and moduli must have same length")

    if len(residues) == 0:
        return 0

    if len(residues) == 1:
        return residues[0] % moduli[0]

    # Iterative CRT
    x = residues[0]
    m = moduli[0]

    for i in range(1, len(residues)):
        x = crt_two(x, m, residues[i], moduli[i])
        m *= moduli[i]

    return x


def crt_recombine(p_result: int, q_result: int, p: int, q: int) -> CRTLayout:
    """
    Recombine bifurcated growth channels via CRT.

    The p_result and q_result are the outputs of applying growth
    operations to the p and q branches respectively. CRT combines
    them into a single phenotype integer.

    Args:
        p_result: Result of f(p_stream)
        q_result: Result of f(q_stream)
        p: Original p factor (modulus)
        q: Original q factor (modulus)

    Returns:
        CRTLayout with combined phenotype
    """
    # Normalize residues to valid range
    r_p = p_result % p if p > 0 else 0
    r_q = q_result % q if q > 0 else 0

    # Handle edge cases
    if p == 0 or q == 0:
        phenotype = max(p_result, q_result)
    elif p == 1:
        phenotype = q_result
    elif q == 1:
        phenotype = p_result
    else:
        # Check if coprime
        gcd = math.gcd(p, q)
        if gcd > 1:
            # Not coprime - need to adjust
            # Divide out common factor
            p_adj = p // gcd
            q_adj = q // gcd

            if p_adj == 1:
                phenotype = q_result
            elif q_adj == 1:
                phenotype = p_result
            else:
                phenotype = crt_two(r_p % p_adj, p_adj, r_q % q_adj, q_adj)
        else:
            phenotype = crt_two(r_p, p, r_q, q)

    return CRTLayout(
        moduli=(p, q),
        residues=(r_p, r_q),
        product=p * q if p > 0 and q > 0 else max(p, q),
        phenotype_int=phenotype,
    )


def split_phenotype(phenotype_int: int, p: int, q: int) -> Tuple[int, int]:
    """
    Split phenotype back into branch residues.

    This is the inverse of CRT recombination.

    Args:
        phenotype_int: Combined phenotype
        p: First modulus
        q: Second modulus

    Returns:
        (r_p, r_q) residues
    """
    r_p = phenotype_int % p if p > 0 else 0
    r_q = phenotype_int % q if q > 0 else 0
    return (r_p, r_q)


def apply_growth_function(
    stream_value: int,
    growth_fn: Callable[[int], int],
    modulus: int
) -> int:
    """
    Apply growth function to a branch value.

    The growth function transforms the stream value, and the result
    is taken modulo the branch's modulus to stay in the field.

    Args:
        stream_value: Input from codon stream
        growth_fn: Transformation function
        modulus: Branch modulus

    Returns:
        Transformed value mod modulus
    """
    transformed = growth_fn(stream_value)
    return transformed % modulus if modulus > 0 else transformed


# Default growth functions for p and q branches
def default_p_growth(x: int) -> int:
    """Default growth function for p-branch (left morphline)."""
    # Fibonacci-like growth
    if x < 2:
        return x
    a, b = 0, 1
    for _ in range(min(x, 1000)):
        a, b = b, a + b
    return b


def default_q_growth(x: int) -> int:
    """Default growth function for q-branch (right morphline)."""
    # Pi-based growth
    PI_BITS = 0x243F6A8885A308D3  # First 64 bits of pi
    return (x * PI_BITS) ^ (x >> 3)


def crt_growth_pipeline(
    p: int,
    q: int,
    p_growth_fn: Callable[[int], int] = None,
    q_growth_fn: Callable[[int], int] = None,
) -> CRTLayout:
    """
    Full CRT growth pipeline.

    Applies growth functions to each branch, then recombines via CRT.

    Args:
        p: First factor (and input to p-branch)
        q: Second factor (and input to q-branch)
        p_growth_fn: Growth function for p-branch (default: Fibonacci)
        q_growth_fn: Growth function for q-branch (default: Pi-based)

    Returns:
        CRTLayout with grown and combined phenotype
    """
    if p_growth_fn is None:
        p_growth_fn = default_p_growth
    if q_growth_fn is None:
        q_growth_fn = default_q_growth

    # Apply growth to each branch
    p_result = apply_growth_function(p, p_growth_fn, p)
    q_result = apply_growth_function(q, q_growth_fn, q)

    # Recombine via CRT
    return crt_recombine(p_result, q_result, p, q)


def derive_crt_layout(seed_hex: str) -> CRTLayout:
    """
    Derive complete CRT layout from seed.

    This is the top-level function that takes a hex seed and produces
    a full CRT recombination result.

    Args:
        seed_hex: Hex-encoded seed

    Returns:
        CRTLayout with phenotype
    """
    from .crypto_dna import b64_to_bigint, factorize_genome
    from .crypto_padding import apply_crypto_padding
    import base64

    # If seed is hex, convert to bytes then base64
    seed_bytes = bytes.fromhex(seed_hex)
    seed_b64 = base64.b64encode(seed_bytes).decode('ascii')

    # Pipeline: b64 → bigint → pad → factor → grow → CRT
    n = b64_to_bigint(seed_b64)
    n_padded = apply_crypto_padding(n)
    p, q = factorize_genome(n_padded)

    return crt_growth_pipeline(p, q)
