#!/usr/bin/env python3
"""
DAG Deep Tuning: find optimal modulus selection strategies.
"""
import sys, math, random, json
from collections import Counter
sys.path.insert(0, '/home/allaun/SilverSight/scripts')
from verify_wrapping import f_k, is_sidon, sum_collisions, wrapping_criterion, certify_sidon_creation, m_difference_condition, wrapping_condition

random.seed(42)

# ---------- Failure Mode Analysis ----------

def analyze_failures(A, S, L1, L2):
    """Why does this moduli pair fail to create Sidon?"""
    M = L1 * L2
    guaranteed, FA, reason = certify_sidon_creation(A, S, [L1, L2])
    collisions = sum_collisions(A)
    wrap_ok, unresolved = wrapping_condition(A, S, [L1, L2])
    mdiff_ok, violators = m_difference_condition(A, M)
    
    failures = []
    if not wrap_ok:
        for (a,b,c,d),(s1,s2) in unresolved:
            failures.append(f"  Wrap fail: {a}+{b}={a+b} and {c}+{d}={c+d} both map to s1={s1}, s2={s2} (same wrap state)")
    if not mdiff_ok:
        for T1, T2, pairs1, pairs2 in violators:
            failures.append(f"  M-diff fail: sum {T1} (from {pairs1}) and {T2} (from {pairs2}) differ by M={M}")
    return failures, FA

def detailed_modulus_report(A, S, max_mod=20):
    """Full report on every valid modulus pair."""
    maxA = max(A)
    rows = []
    for L1 in range(2, max_mod + 1):
        for L2 in range(L1 + 1, max_mod + 1):
            if math.gcd(L1, L2) != 1: continue
            M = L1 * L2
            if not (maxA < M <= 2 * maxA): continue
            guaranteed, FA, _ = certify_sidon_creation(A, S, [L1, L2])
            sidon = is_sidon(FA)
            fails, _ = analyze_failures(A, S, L1, L2)
            rows.append({
                'L1': L1, 'L2': L2, 'M': M,
                'sidon': sidon, 'guaranteed': guaranteed,
                'failures': fails, 'FA': FA
            })
    return rows

# ---------- Optimal M Strategy ----------

def analyze_optimal_M_trend(trials=500):
    """Trend: what M/maxA ratios work best?"""
    results = []
    for trial in range(trials):
        maxA = random.randint(5, 30)
        n = random.randint(4, 8)
        A = sorted(random.sample(range(maxA + 1), min(n, maxA + 1)))
        S = random.randint(maxA, maxA + 10)
        
        max_mod = max(2, min(20, 2 * maxA))
        for L1 in range(2, max_mod + 1):
            for L2 in range(L1 + 1, max_mod + 1):
                if math.gcd(L1, L2) != 1: continue
                M = L1 * L2
                if not (maxA < M <= 2 * maxA): continue
                FA = [f_k(a, S, [L1, L2]) for a in A]
                sidon = is_sidon(FA)
                results.append({
                    'maxA': maxA, 'M': M,
                    'ratio': M / maxA,
                    'sidon': sidon
                })
    
    # Group by ratio buckets
    buckets = {}
    for r in results:
        bucket = round(r['ratio'] * 10) / 10  # 0.1 increments
        if bucket not in buckets:
            buckets[bucket] = {'total': 0, 'sidon': 0}
        buckets[bucket]['total'] += 1
        if r['sidon']:
            buckets[bucket]['sidon'] += 1
    
    print("\n=== Optimal M/maxA Ratio Analysis ===")
    print(f"{'Ratio':>8} {'Total':>8} {'Sidon':>8} {'Rate':>8}")
    print("-" * 36)
    for ratio in sorted(buckets.keys()):
        b = buckets[ratio]
        pct = b['sidon'] / b['total'] * 100
        print(f"{ratio:>8.1f} {b['total']:>8} {b['sidon']:>8} {pct:>7.1f}%")
    
    # Best ratio range
    best = max(buckets.items(), key=lambda x: x[1]['sidon'] / x[1]['total'])
    print(f"\nBest ratio: {best[0]:.1f} ({best[1]['sidon']/best[1]['total']*100:.1f}% success)")

# ---------- Modulus Size Preference ----------

