<?php
declare(strict_types=1);

const BS_ROOT = '/home/mediabyt/public_html';
const BS_DATA = '/home/mediabyt/adstats';
const BS_CACHE = BS_DATA . '/rotation-cache.json';

function bs_clean(mixed $value, int $max = 180): string {
    return substr((string) preg_replace('/[^a-zA-Z0-9._:\/-]/', '', (string) $value), 0, $max);
}
function bs_sites(): array {
    static $sites;
    return $sites ??= (json_decode((string) file_get_contents(__DIR__ . '/sites.json'), true) ?: []);
}
function bs_params(): array {
    $site = strtolower(bs_clean($_GET['site'] ?? '', 120));
    $slot = bs_clean($_GET['slot'] ?? 'email_mid', 60);
    $format = bs_clean($_GET['format'] ?? 'rectangle', 30);
    $campaign = bs_clean($_GET['campaign'] ?? '', 100);
    $send = bs_clean($_GET['send'] ?? '', 100);
    $subscriber = substr((string) preg_replace('/[^a-zA-Z0-9@._+\-]/', '', (string) ($_GET['subscriber'] ?? '')), 0, 254);
    if (!in_array($site, bs_sites(), true) || !in_array($slot, ['email_header','email_mid','email_footer'], true) || !in_array($format, ['leaderboard','rectangle'], true)) {
        http_response_code(422); exit;
    }
    return compact('site','slot','format','campaign','send','subscriber');
}
function bs_config(): array {
    static $config;
    if ($config === null) $config = require BS_DATA . '/config.php';
    return $config;
}
function bs_db(): PDO {
    $c = bs_config();
    $db = new PDO($c['dsn'], $c['user'], $c['password'], [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false,
        PDO::ATTR_TIMEOUT => 2,
    ]);
    $db->exec("SET time_zone = '+00:00'");
    return $db;
}
function bs_manifest(): array {
    static $manifest;
    return $manifest ??= (json_decode((string) file_get_contents(BS_ROOT . '/ads/manifest.json'), true)['creatives'] ?? []);
}
function bs_offer_weight(int|string $offerId): int {
    $offerId=(int)$offerId;
    if($offerId>=400000&&$offerId<500000)return 4;
    return [50001=>40,60001=>25,13057=>40,12651=>40,12731=>30,12801=>25,12664=>25,20001=>25,12759=>15,12929=>10,30001=>8,30002=>8,30003=>8,13063=>6,13080=>5,12776=>4][$offerId] ?? 1;
}
function bs_weighted_offer(array $offers, string $seed): array {
    $total = array_sum(array_map(fn($g) => bs_offer_weight($g[0]['offer_id'] ?? 0), $offers));
    $pick = hexdec(substr(hash('sha256', $seed), 0, 7)) % max(1, $total);
    foreach ($offers as $group) {
        $pick -= bs_offer_weight($group[0]['offer_id'] ?? 0);
        if ($pick < 0) return $group;
    }
    return $offers[array_key_last($offers)];
}
function bs_wilson(int $clicks, int $impressions): array {
    if ($impressions < 1) return [0.0, 1.0];
    $z=1.96; $p=$clicks/$impressions; $z2=$z*$z; $den=1+$z2/$impressions;
    $center=($p+$z2/(2*$impressions))/$den;
    $margin=$z*sqrt(($p*(1-$p)/$impressions)+($z2/(4*$impressions*$impressions)))/$den;
    return [max(0.0,$center-$margin),min(1.0,$center+$margin)];
}
function bs_apply_winners(array $creatives): array {
    $locked=[12651=>'12651-gift-v2',12664=>'12664-offer-v2',12731=>'12731-demo-v2',12759=>'12759-bundle-v2',12776=>'12776-demo-v2',12801=>'12801-routine-v2',12929=>'12929-mechanism-v2',13057=>'13057-detail-v2',13063=>'13063-offer-v2',13080=>'13080-buried-v2'];
    $creatives=array_values(array_filter($creatives,function($c)use($locked){$id=(int)($c['offer_id']??0);return !isset($locked[$id])||($c['creative_id']??'')===$locked[$id];}));
    foreach($creatives as &$c)$c['offer_weight']=bs_offer_weight($c['offer_id']??0);
    unset($c);
    return $creatives;
}
function bs_calculate_active(array $eligible, array $stats): array {
    // Apply the current freshness set before significance pruning so a retired
    // high-volume winner cannot immediately eliminate its replacement.
    $eligible=bs_apply_winners($eligible);
    $byOffer=[];
    foreach($eligible as $c)$byOffer[(string)($c['offer_id']??0)][]=$c;
    $active=[];
    foreach($byOffer as $creatives){
        $ready=true;$leader=null;$leaderCtr=-1.0;
        foreach($creatives as $c){
            $s=$stats[(string)$c['creative_id']]??['impressions'=>0,'clicks'=>0];
            if($s['impressions']<2000)$ready=false;
            $ctr=$s['impressions']>0?$s['clicks']/$s['impressions']:0.0;
            if($ctr>$leaderCtr){$leaderCtr=$ctr;$leader=$c;}
        }
        $ls=$stats[(string)($leader['creative_id']??'')]??['impressions'=>0,'clicks'=>0];
        if(!$ready||$ls['clicks']<40){array_push($active,...$creatives);continue;}
        [$leaderLow]=bs_wilson($ls['clicks'],$ls['impressions']);
        foreach($creatives as $c){
            $s=$stats[(string)$c['creative_id']]??['impressions'=>0,'clicks'=>0];
            [,$high]=bs_wilson($s['clicks'],$s['impressions']);
            if(($c['creative_id']??'')===($leader['creative_id']??'')||$high>=$leaderLow)$active[]=$c;
        }
    }
    return bs_apply_winners($active);
}
function bs_cached_creatives(): array {
    $cached=json_decode((string)@file_get_contents(BS_CACHE),true);
    if(is_array($cached['creatives']??null)&&$cached['creatives'])return $cached['creatives'];
    return bs_apply_winners(bs_manifest());
}
function bs_active_creatives(PDO $db, array $eligible): array {
    $allowed=array_fill_keys(array_map(fn($c)=>(string)$c['creative_id'],bs_cached_creatives()),true);
    return array_values(array_filter($eligible,fn($c)=>isset($allowed[(string)($c['creative_id']??'')])));
}
function bs_assignment_key(array $p): string {
    return hash('sha256',implode('|',[$p['site'],$p['slot'],$p['format'],$p['campaign'],$p['send'],$p['subscriber']]));
}
function bs_select_creative(array $p, ?PDO $db=null): array {
    $key=bs_assignment_key($p);
    $eligible=array_values(array_filter(bs_cached_creatives(),fn($c)=>(empty($c['channels'])||in_array('email',$c['channels'],true))&&(empty($c['formats'])||in_array($p['format'],$c['formats'],true))));
    if(!$eligible)throw new RuntimeException('No eligible creatives');
    if($db){
        try{
            $stmt=$db->prepare('SELECT creative FROM email_assignments WHERE assignment_key=?');$stmt->execute([$key]);$saved=(string)$stmt->fetchColumn();
            foreach($eligible as $c)if(($c['creative_id']??'')===$saved)return[$key,$c];
        }catch(Throwable $e){error_log('assignment read: '.$e->getMessage());}
    }
    $byOffer=[];foreach($eligible as $c)$byOffer[(string)($c['offer_id']??0)][]=$c;
    $group=bs_weighted_offer(array_values($byOffer),$key.'|offer');
    $creative=$group[hexdec(substr(hash('sha256',$key.'|creative'),0,7))%count($group)];
    if($db){
        try{$stmt=$db->prepare('INSERT INTO email_assignments(assignment_key,creative,created_at) VALUES(?,?,UTC_TIMESTAMP()) ON DUPLICATE KEY UPDATE creative=VALUES(creative),created_at=VALUES(created_at)');$stmt->execute([$key,$creative['creative_id']]);}
        catch(Throwable $e){error_log('assignment write: '.$e->getMessage());}
    }
    return[$key,$creative];
}
function bs_creative(PDO $db,array $p):array{return bs_select_creative($p,$db);}
function bs_event(PDO $db,string $event,array $p,array $creative,string $key):void{
    $ua=substr((string)($_SERVER['HTTP_USER_AGENT']??''),0,500);
    $bot=preg_match('/bot|spider|crawler|headless|slurp|preview|facebookexternalhit|bingpreview|barracuda|proofpoint|mimecast|safelinks|urlscan/i',$ua)?1:0;
    $visitor=$p['subscriber']!==''?hash('sha256',strtolower($p['subscriber'])):hash('sha256',$key.'|'.($_SERVER['REMOTE_ADDR']??'').'|'.$ua);
    $stmt=$db->prepare('INSERT INTO events(occurred_at,event,site,slot,placement,format,creative,offer,visitor_hash,referrer,user_agent,is_bot) VALUES(UTC_TIMESTAMP(),?,?,?,?,?,?,?,?,?,?,?)');
    $stmt->execute([$event,$p['site'],$p['slot'],$p['site'].'-'.$p['slot'].'-'.$p['campaign'],$p['format'],$creative['creative_id'],(int)($creative['offer_id']??0),$visitor,'email',$ua,$bot]);
}
function bs_safe_event(?PDO $db,string $event,array $p,array $creative,string $key):void{
    if(!$db)return;
    try{bs_event($db,$event,$p,$creative,$key);}catch(Throwable $e){error_log('event write: '.$e->getMessage());}
}
