#!/usr/bin/env python3
"""
FibPi Substrate API
===================

Shared mathematical foundation for Helix CDC substrate.
Based on φ (golden ratio) and π, governs:
- Regen layer: Temporal scheduling
- Vault layer: Spatial positioning
- Decision layer: Routing and optimization

All FibPi-related computations should go through this API to ensure
consistency and prevent math drift across experiments.
"""

import hashlib
import numpy as np
from typing import List, Dict, Optional, Tuple

# FibPi constants (from helix_regen_cli.c line 17-18)
GOLDEN = 0x9e3779b97f4a7c15  # φ (golden ratio) as 64-bit constant
PI_LIKE = 0x243f6a8885a308d3  # π as 64-bit constant

# Golden ratio for vault coordinates
PHI = (1 + np.sqrt(5)) / 2


def fibpi_mix_u64(idx: int) -> int:
    """
    Core FibPi mixing function - EXACT replica from helix_regen_cli.c line 70-76

    This is the mathematical foundation of the substrate. Do not modify
    without updating the C implementation and regenerating KATs.

    Args:
        idx: Block index or step number

    Returns:
        64-bit mixed value using φ and π
    """
    w = (idx * GOLDEN) ^ PI_LIKE
    w &= 0xFFFFFFFFFFFFFFFF  # Keep in 64-bit range
    return w


def fibpi_regen_schedule(seed: str, block_range: Tuple[int, int]) -> List[int]:
    """
    Compute FibPi regen schedule for a seed over a block range.

    This is the TEMPORAL dimension of the substrate (WHEN to pull bytes).

    Args:
        seed: Seed string (unused in current impl, schedule is deterministic by block index)
        block_range: (start_block, end_block) inclusive

    Returns:
        List of fibpi_mix values for each block in range
    """
    start, end = block_range
    schedule = []
    for block_idx in range(start, end + 1):
        mix = fibpi_mix_u64(block_idx)
        schedule.append(mix)
    return schedule


def fibpi_vault_coords(seed: str, block_range: Tuple[int, int]) -> List[Dict]:
    """
    Compute FibPi3D vault coordinates for a seed over a block range.

    This is the SPATIAL dimension of the substrate (WHERE in 72D torus).

    Args:
        seed: Seed string for vault lookup
        block_range: (start_step, end_step) inclusive

    Returns:
        List of coordinate dicts with {x, y, z, phi_component, pi_component, step}
    """
    # Try to import crystal_vault for real coordinates
    try:
        import sys
        from pathlib import Path
        sys.path.insert(0, str(Path(__file__).parent.parent.parent))
        import crystal_vault
        vault_available = True
    except:
        vault_available = False

    coords = []
    start, end = block_range

    for step in range(start, end + 1):
        if vault_available:
            try:
                # Use actual crystal_vault anchor computation
                seed_hash = hashlib.sha256(seed.encode()).hexdigest()
                context = f"block_{step}".encode()
                anchor = crystal_vault.anchor_for(seed_hash, context, step)

                coords.append({
                    "x": anchor["x"],
                    "y": anchor["y"],
                    "z": anchor["z"],
                    "phi_component": (anchor["x"] / PHI) % 1.0,
                    "pi_component": (anchor["y"] / np.pi) % 1.0,
                    "step": step
                })
                continue
            except:
                vault_available = False

        # Fallback: Synthetic FibPi3D coordinates
        mix = fibpi_mix_u64(step)

        # Extract 3 coordinates from 64-bit mix
        x = (mix & 0xFFFF) * PHI
        y = ((mix >> 16) & 0xFFFF) * np.pi
        z = ((mix >> 32) & 0xFFFF) * (PHI * np.pi)

        coords.append({
            "x": x,
            "y": y,
            "z": z,
            "phi_component": (x / PHI) % 1.0,
            "pi_component": (y / np.pi) % 1.0,
            "step": step,
            "synthetic": True
        })

    return coords


def fibpi_metric(seed: str, num_blocks: int = 10) -> float:
    """
    Compute a scalar FibPi metric for a seed.

    This provides a single number representing the seed's position
    in FibPi substrate space, useful for routing and distance calculations.

    Args:
        seed: Seed string or manifest ID
        num_blocks: Number of blocks to average over (default 10)

    Returns:
        Normalized scalar in [0, 1) representing FibPi substrate position
    """
    # Compute regen schedule and vault coords
    schedule = fibpi_regen_schedule(seed, (0, num_blocks - 1))
    coords = fibpi_vault_coords(seed, (0, num_blocks - 1))

    # Average normalized regen values
    regen_norm = np.mean([x / 0xFFFFFFFFFFFFFFFF for x in schedule])

    # Average phi components from vault
    phi_norm = np.mean([c["phi_component"] for c in coords])

    # Combine (equal weight for now, could tune)
    metric = (regen_norm + phi_norm) / 2.0

    return float(metric)


