"""Exact calibration decisions, detector-aware feasible sets and source bounds."""
from dataclasses import dataclass
from fractions import Fraction as F
import math
import mpmath as mp
from models import FounderPreparation


def binomial_cdf(n,k,p):
    p=F(p)
    if not isinstance(n,int) or not 0<=n<=10000 or not 0<=p<=1:raise ValueError('Exact binomial calculation supports integer n in [0,10000]')
    if k<0:return F(0)
    if k>=n or p==0:return F(1)
    if p==1:return F(0)
    a,b=p.numerator,p.denominator;term=(b-a)**n;total=term
    for j in range(k):
        term,remainder=divmod(a*(n-j)*term,(b-a)*(j+1))
        if remainder:raise ArithmeticError('Noninteger binomial recurrence')
        total+=term
    return F(total,b**n)


@dataclass(frozen=True)
class MenuRule:
    event: str='three_or_four'

    def __post_init__(self):
        if self.event not in ['three_or_four','at_most_two']:raise ValueError('Unknown predeclared event')

    def threshold(self,n):
        return (33*n+127)//128 if self.event=='three_or_four' else (19*n-1)//32

    def choose(self,event_count,n,perfect_counts=False):
        if not isinstance(n,int) or not isinstance(event_count,int) or n<1 or not 0<=event_count<=n:raise ValueError('Invalid calibration count')
        if not perfect_counts:return dict(cutoff=7,status='CONSERVATIVE_FALLBACK',reason='Perfect-count calibration contract not asserted')
        small=event_count>=self.threshold(n) if self.event=='three_or_four' else event_count<=self.threshold(n)
        return dict(cutoff=6 if small else 7,status='CONDITIONAL_MENU_DECISION',threshold=self.threshold(n))

    def exact_errors(self,n):
        if not isinstance(n,int) or n<1:raise ValueError('Positive integer calibration size required')
        threshold=self.threshold(n)
        if self.event=='three_or_four':
            ew=1-binomial_cdf(n,threshold-1,F(7,32));ei=binomial_cdf(n,threshold-1,F(19,64))
        else:
            ew=binomial_cdf(n,threshold,F(5,8));ei=1-binomial_cdf(n,threshold,F(9,16))
        w=max(F(121,128),F(31,32)-ew)
        return dict(W_error=ew,I_error=ei,uniform_coverage=min(F(245,256),w),
                    independent_future_W=F(31,32)-F(3,128)*ew,
                    independent_future_I=F(245,256)+F(5,256)*ei)


def dependence_decision(successes,n,beta=F(3,160)):
    """Exact inversion at theta=1/4; six only if the lower confidence endpoint reaches 1/4."""
    if not 0<beta<1 or n<1 or not 0<=successes<=n:raise ValueError('Invalid confidence inputs')
    tail=1-binomial_cdf(n,successes-1,F(1,4))
    return dict(cutoff=6 if tail<=beta else 7,null_tail=tail,
                coverage=min(F(19,20),F(31,32)-beta),
                meaning='Seven is a fallback, not evidence that dependence exceeds 3/5')


def hoeffding_width(n,beta,scale=1):
    """Exact rational upper endpoint of an outward interval evaluation of the radius."""
    if n<1 or not 0<beta<1 or scale<=0:raise ValueError('Invalid concentration allowance')
    mp.iv.dps=40;b=mp.iv.mpf(beta.numerator)/beta.denominator
    value=scale*mp.iv.sqrt(mp.iv.ln(2/b)/(2*n))
    sign,mantissa,exponent,_=value._mpi_[1]
    return F((-1)**sign*mantissa)*(F(2)**exponent)