def analyze_modulus_size_preference(trials=500):
    """Which modulus values work most often?"""
    mod_counts = Counter()
    mod_sidon = Counter()
    
    for trial in range(trials):
        maxA = random.randint(5, 30)
        n = random.randint(4, 8)
        A = sorted(random.sample(range(maxA + 1), min(n, maxA + 1)))
        S = random.randint(maxA, maxA + 10)
        
        max_mod = max(2, min(20, 2 * maxA))
        for L1 in range(2, max_mod + 1):
            for L2 in range(L1 + 1, max_mod + 1):
                if math.gcd(L1, L2) != 1: continue
                M = L1 * L2
                if not (maxA < M <= 2 * maxA): continue
                FA = [f_k(a, S, [L1, L2]) for a in A]
                sidon = is_sidon(FA)
                mod_counts[(L1, L2)] += 1
                if sidon:
                    mod_sidon[(L1, L2)] += 1
    
    print("\n=== Modulus Size Preference ===")
    print(f"{'Moduli':>10} {'Trials':>8} {'Sidon':>8} {'Rate':>8}")
    print("-" * 38)
    sorted_mods = sorted(mod_counts.items(), key=lambda x: x[1], reverse=True)
    for (L1, L2), count in sorted_mods[:15]:
        sidon_count = mod_sidon.get((L1, L2), 0)
        pct = sidon_count / count * 100
        print(f"[{L1:>2},{L2:>2}] {count:>8} {sidon_count:>8} {pct:>7.1f}%")

# ---------- Multi-step Analysis ----------

def analyze_multi_step_needed(trials=300):
    """For sets that fail one-step, analyze multi-step depth."""
    print("\n=== Multi-Step Analysis ===")
    from iteration_dag import IterationDAG, AdaptiveRule
    
    one_step_only = 0
    multi_step = 0
    no_path = 0
    
    for trial in range(trials):
        maxA = random.randint(5, 30)
        n = random.randint(4, 7)
        A = sorted(random.sample(range(maxA + 1), min(n, maxA + 1)))
        S = random.randint(maxA, maxA + 10)
        
        # Check one-step
        mods = []
        for L1 in range(2, 15):
            for L2 in range(L1 + 1, 15):
                if math.gcd(L1, L2) != 1: continue
                M = L1 * L2
                if not (maxA < M <= 2 * maxA): continue
                FA = [f_k(a, S, [L1, L2]) for a in A]
                if is_sidon(FA):
                    mods.append((L1, L2))
        
        if mods:
            one_step_only += 1
            continue
        
        # Check multi-step
        rule = AdaptiveRule(max_val=12)
        dag = IterationDAG(A, S, rule, max_steps=3, max_branch=30)
        dag.build()
        
        if dag.sidon_paths:
            multi_step += 1
        else:
            no_path += 1
    
    print(f"  One-step success: {one_step_only}/{trials}")
    print(f"  Multi-step only: {multi_step}/{trials}")
    print(f"  No path found: {no_path}/{trials}")

# ---------- Main ----------

if __name__ == "__main__":
    # Detailed failure analysis for known examples
    print("=" * 60)
    print("DEEP TUNING: FAILURE ANALYSIS")
    print("=" * 60)
    
    print("\n--- Sidon Example: A=[1,2,5,6], S=7 ---")
    rows = detailed_modulus_report([1,2,5,6], 7)
    for r in rows:
        status = "✓ SIDON" if r['sidon'] else "✗ FAIL"
        g = "guaranteed" if r['guaranteed'] else "not-guaranteed"
        print(f"  [{r['L1']},{r['L2']}] M={r['M']} {status} ({g})")
        if r['failures']:
            for f in r['failures'][:2]:
                print(f"    {f}")
    
    print("\n--- Complex Set: A=[0,1,3,8,13], S=27 ---")
    rows = detailed_modulus_report([0,1,3,8,13], 27)
    for r in rows[:8]:
        status = "✓ SIDON" if r['sidon'] else "✗ FAIL"
        print(f"  [{r['L1']},{r['L2']}] M={r['M']} {status}")
        if r['failures']:
            for f in r['failures'][:3]:
                print(f"    {f}")
    
    analyze_optimal_M_trend(300)
    analyze_modulus_size_preference(300)
    analyze_multi_step_needed(200)