def fibpi_distance(seed1: str, seed2: str, num_blocks: int = 10) -> float:
    """
    Compute distance between two seeds in FibPi substrate space.

    Args:
        seed1: First seed string or manifest ID
        seed2: Second seed string or manifest ID
        num_blocks: Number of blocks to average over (default 10)

    Returns:
        Distance in [0, 1] where 0 = identical, 1 = maximally different
    """
    metric1 = fibpi_metric(seed1, num_blocks)
    metric2 = fibpi_metric(seed2, num_blocks)

    # Simple Euclidean distance in [0,1] space
    # Could enhance with φ-weighted distance or other substrate-aware metrics
    distance = abs(metric1 - metric2)

    return float(distance)


def fibpi_metric_for_manifest(
    manifest_id: str,
    seed_index: Dict,
    task: str = None,
    num_blocks: int = 10
) -> float:
    """
    Compute FibPi substrate metric for a specific manifest.

    This is a helper for decision layer functions (router, QUBO) that need
    to compute FibPi metrics from manifest data in seed_index.

    Args:
        manifest_id: Manifest identifier
        seed_index: Seed index dict (supports both flat and nested structures)
        task: Task name (unused currently, for future task-specific metrics)
        num_blocks: Number of blocks for FibPi calculation

    Returns:
        FibPi metric in [0, 1] range (0.5 if seed not found)
    """
    # Handle nested structure: {"manifests": {"id": {...}}}
    manifests = seed_index.get("manifests", seed_index)

    # Extract manifest info
    manifest_info = manifests.get(manifest_id, {})

    # Try to get seed (check multiple possible keys)
    seed = manifest_info.get("seed")
    if not seed:
        seed = manifest_info.get("seed_id")
    if not seed:
        # Fallback: use manifest_id itself as seed
        seed = f"manifest_{manifest_id}"

    # Use existing fibpi_metric function
    return fibpi_metric(seed, num_blocks=num_blocks)


def fibpi_opt_position(idx: int) -> float:
    """
    Compute optimization layer position for a given index.

    This is the DECISION dimension (WHICH position is optimal).

    Args:
        idx: Position index

    Returns:
        Normalized position in [0, 1)
    """
    mix = fibpi_mix_u64(idx)
    position = (mix / 0xFFFFFFFFFFFFFFFF)
    return float(position)


def fibpi_energy(position: float) -> float:
    """
    Compute FibPi energy for a given position.

    Lower energy near golden ratio points (optimization target).

    Args:
        position: Normalized position in [0, 1)

    Returns:
        Energy value (negative = favorable)
    """
    # Favor positions near golden ratio points
    phi_distance = abs(position - (PHI % 1.0))
    energy = -1.0 / (1.0 + phi_distance * 10)

    return float(energy)


def fibpi_score(position: float) -> float:
    """
    Compute FibPi alignment score for a position.

    Args:
        position: Normalized position in [0, 1)

    Returns:
        Score in [-1, 1] where higher = better alignment with FibPi structure
    """
    score = np.cos(position * 2 * np.pi * PHI)
    return float(score)


# Substrate health check helper
def substrate_fingerprint(seed: str, num_blocks: int = 20) -> Dict:
    """
    Compute complete FibPi substrate fingerprint for a seed.

    Useful for health checks, comparisons, and validation.

    Args:
        seed: Seed string or manifest ID
        num_blocks: Number of blocks to compute

    Returns:
        Dict with regen schedule, vault coords, metric, and stats
    """
    schedule = fibpi_regen_schedule(seed, (0, num_blocks - 1))
    coords = fibpi_vault_coords(seed, (0, num_blocks - 1))
    metric = fibpi_metric(seed, num_blocks)

    # Compute variance (structure vs randomness)
    regen_norm = np.array([x / 0xFFFFFFFFFFFFFFFF for x in schedule])
    vault_phi = np.array([c["phi_component"] for c in coords])

    return {
        "seed": seed,
        "num_blocks": num_blocks,
        "fibpi_metric": metric,
        "regen_variance": float(np.var(regen_norm)),
        "vault_variance": float(np.var(vault_phi)),
        "regen_mean": float(np.mean(regen_norm)),
        "vault_mean": float(np.mean(vault_phi)),
        "schedule_sample": schedule[:5],  # First 5 for brevity
        "coords_sample": coords[:3]       # First 3 for brevity
    }