@dataclass(frozen=True)
class DetectorFeasibleRule:
    truncation: int=10
    z: F=F(1,2)
    beta_mean: F=F(3,512)
    beta_pgf: F=F(3,512)

    def __post_init__(self):
        if self.truncation<2 or not 0<self.z<1 or not 0<self.beta_mean<1 or not 0<self.beta_pgf<1:
            raise ValueError('Invalid detector-aware rule')

    def evaluate(self,counts):
        if not counts or any(not isinstance(v,int) or v<0 or v>10000 for v in counts):raise ValueError('Complete nonnegative integer counts up to 10000 required')
        n=len(counts);L=self.truncation
        mean=F(sum(min(y,L) for y in counts),n);pgf=sum((self.z**y for y in counts),F(0))/n
        a=hoeffding_width(n,self.beta_mean,L);e=hoeffding_width(n,self.beta_pgf)
        remainder=F(L+2,2**L);lo=max(F(0),(mean-a)/3);hi=min(F(1),(mean+a+remainder)/3)
        retained=[];ranges={}
        if lo<=hi:
            for name,rho in [('I',F(0)),('W',F(1))]:
                model=FounderPreparation(rho);bottom=model.pgf(self.z,detection=hi);top=model.pgf(self.z,detection=lo)
                ranges[name]=(bottom,top)
                if max(bottom,pgf-e)<=min(top,pgf+e):retained.append(name)
        cutoff=6 if retained==['I'] else 7
        return dict(cutoff=cutoff,retained_models=retained,detector_interval=(lo,hi) if lo<=hi else None,
                    statistic_mean=mean,statistic_pgf=pgf,mean_radius=a,pgf_radius=e,model_ranges=ranges,
                    status='CONDITIONAL_FEASIBLE_SET' if retained else 'INCOMPATIBLE_STATISTICS_FALLBACK',
                    uniform_coverage=max(F(0),min(F(245,256),F(31,32)-self.beta_mean-self.beta_pgf)))


@dataclass(frozen=True)
class ObservationBudget:
    multiple_founder: F=F(1,1000)
    false_object: F=F(1,100)

    def __post_init__(self):
        if not 0<=self.multiple_founder<=1 or not 0<=self.false_object<=1:raise ValueError('Budgets must lie in [0,1]')

    def recorded(self,single_founder_lower):
        return max(F(0),(1-self.multiple_founder)*single_founder_lower-self.false_object)


@dataclass(frozen=True)
class InheritedRateClass:
    radius: F=F(1,2000)
    founder_center: F=F(5,24)
    founder_radius: F=F(1,1000)

    def __post_init__(self):
        if not 0<=self.radius<F(1,10) or self.founder_radius<0 or not 0<=self.founder_center-self.founder_radius<=self.founder_center+self.founder_radius<=1:
            raise ValueError('Unsupported successful-history class')

    def contains(self,rates):
        reference=(F(0),F(3,10),F(1,1000),F(1,10),F(0),F(1,100000))
        rates=tuple(F(str(v)) for v in rates)
        return len(rates)==6 and min(rates)>=0 and all(sum(abs(rates[i]-reference[i]) for i in indices)<=self.radius for indices in [range(3),range(3,6)])

    def source_lower(self,cutoff):
        if not isinstance(cutoff,int) or not 1<=cutoff<=100:raise ValueError('Endpoint in [1,100] required')
        # T0=log(2)/.10001 <= 7; exp(-Q_R*T0) in [.5*(1-7 radius), .5].
        a=(F(3,10)-self.radius)/(F(301,1000)+self.radius)
        c=(F(1,10)-self.radius)/(F(10001,100000)+self.radius)
        E=(1-7*self.radius)/2
        r=E*sum(((c/2)**j for j in range(cutoff)),F(0))
        mixtures=[(1-p)*a+p*r for p in [self.founder_center-self.founder_radius,self.founder_center+self.founder_radius]]
        return dict(slow=a,resistant=r,lower=min(mixtures),scope='Fixed reference horizon; per-type l1 class; arbitrary conditional static-environment mixing')

    @staticmethod
    def reference_endpoint_two_failure_lower():
        return F(5,48)*sum(((F(10000,10001)/2)**j for j in range(2,7)),F(0))


def scalar_finite_certificate():
    scale=10**6;row=[scale]*5+[0,0];retained=[]
    for step in range(16):
        retained.append(row[1])
        row=[((5000-751*i)*row[i]+501*i*row[i+1]+250*i*row[max(i-1,0)])//5000 for i in range(6)]+[0]
    lower=sum((F(7**j*retained[j],math.factorial(j)) for j in range(16)),F(0))/(1097*scale)
    # Fresh upper bound on e using a factorial tail, and thus exp(7)<1097.
    e_upper=sum((F(1,math.factorial(j)) for j in range(13)),F(0))+F(14,13*math.factorial(13))
    assert e_upper<F(271829,100000) and F(271829,100000)**7<1097
    return dict(rows=retained,lower=lower,compiled_source_lower=F(963,1000))


def unresolved_coverage(covered,unresolved,total):
    if min(covered,unresolved)<0 or total<=0 or covered+unresolved>total:raise ValueError('Invalid outcome ledger')
    return F(covered,total),F(covered+unresolved,total)
