<?php
/**
 * Accounting service v1
 * Adds financial ledger, income/liability report, CSV/Excel export, PDF export and daily accounting channel report.
 */

if(!function_exists('bot_accounting_root')){
    function bot_accounting_root(){ return defined('BOT_ROOT_DIR') ? rtrim((string)BOT_ROOT_DIR, '/') : rtrim(dirname(__DIR__, 2), '/'); }
}
if(!function_exists('bot_accounting_load_jdf')){
    function bot_accounting_load_jdf(){
        if(function_exists('jdate') && function_exists('jalali_to_gregorian')) return true;
        $p = bot_accounting_root().'/lib/jdf.php';
        if(is_file($p)) { @require_once $p; }
        return function_exists('jdate') && function_exists('jalali_to_gregorian');
    }
}
if(!function_exists('bot_accounting_digits_en')){
    function bot_accounting_digits_en($v){
        return strtr((string)$v, ['۰'=>'0','۱'=>'1','۲'=>'2','۳'=>'3','۴'=>'4','۵'=>'5','۶'=>'6','۷'=>'7','۸'=>'8','۹'=>'9','٠'=>'0','١'=>'1','٢'=>'2','٣'=>'3','٤'=>'4','٥'=>'5','٦'=>'6','٧'=>'7','٨'=>'8','٩'=>'9']);
    }
}
if(!function_exists('bot_accounting_today_jalali')){
    function bot_accounting_today_jalali($format='Y/m/d'){
        bot_accounting_load_jdf();
        return function_exists('jdate') ? jdate($format, time(), '', 'Asia/Tehran', 'fa') : date($format);
    }
}
if(!function_exists('bot_accounting_date_key')){
    function bot_accounting_date_key($ts=null){
        $ts = $ts === null ? time() : (int)$ts;
        bot_accounting_load_jdf();
        return function_exists('jdate') ? jdate('Y-m-d', $ts, '', 'Asia/Tehran', 'en') : date('Y-m-d', $ts);
    }
}
if(!function_exists('bot_accounting_file_stamp')){
    function bot_accounting_file_stamp($ts=null){
        $ts = $ts === null ? time() : (int)$ts;
        bot_accounting_load_jdf();
        return function_exists('jdate') ? jdate('Ymd-His', $ts, '', 'Asia/Tehran', 'en') : date('Ymd-His', $ts);
    }
}
if(!function_exists('bot_accounting_parse_filter_date')){
    function bot_accounting_parse_filter_date($v, $end=false){
        $v = trim(bot_accounting_digits_en($v));
        if($v === '') return 0;
        $v = preg_replace('/\s+/', ' ', $v);
        if(!preg_match('/^(\d{4})[\/\-.](\d{1,2})[\/\-.](\d{1,2})(?:\s+(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?)?$/', $v, $m)){
            $t = strtotime($v.($end ? ' 23:59:59' : ' 00:00:00'));
            return $t ? (int)$t : 0;
        }
        $y=(int)$m[1]; $mo=(int)$m[2]; $d=(int)$m[3];
        $hh = isset($m[4]) && $m[4] !== '' ? (int)$m[4] : ($end ? 23 : 0);
        $mi = isset($m[5]) && $m[5] !== '' ? (int)$m[5] : ($end ? 59 : 0);
        $ss = isset($m[6]) && $m[6] !== '' ? (int)$m[6] : ($end ? 59 : 0);
        if($y >= 1200 && $y <= 1700 && bot_accounting_load_jdf() && function_exists('jalali_to_gregorian')){
            [$gy,$gm,$gd] = jalali_to_gregorian($y,$mo,$d);
            return (int)strtotime(sprintf('%04d-%02d-%02d %02d:%02d:%02d',$gy,$gm,$gd,$hh,$mi,$ss));
        }
        return (int)strtotime(sprintf('%04d-%02d-%02d %02d:%02d:%02d',$y,$mo,$d,$hh,$mi,$ss));
    }
}
if(!function_exists('bot_accounting_h')){
    function bot_accounting_h($v){ return htmlspecialchars((string)$v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
}
if(!function_exists('bot_accounting_amount')){
    function bot_accounting_amount($v){
        if(is_array($v) || is_object($v)) return 0;
        $s = strtr((string)$v, ['۰'=>'0','۱'=>'1','۲'=>'2','۳'=>'3','۴'=>'4','۵'=>'5','۶'=>'6','۷'=>'7','۸'=>'8','۹'=>'9','٠'=>'0','١'=>'1','٢'=>'2','٣'=>'3','٤'=>'4','٥'=>'5','٦'=>'6','٧'=>'7','٨'=>'8','٩'=>'9']);
        $s = preg_replace('/[^0-9\-]/', '', $s);
        return (int)$s;
    }
}
if(!function_exists('bot_accounting_ts')){
    function bot_accounting_ts($v){
        if(is_int($v) || ctype_digit((string)$v)){ $n=(int)$v; return $n > 1000000000000 ? (int)floor($n/1000) : $n; }
        $s = trim((string)$v); if($s==='') return 0;
        $s = strtr($s, ['۰'=>'0','۱'=>'1','۲'=>'2','۳'=>'3','۴'=>'4','۵'=>'5','۶'=>'6','۷'=>'7','۸'=>'8','۹'=>'9','٠'=>'0','١'=>'1','٢'=>'2','٣'=>'3','٤'=>'4','٥'=>'5','٦'=>'6','٧'=>'7','٨'=>'8','٩'=>'9']);
        $t = strtotime($s); return $t ? (int)$t : 0;
    }
}
if(!function_exists('bot_accounting_date')){
    function bot_accounting_date($ts){
        $ts=(int)$ts; if($ts<=0) return '-';
        bot_accounting_load_jdf();
        if(function_exists('jdate')) return jdate('Y/m/d H:i', $ts, '', 'Asia/Tehran', 'fa');
        return date('Y/m/d H:i', $ts);
    }
}
if(!function_exists('bot_accounting_money')){
    function bot_accounting_money($n){ return number_format((int)$n); }
}
if(!function_exists('bot_accounting_json_read')){
    function bot_accounting_json_read($rel, $default=[]){
        if(function_exists('bot_data_get_json')){ $v = bot_data_get_json($rel, null); if(is_array($v)) return $v; }
        $p = bot_accounting_root().'/'.ltrim((string)$rel,'/');
        if(!is_file($p)) return $default;
        $j = json_decode((string)@file_get_contents($p), true);
        return is_array($j) ? $j : $default;
    }
}
if(!function_exists('bot_accounting_json_write')){
    function bot_accounting_json_write($rel, array $data){
        if(function_exists('bot_data_put_json')) return bot_data_put_json($rel, $data);
        $p = bot_accounting_root().'/'.ltrim((string)$rel,'/');
        $dir=dirname($p); if(!is_dir($dir)) @mkdir($dir,0775,true);
        return @file_put_contents($p, json_encode($data, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT), LOCK_EX) !== false;
    }
}
if(!function_exists('bot_accounting_settings')){
    function bot_accounting_settings(){
        $def = [
            'enabled'=>'✅فعال',
            'daily_channel_enabled'=>'❌غیر فعال',
            'channel_id'=>'',
            'last_daily_date'=>'',
            'updated_at'=>time(),
        ];
        $s = bot_accounting_json_read('lib/kodam/accounting_settings.json', $def);
        if(!is_array($s)) $s=[];
        return array_merge($def, $s);
    }
}
if(!function_exists('bot_accounting_save_settings')){
    function bot_accounting_save_settings(array $patch){
        $s = bot_accounting_settings();
        foreach($patch as $k=>$v) $s[$k]=$v;
        $s['updated_at']=time();
        return bot_accounting_json_write('lib/kodam/accounting_settings.json', $s);
    }
}
if(!function_exists('bot_accounting_reason_title')){
    function bot_accounting_reason_title($reason){
        $r = trim((string)$reason);
        $map = [
            'manual_card_shop'=>'خرید کارت‌به‌کارت', 'zibal_shop'=>'خرید زیبال', 'zarinpal_shop'=>'خرید زرین‌پال قدیمی',
            'manual_admin'=>'شارژ دستی ادمین', 'admin_add'=>'شارژ دستی ادمین', 'admin_gift'=>'هدیه مدیریت',
            'daily_reward'=>'هدیه روزانه', 'gift_code'=>'کد هدیه', 'referral'=>'پاداش زیرمجموعه', 'referral_forced_join_bonus'=>'پاداش جوین اجباری زیرمجموعه',
            'left_penalty'=>'جریمه لفت', 'order'=>'مصرف برای سفارش', 'order_refund'=>'برگشت سفارش', 'transfer_in'=>'دریافت انتقال', 'transfer_out'=>'ارسال انتقال'
        ];
        return $map[$r] ?? ($r !== '' ? $r : 'نامشخص');
    }
}
if(!function_exists('bot_accounting_row')){
    function bot_accounting_row(array $r){
        $ts = bot_accounting_ts($r['time'] ?? ($r['created_at'] ?? time()));
        return [
            'time'=>$ts,
            'date'=>bot_accounting_date($ts),
            'user_id'=>(string)($r['user_id'] ?? ''),
            'type'=>(string)($r['type'] ?? 'ledger'),
            'title'=>(string)($r['title'] ?? ''),
            'method'=>(string)($r['method'] ?? ''),
            'money_in'=>bot_accounting_amount($r['money_in'] ?? 0),
            'money_out'=>bot_accounting_amount($r['money_out'] ?? 0),
            'diamond_delta'=>bot_accounting_amount($r['diamond_delta'] ?? 0),
            'balance_before'=>isset($r['balance_before']) ? bot_accounting_amount($r['balance_before']) : null,
            'balance_after'=>isset($r['balance_after']) ? bot_accounting_amount($r['balance_after']) : null,
            'ref'=>(string)($r['ref'] ?? ''),
            'actor_id'=>(string)($r['actor_id'] ?? ''),
            'description'=>(string)($r['description'] ?? ''),
            'source'=>(string)($r['source'] ?? ''),
        ];
    }
}
if(!function_exists('bot_accounting_collect_payment_rows')){
    function bot_accounting_collect_payment_rows($fromTs=0,$toTs=0){
        $rows=[];
        if(!function_exists('bot_payment_report_collect')) return $rows;
        $rep = bot_payment_report_collect((int)$fromTs,(int)$toTs,false);
        foreach(($rep['rows'] ?? []) as $p){
            if(function_exists('bot_payment_report_is_paid') && !bot_payment_report_is_paid($p['status'] ?? '')) continue;
            $ts=(int)($p['paid_at'] ?: ($p['updated_at'] ?: $p['created_at']));
            $src=(string)($p['source'] ?? '');
            $title='درآمد پرداخت موفق';
            if($src==='zibal') $title='درآمد زیبال';
            elseif($src==='card') $title='درآمد کارت‌به‌کارت';
            elseif($src==='zarinpal') $title='درآمد زرین‌پال قدیمی';
            $rows[] = bot_accounting_row([
                'time'=>$ts,
                'user_id'=>$p['user_id'] ?? '',
                'type'=>'payment',
                'title'=>$title,
                'method'=>function_exists('bot_payment_report_source_title') ? bot_payment_report_source_title($src) : $src,
                'money_in'=>$p['amount'] ?? 0,
                'diamond_delta'=>$p['reward'] ?? 0,
                'ref'=>$p['ref'] ?: ($p['id'] ?? ''),
                'description'=>$p['item'] ?? '',
                'source'=>'payment_report:'.$src,
            ]);
        }
        return $rows;
    }
}
if(!function_exists('bot_accounting_sql_table_exists')){
    function bot_accounting_sql_table_exists($pdo,$table){ try{ $st=$pdo->prepare('SHOW TABLES LIKE ?'); $st->execute([(string)$table]); return (bool)$st->fetchColumn(); }catch(Throwable $e){ return false; } }
}
if(!function_exists('bot_accounting_collect_wallet_ledger')){
    function bot_accounting_collect_wallet_ledger($fromTs=0,$toTs=0){
        $rows=[]; $seen=[];
        if(function_exists('bot_sql_pdo') && ($pdo=bot_sql_pdo())){
            try{
                if(bot_accounting_sql_table_exists($pdo,'bot_wallet_ledger')){
                    $where=[]; $args=[];
                    if((int)$fromTs>0){ $where[]='created_at >= FROM_UNIXTIME(?)'; $args[]=(int)$fromTs; }
                    if((int)$toTs>0){ $where[]='created_at <= FROM_UNIXTIME(?)'; $args[]=(int)$toTs; }
                    $sql='SELECT * FROM bot_wallet_ledger'.($where?' WHERE '.implode(' AND ',$where):'').' ORDER BY created_at DESC,id DESC LIMIT 5000';
                    $st=$pdo->prepare($sql); $st->execute($args);
                    foreach($st->fetchAll(PDO::FETCH_ASSOC) as $r){
                        $reason=(string)($r['reason'] ?? '');
                        $delta=(int)($r['delta_amount'] ?? 0);
                        $key='sql:'.($r['id'] ?? md5(json_encode($r)));
                        $seen[$key]=true;
                        $rows[] = bot_accounting_row([
                            'time'=>$r['created_at'] ?? '',
                            'user_id'=>$r['user_id'] ?? '',
                            'type'=>'wallet',
                            'title'=>bot_accounting_reason_title($reason),
                            'method'=>'کیف پول/الماس',
                            'diamond_delta'=>$delta,
                            'balance_before'=>$r['balance_before'] ?? null,
                            'balance_after'=>$r['balance_after'] ?? null,
                            'ref'=>trim((string)($r['ref_type'] ?? '').':'.(string)($r['ref_id'] ?? ''), ':'),
                            'actor_id'=>$r['actor_id'] ?? '',
                            'description'=>$reason,
                            'source'=>'bot_wallet_ledger',
                        ]);
                    }
                }
            }catch(Throwable $e){}
        }
        // JSON user wallet logs fallback
        $root = bot_accounting_root();
        $files = glob($root.'/melat/*.json') ?: [];
        foreach($files as $f){
            $uid = basename($f,'.json');
            $j = json_decode((string)@file_get_contents($f), true);
            if(!is_array($j) || empty($j['wallet_log']) || !is_array($j['wallet_log'])) continue;
            foreach($j['wallet_log'] as $i=>$w){
                if(!is_array($w)) continue;
                $ts = bot_accounting_ts($w['time'] ?? ($w['created_at'] ?? 0));
                if((int)$fromTs>0 && $ts>0 && $ts<(int)$fromTs) continue;
                if((int)$toTs>0 && $ts>0 && $ts>(int)$toTs) continue;
                $reason=(string)($w['reason'] ?? '');
                $delta=bot_accounting_amount($w['delta'] ?? ($w['delta_amount'] ?? 0));
                $key='json:'.$uid.':'.$ts.':'.$delta.':'.$reason.':'.$i;
                if(isset($seen[$key])) continue;
                $rows[] = bot_accounting_row([
                    'time'=>$ts,
                    'user_id'=>$uid,
                    'type'=>'wallet',
                    'title'=>bot_accounting_reason_title($reason),
                    'method'=>'کیف پول/الماس',
                    'diamond_delta'=>$delta,
                    'balance_before'=>$w['before'] ?? null,
                    'balance_after'=>$w['after'] ?? null,
                    'ref'=>$w['ref'] ?? ($w['payment_id'] ?? ($w['order_id'] ?? '')),
                    'actor_id'=>$w['admin_id'] ?? ($w['actor_id'] ?? ''),
                    'description'=>$reason,
                    'source'=>'melat.wallet_log',
                ]);
            }
        }
        return $rows;
    }
}
if(!function_exists('bot_accounting_collect_users_liability')){
    function bot_accounting_collect_users_liability(){
        $total=0; $count=0; $withBalance=0; $top=[];
        if(function_exists('bot_sql_pdo') && ($pdo=bot_sql_pdo())){
            try{
                if(bot_accounting_sql_table_exists($pdo,'bot_users')){
                    $row=$pdo->query('SELECT COUNT(*) c, COALESCE(SUM(points),0) s, SUM(CASE WHEN points>0 THEN 1 ELSE 0 END) wb FROM bot_users')->fetch(PDO::FETCH_ASSOC);
                    if($row){ $count=(int)$row['c']; $total=(int)$row['s']; $withBalance=(int)$row['wb']; }
                    $st=$pdo->query('SELECT user_id, points FROM bot_users WHERE points>0 ORDER BY points DESC LIMIT 10');
                    foreach($st->fetchAll(PDO::FETCH_ASSOC) as $r) $top[]=['user_id'=>(string)$r['user_id'],'points'=>(int)$r['points']];
                    return ['users'=>$count,'with_balance'=>$withBalance,'total_points'=>$total,'top'=>$top,'source'=>'sql'];
                }
            }catch(Throwable $e){}
        }
        foreach(glob(bot_accounting_root().'/melat/*.json') ?: [] as $f){
            $j=json_decode((string)@file_get_contents($f), true); if(!is_array($j)) continue;
            $count++; $p=(int)($j['Points'] ?? 0); $total += $p; if($p>0){ $withBalance++; $top[]=['user_id'=>basename($f,'.json'),'points'=>$p]; }
        }
        usort($top,function($a,$b){return $b['points']<=>$a['points'];}); $top=array_slice($top,0,10);
        return ['users'=>$count,'with_balance'=>$withBalance,'total_points'=>$total,'top'=>$top,'source'=>'json'];
    }
}
if(!function_exists('bot_accounting_estimated_point_rate')){
    function bot_accounting_estimated_point_rate(array $paymentReport=null){
        if($paymentReport===null && function_exists('bot_payment_report_collect')) $paymentReport=bot_payment_report_collect(0,0,false);
        $amount=0; $reward=0;
        foreach(($paymentReport['rows'] ?? []) as $r){
            if(function_exists('bot_payment_report_is_paid') && !bot_payment_report_is_paid($r['status'] ?? '')) continue;
            $a=(int)($r['amount'] ?? 0); $d=(int)($r['reward'] ?? 0);
            if($a>0 && $d>0){ $amount += $a; $reward += $d; }
        }
        if($amount>0 && $reward>0) return $amount / max(1,$reward);
        if(function_exists('bot_simple_shop_packages')){
            $rates=[]; foreach(bot_simple_shop_packages() as $p){ $a=(int)($p['amount']??0); $c=(int)($p['coins']??0); if($a>0 && $c>0) $rates[]=$a/$c; }
            if($rates) return array_sum($rates)/count($rates);
        }
        return 0.0;
    }
}
if(!function_exists('bot_accounting_collect')){
    function bot_accounting_collect($fromTs=0,$toTs=0){
        $paymentReport = function_exists('bot_payment_report_collect') ? bot_payment_report_collect((int)$fromTs,(int)$toTs,false) : ['rows'=>[],'totals'=>[]];
        $ledgerRows = array_merge(bot_accounting_collect_payment_rows($fromTs,$toTs), bot_accounting_collect_wallet_ledger($fromTs,$toTs));
        usort($ledgerRows,function($a,$b){ return ((int)$b['time']) <=> ((int)$a['time']); });
        $tot=[
            'money_in'=>0,'money_out'=>0,'zibal_income'=>0,'card_income'=>0,'payment_count'=>0,'diamond_sold'=>0,
            'diamond_plus'=>0,'diamond_minus'=>0,'admin_gifts'=>0,'referral_rewards'=>0,'penalties'=>0,'orders_spent'=>0,
        ];
        foreach($ledgerRows as $r){
            $tot['money_in'] += (int)$r['money_in']; $tot['money_out'] += (int)$r['money_out'];
            if($r['type']==='payment'){
                $tot['payment_count']++;
                $tot['diamond_sold'] += max(0,(int)$r['diamond_delta']);
                if(mb_strpos($r['method'],'زیبال',0,'UTF-8')!==false) $tot['zibal_income'] += (int)$r['money_in'];
                elseif(mb_strpos($r['method'],'کارت',0,'UTF-8')!==false) $tot['card_income'] += (int)$r['money_in'];
            }else{
                $d=(int)$r['diamond_delta']; if($d>0) $tot['diamond_plus'] += $d; if($d<0) $tot['diamond_minus'] += abs($d);
                $desc=(string)$r['description'];
                if(strpos($desc,'referral')!==false) $tot['referral_rewards'] += max(0,$d);
                if(strpos($desc,'gift')!==false || strpos($desc,'admin')!==false) $tot['admin_gifts'] += max(0,$d);
                if(strpos($desc,'left')!==false || strpos($desc,'penalty')!==false) $tot['penalties'] += abs(min(0,$d));
                if(strpos($desc,'order')!==false) $tot['orders_spent'] += abs(min(0,$d));
            }
        }
        $liab = bot_accounting_collect_users_liability();
        $rate = bot_accounting_estimated_point_rate($paymentReport);
        $liab['estimated_value'] = (int)round(((int)$liab['total_points']) * $rate);
        $liab['point_rate'] = $rate;
        return ['rows'=>$ledgerRows,'totals'=>$tot,'liabilities'=>$liab,'payments'=>$paymentReport,'from'=>(int)$fromTs,'to'=>(int)$toTs,'generated_at'=>time()];
    }
}
if(!function_exists('bot_accounting_report_text')){
    function bot_accounting_report_text(array $report){
        $t=$report['totals']; $l=$report['liabilities'];
        $period = 'امروز';
        if(!empty($report['from']) || !empty($report['to'])) $period = bot_accounting_date($report['from']).' تا '.bot_accounting_date($report['to']);
        return "📊 <b>گزارش حسابداری {$period}</b>\n\n".
            "💰 درآمد کل: <b>".bot_accounting_money($t['money_in'])." ریال</b>\n".
            "💳 زیبال: <b>".bot_accounting_money($t['zibal_income'])." ریال</b>\n".
            "🏦 کارت‌به‌کارت: <b>".bot_accounting_money($t['card_income'])." ریال</b>\n".
            "🧾 تعداد پرداخت موفق: <b>".(int)$t['payment_count']."</b>\n".
            "💎 الماس فروخته‌شده: <b>".bot_accounting_money($t['diamond_sold'])."</b>\n".
            "🎁 الماس افزایشی/هدیه‌ها: <b>".bot_accounting_money($t['diamond_plus'])."</b>\n".
            "⚠️ جریمه/مصرف‌های منفی: <b>".bot_accounting_money($t['diamond_minus'])."</b>\n\n".
            "🏦 <b>تعهدات فعلی ربات</b>\n".
            "👥 کاربران: <b>".bot_accounting_money($l['users'])."</b>\n".
            "💎 مانده کل کاربران: <b>".bot_accounting_money($l['total_points'])."</b>\n".
            "💵 ارزش تقریبی مانده: <b>".bot_accounting_money($l['estimated_value'])." ریال</b>";
    }
}
if(!function_exists('bot_accounting_daily_range')){
    function bot_accounting_daily_range($offsetDays=0){
        $start = strtotime(date('Y-m-d 00:00:00', strtotime(((int)$offsetDays).' days')));
        return [$start, $start+86399];
    }
}
if(!function_exists('bot_accounting_send_daily_channel')){
    function bot_accounting_send_daily_channel($force=false){
        $s = bot_accounting_settings();
        if((string)($s['daily_channel_enabled'] ?? '') !== '✅فعال' && !$force) return ['ok'=>true,'skipped'=>'disabled'];
        $channel = trim((string)($s['channel_id'] ?? ''));
        if($channel==='') return ['ok'=>false,'error'=>'empty_channel'];
        $today = bot_accounting_date_key();
        if(!$force && (string)($s['last_daily_date'] ?? '') === $today) return ['ok'=>true,'skipped'=>'already_sent'];
        [$from,$to]=bot_accounting_daily_range(0);
        $rep=bot_accounting_collect($from,$to);
        $text=bot_accounting_report_text($rep);
        if(!function_exists('bot')) return ['ok'=>false,'error'=>'bot_missing'];
        $res=bot('sendMessage',['chat_id'=>$channel,'text'=>$text,'parse_mode'=>'HTML']);
        $ok=is_object($res)&&!empty($res->ok);
        $log=['time'=>bot_accounting_date(time()),'time_key'=>bot_accounting_date_key(),'ok'=>$ok,'channel'=>$channel,'response'=>$res];
        $dir=bot_accounting_root().'/cache'; if(!is_dir($dir)) @mkdir($dir,0775,true);
        @file_put_contents($dir.'/accounting_daily.log', json_encode($log,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)."\n", FILE_APPEND|LOCK_EX);
        if($ok) bot_accounting_save_settings(['last_daily_date'=>$today]);
        return ['ok'=>$ok,'sent'=>$ok,'channel'=>$channel,'report_totals'=>$rep['totals'],'error'=>$ok?'':(isset($res->description)?(string)$res->description:'send_failed')];
    }
}
if(!function_exists('bot_accounting_csv')){
    function bot_accounting_csv(array $report){
        $out = fopen('php://temp','r+');
        fputcsv($out, ['تاریخ','کاربر','عنوان','روش','ورودی ریال','خروجی ریال','تغییر الماس','موجودی قبل','موجودی بعد','مرجع','ادمین','توضیحات','منبع']);
        foreach($report['rows'] as $r){
            fputcsv($out, [$r['date'],$r['user_id'],$r['title'],$r['method'],$r['money_in'],$r['money_out'],$r['diamond_delta'],$r['balance_before'],$r['balance_after'],$r['ref'],$r['actor_id'],$r['description'],$r['source']]);
        }
        rewind($out); return stream_get_contents($out);
    }
}
if(!function_exists('bot_accounting_pdf_escape')){
    function bot_accounting_pdf_escape($s){ return str_replace(['\\','(',')'], ['\\\\','\\(','\\)'], (string)$s); }
}
if(!function_exists('bot_accounting_generate_pdf')){
    function bot_accounting_generate_pdf(array $report){
        $dir=bot_accounting_root().'/cache/reports'; if(!is_dir($dir)) @mkdir($dir,0775,true);
        $path=$dir.'/accounting-report-'.bot_accounting_file_stamp().'.pdf';
        $lines=[]; $t=$report['totals']; $l=$report['liabilities'];
        $lines[]='Accounting Report';
        $lines[]='Generated (Jalali): '.bot_accounting_date($report['generated_at']);
        $lines[]='Income total: '.bot_accounting_money($t['money_in']).' Rial';
        $lines[]='Zibal: '.bot_accounting_money($t['zibal_income']).' Rial | Card: '.bot_accounting_money($t['card_income']).' Rial';
        $lines[]='Paid payments: '.(int)$t['payment_count'].' | Diamonds sold: '.bot_accounting_money($t['diamond_sold']);
        $lines[]='User balances: '.bot_accounting_money($l['total_points']).' diamonds | Estimated liability: '.bot_accounting_money($l['estimated_value']).' Rial';
        $lines[]=''; $lines[]='Ledger (latest rows)';
        $i=0; foreach($report['rows'] as $r){ if($i++>=350){$lines[]='... truncated'; break;} $lines[]=$r['date'].' | user '.$r['user_id'].' | '.$r['title'].' | money '.bot_accounting_money($r['money_in']).' | diamonds '.$r['diamond_delta'].' | ref '.$r['ref']; }
        $pages=[]; $page=[]; foreach($lines as $ln){ if(count($page)>=58){$pages[]=$page;$page=[];} $page[]=$ln; } if($page)$pages[]=$page;
        $pdf="%PDF-1.4\n"; $offs=[0]; $add=function($body)use(&$pdf,&$offs){$id=count($offs);$offs[$id]=strlen($pdf);$pdf.="$id 0 obj\n$body\nendobj\n";return $id;};
        $font=$add('<< /Type /Font /Subtype /Type1 /BaseFont /Courier >>'); $contentIds=[]; $pageIds=[];
        foreach($pages as $pi=>$pl){ $content="BT\n/F1 9 Tf\n"; $y=805; foreach($pl as $ln){ $content.='1 0 0 1 35 '.$y.' Tm ('.bot_accounting_pdf_escape(substr($ln,0,110)).") Tj\n"; $y-=13; } $content.="ET\n"; $contentIds[]=$add('<< /Length '.strlen($content).' >>' . "\nstream\n".$content."endstream"); }
        foreach($contentIds as $cid){ $pageIds[]=$add('<< /Type /Page /Parent '.(3+count($contentIds)*2).' 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 '.$font.' 0 R >> >> /Contents '.$cid.' 0 R >>'); }
        $kids=implode(' ',array_map(function($id){return $id.' 0 R';},$pageIds)); $pagesId=$add('<< /Type /Pages /Kids ['.$kids.'] /Count '.count($pageIds).' >>'); $cat=$add('<< /Type /Catalog /Pages '.$pagesId.' 0 R >>');
        $xref=strlen($pdf); $pdf.="xref\n0 ".count($offs)."\n0000000000 65535 f \n"; for($i=1;$i<count($offs);$i++) $pdf.=sprintf("%010d 00000 n \n",$offs[$i]); $pdf.="trailer\n<< /Size ".count($offs)." /Root $cat 0 R >>\nstartxref\n$xref\n%%EOF";
        @file_put_contents($path,$pdf,LOCK_EX); return $path;
    }
}
?>
