<?php
declare(strict_types=1);

namespace iTz\Commerce;

use PDO;
use Throwable;

final class Commerce
{
    private PDO $pdo;
    private string $root;

    public function __construct(PDO $pdo, ?string $root = null)
    {
        $this->pdo = $pdo;
        $this->root = $root ?: dirname(__DIR__, 2);
        $this->ensureSchema();
    }

    public function ensureSchema(): void
    {
        $sql = [
            "CREATE TABLE IF NOT EXISTS itz_store_sites (
                id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                site_name VARCHAR(190) NOT NULL,
                base_url VARCHAR(500) NOT NULL,
                status_name VARCHAR(30) NOT NULL DEFAULT 'active',
                api_key_hash CHAR(64) NOT NULL,
                api_key_prefix VARCHAR(32) NOT NULL,
                secret_enc MEDIUMTEXT NOT NULL,
                allowed_ips TEXT NULL,
                last_seen_at INT UNSIGNED NOT NULL DEFAULT 0,
                last_error TEXT NULL,
                created_at INT UNSIGNED NOT NULL DEFAULT 0,
                updated_at INT UNSIGNED NOT NULL DEFAULT 0,
                UNIQUE KEY uniq_store_url (base_url(190)),
                INDEX idx_store_status (status_name)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
            "CREATE TABLE IF NOT EXISTS itz_store_products (
                id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                title VARCHAR(255) NOT NULL,
                slug VARCHAR(190) NOT NULL,
                description_text MEDIUMTEXT NULL,
                price DECIMAL(18,2) NOT NULL DEFAULT 0,
                currency VARCHAR(20) NOT NULL DEFAULT 'IRT',
                status_name VARCHAR(30) NOT NULL DEFAULT 'active',
                fulfillment_type VARCHAR(40) NOT NULL DEFAULT 'manual',
                core_product_code VARCHAR(190) NULL,
                backend_type VARCHAR(80) NULL,
                backend_ref VARCHAR(190) NULL,
                parameter_schema_json MEDIUMTEXT NULL,
                fulfillment_config_json MEDIUMTEXT NULL,
                stock_count INT NOT NULL DEFAULT -1,
                sort_order INT NOT NULL DEFAULT 100,
                created_at INT UNSIGNED NOT NULL DEFAULT 0,
                updated_at INT UNSIGNED NOT NULL DEFAULT 0,
                UNIQUE KEY uniq_store_slug (slug),
                INDEX idx_store_product_status (status_name,sort_order)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
            "CREATE TABLE IF NOT EXISTS itz_store_customers (
                id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                external_customer_id VARCHAR(190) NULL,
                telegram_id VARCHAR(64) NULL,
                telegram_username VARCHAR(190) NULL,
                full_name VARCHAR(255) NULL,
                email VARCHAR(255) NULL,
                phone VARCHAR(80) NULL,
                meta_json MEDIUMTEXT NULL,
                created_at INT UNSIGNED NOT NULL DEFAULT 0,
                updated_at INT UNSIGNED NOT NULL DEFAULT 0,
                UNIQUE KEY uniq_store_customer_external (external_customer_id),
                INDEX idx_store_customer_tg (telegram_id),
                INDEX idx_store_customer_phone (phone)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
            "CREATE TABLE IF NOT EXISTS itz_store_orders (
                id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                public_id VARCHAR(50) NOT NULL,
                site_id BIGINT UNSIGNED NOT NULL,
                external_order_id VARCHAR(190) NULL,
                customer_id BIGINT UNSIGNED NULL,
                product_id BIGINT UNSIGNED NULL,
                product_title VARCHAR(255) NOT NULL,
                quantity INT UNSIGNED NOT NULL DEFAULT 1,
                unit_price DECIMAL(18,2) NOT NULL DEFAULT 0,
                total_amount DECIMAL(18,2) NOT NULL DEFAULT 0,
                currency VARCHAR(20) NOT NULL DEFAULT 'IRT',
                payment_method VARCHAR(100) NULL,
                payment_ref VARCHAR(255) NULL,
                payment_status VARCHAR(30) NOT NULL DEFAULT 'unpaid',
                status_name VARCHAR(40) NOT NULL DEFAULT 'pending',
                parameters_json MEDIUMTEXT NULL,
                meta_json MEDIUMTEXT NULL,
                paid_at INT UNSIGNED NOT NULL DEFAULT 0,
                created_at INT UNSIGNED NOT NULL DEFAULT 0,
                updated_at INT UNSIGNED NOT NULL DEFAULT 0,
                UNIQUE KEY uniq_store_public (public_id),
                UNIQUE KEY uniq_store_external (site_id,external_order_id),
                INDEX idx_store_order_status (status_name,created_at),
                INDEX idx_store_order_site (site_id,created_at),
                INDEX idx_store_order_customer (customer_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
            "CREATE TABLE IF NOT EXISTS itz_store_order_events (
                id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                order_id BIGINT UNSIGNED NOT NULL,
                event_name VARCHAR(100) NOT NULL,
                actor_type VARCHAR(40) NULL,
                actor_id VARCHAR(190) NULL,
                payload_json MEDIUMTEXT NULL,
                created_at INT UNSIGNED NOT NULL DEFAULT 0,
                INDEX idx_store_event_order (order_id,created_at),
                INDEX idx_store_event_name (event_name,created_at)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
            "CREATE TABLE IF NOT EXISTS itz_store_jobs (
                id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                order_id BIGINT UNSIGNED NOT NULL,
                job_type VARCHAR(80) NOT NULL DEFAULT 'fulfillment',
                status_name VARCHAR(30) NOT NULL DEFAULT 'queued',
                attempts INT UNSIGNED NOT NULL DEFAULT 0,
                payload_json MEDIUMTEXT NULL,
                result_json MEDIUMTEXT NULL,
                last_error TEXT NULL,
                available_at INT UNSIGNED NOT NULL DEFAULT 0,
                created_at INT UNSIGNED NOT NULL DEFAULT 0,
                updated_at INT UNSIGNED NOT NULL DEFAULT 0,
                INDEX idx_store_jobs_queue (status_name,available_at),
                INDEX idx_store_jobs_order (order_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
            "CREATE TABLE IF NOT EXISTS itz_store_settings (
                setting_key VARCHAR(120) PRIMARY KEY,
                setting_value MEDIUMTEXT NULL,
                updated_at INT UNSIGNED NOT NULL DEFAULT 0
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
        ];
        foreach ($sql as $q) $this->pdo->exec($q);
        $this->pdo->prepare("INSERT IGNORE INTO itz_store_settings(setting_key,setting_value,updated_at) VALUES('module_enabled','1',:t)")->execute([':t'=>time()]);
    }

    public function enabled(): bool
    {
        try {
            $v=$this->pdo->query("SELECT setting_value FROM itz_store_settings WHERE setting_key='module_enabled' LIMIT 1")->fetchColumn();
            return $v===false ? true : ((string)$v === '1');
        } catch (Throwable $e) { return true; }
    }

    public function setEnabled(bool $enabled): void
    {
        $s=$this->pdo->prepare("INSERT INTO itz_store_settings(setting_key,setting_value,updated_at) VALUES('module_enabled',:v,:t) ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value),updated_at=VALUES(updated_at)");
        $s->execute([':v'=>$enabled?'1':'0',':t'=>time()]);
    }

    private function keyPath(): string { return $this->root . '/storage/.commerce_master.key'; }
    private function masterKey(): string
    {
        $path=$this->keyPath();
        $dir=dirname($path);
        if (!is_dir($dir)) @mkdir($dir,0700,true);
        if (is_file($path)) {
            $raw=@file_get_contents($path);
            if (is_string($raw) && strlen($raw)===32) return $raw;
        }
        $raw=random_bytes(32);
        $written=@file_put_contents($path,$raw,LOCK_EX);
        if($written!==32) throw new \RuntimeException('storage/ writable نیست؛ کلید امن Commerce ساخته نشد.');
        @chmod($path,0600);
        return $raw;
    }

    public function encrypt(string $plain): string
    {
        $key=$this->masterKey();
        if (function_exists('sodium_crypto_secretbox')) {
            $nonce=random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
            return 's1:'.base64_encode($nonce.sodium_crypto_secretbox($plain,$nonce,$key));
        }
        $iv=random_bytes(12); $tag='';
        $cipher=openssl_encrypt($plain,'aes-256-gcm',$key,OPENSSL_RAW_DATA,$iv,$tag);
        if ($cipher===false) throw new \RuntimeException('Unable to encrypt commerce secret');
        return 'o1:'.base64_encode($iv.$tag.$cipher);
    }

    public function decrypt(string $enc): string
    {
        if ($enc==='') return '';
        $key=$this->masterKey();
        if (str_starts_with($enc,'s1:') && function_exists('sodium_crypto_secretbox_open')) {
            $bin=base64_decode(substr($enc,3),true); if($bin===false) return '';
            $n=SODIUM_CRYPTO_SECRETBOX_NONCEBYTES; $nonce=substr($bin,0,$n); $cipher=substr($bin,$n);
            $plain=sodium_crypto_secretbox_open($cipher,$nonce,$key); return $plain===false?'':$plain;
        }
        if (str_starts_with($enc,'o1:')) {
            $bin=base64_decode(substr($enc,3),true); if($bin===false||strlen($bin)<28)return '';
            $iv=substr($bin,0,12);$tag=substr($bin,12,16);$cipher=substr($bin,28);
            $plain=openssl_decrypt($cipher,'aes-256-gcm',$key,OPENSSL_RAW_DATA,$iv,$tag);return $plain===false?'':$plain;
        }
        return '';
    }

    public static function normalizeUrl(string $url): string
    {
        $url=trim($url);
        if ($url==='' || !preg_match('#^https?://#i',$url)) throw new \InvalidArgumentException('آدرس سایت باید با http:// یا https:// شروع شود.');
        $parts=parse_url($url); if(empty($parts['host'])) throw new \InvalidArgumentException('آدرس سایت معتبر نیست.');
        return rtrim($url,'/');
    }

    public function createSite(string $name,string $url): array
    {
        $url=self::normalizeUrl($url); $name=trim($name)!==''?trim($name):(parse_url($url,PHP_URL_HOST)?:'Website');
        $api='itz_site_'.bin2hex(random_bytes(24)); $secret='itz_wh_'.bin2hex(random_bytes(32)); $now=time();
        $s=$this->pdo->prepare('INSERT INTO itz_store_sites(site_name,base_url,status_name,api_key_hash,api_key_prefix,secret_enc,created_at,updated_at) VALUES(:n,:u,\'active\',:h,:p,:s,:created_at,:updated_at)');
        $s->execute([':n'=>$name,':u'=>$url,':h'=>hash('sha256',$api),':p'=>substr($api,0,16),':s'=>$this->encrypt($secret),':created_at'=>$now,':updated_at'=>$now]);
        return ['id'=>(int)$this->pdo->lastInsertId(),'site_name'=>$name,'base_url'=>$url,'api_key'=>$api,'webhook_secret'=>$secret];
    }

    public function rotateSiteCredentials(int $id): array
    {
        $api='itz_site_'.bin2hex(random_bytes(24));$secret='itz_wh_'.bin2hex(random_bytes(32));
        $s=$this->pdo->prepare('UPDATE itz_store_sites SET api_key_hash=:h,api_key_prefix=:p,secret_enc=:s,updated_at=:t WHERE id=:id');
        $s->execute([':h'=>hash('sha256',$api),':p'=>substr($api,0,16),':s'=>$this->encrypt($secret),':t'=>time(),':id'=>$id]);
        if($s->rowCount()<1) throw new \RuntimeException('سایت پیدا نشد.');
        return ['api_key'=>$api,'webhook_secret'=>$secret];
    }

    public function sites(): array { return $this->pdo->query('SELECT id,site_name,base_url,status_name,api_key_prefix,last_seen_at,last_error,created_at,updated_at FROM itz_store_sites ORDER BY id DESC')->fetchAll(PDO::FETCH_ASSOC)?:[]; }
    public function site(int $id): ?array { $s=$this->pdo->prepare('SELECT * FROM itz_store_sites WHERE id=:id LIMIT 1');$s->execute([':id'=>$id]);$r=$s->fetch(PDO::FETCH_ASSOC);return $r?:null; }
    public function setSiteStatus(int $id,string $status): void { if(!in_array($status,['active','paused'],true))$status='paused';$this->pdo->prepare('UPDATE itz_store_sites SET status_name=:s,updated_at=:t WHERE id=:id')->execute([':s'=>$status,':t'=>time(),':id'=>$id]); }
    public function deleteSite(int $id): void { $this->pdo->prepare('DELETE FROM itz_store_sites WHERE id=:id')->execute([':id'=>$id]); }

    public function authenticate(string $apiKey,string $timestamp,string $signature,string $rawBody): ?array
    {
        if(!$this->enabled() || $apiKey==='' || $timestamp==='' || $signature==='') return null;
        if(!ctype_digit($timestamp) || abs(time()-(int)$timestamp)>300) return null;
        $hash=hash('sha256',$apiKey);
        $s=$this->pdo->prepare("SELECT * FROM itz_store_sites WHERE api_key_hash=:h AND status_name='active' LIMIT 1");$s->execute([':h'=>$hash]);$site=$s->fetch(PDO::FETCH_ASSOC);if(!$site)return null;
        $secret=$this->decrypt((string)$site['secret_enc']); if($secret==='') return null;
        $expected=hash_hmac('sha256',$timestamp."\n".$rawBody,$secret);
        if(!hash_equals($expected,strtolower($signature))) return null;
        $this->pdo->prepare('UPDATE itz_store_sites SET last_seen_at=:t,last_error=NULL WHERE id=:id')->execute([':t'=>time(),':id'=>$site['id']]);
        return $site;
    }

    public function products(bool $onlyActive=false): array
    {
        $sql='SELECT * FROM itz_store_products'.($onlyActive?" WHERE status_name='active'":'').' ORDER BY sort_order ASC,id DESC';
        return $this->pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC)?:[];
    }
    public function product(int $id): ?array { $s=$this->pdo->prepare('SELECT * FROM itz_store_products WHERE id=:id LIMIT 1');$s->execute([':id'=>$id]);$r=$s->fetch(PDO::FETCH_ASSOC);return $r?:null; }
    public function saveProduct(array $d,?int $id=null): int
    {
        $title=trim((string)($d['title']??'')); if($title==='')throw new \InvalidArgumentException('نام محصول الزامی است.');
        $slug=trim((string)($d['slug']??'')); if($slug==='')$slug=preg_replace('/[^a-z0-9]+/i','-',strtolower($title)).'-'.substr(bin2hex(random_bytes(4)),0,6);
        $type=(string)($d['fulfillment_type']??'manual'); if(!in_array($type,['manual','core','api','webhook','stock'],true))$type='manual';
        $params=$d['parameter_schema']??[]; if(is_string($params)){$j=json_decode($params,true);$params=is_array($j)?$j:[];}
        $cfg=$d['fulfillment_config']??[]; if(is_string($cfg)){$j=json_decode($cfg,true);$cfg=is_array($j)?$j:[];}
        $vals=[':title'=>$title,':slug'=>$slug,':desc'=>(string)($d['description_text']??''),':price'=>(float)($d['price']??0),':currency'=>(string)($d['currency']??'IRT'),':status'=>in_array(($d['status_name']??'active'),['active','inactive'],true)?$d['status_name']:'active',':type'=>$type,':core'=>($d['core_product_code']??null)?:null,':bt'=>($d['backend_type']??null)?:null,':br'=>($d['backend_ref']??null)?:null,':params'=>json_encode($params,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),':cfg'=>json_encode($cfg,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),':stock'=>(int)($d['stock_count']??-1),':sort'=>(int)($d['sort_order']??100),':t'=>time()];
        if($id){$vals[':id']=$id;$s=$this->pdo->prepare('UPDATE itz_store_products SET title=:title,slug=:slug,description_text=:desc,price=:price,currency=:currency,status_name=:status,fulfillment_type=:type,core_product_code=:core,backend_type=:bt,backend_ref=:br,parameter_schema_json=:params,fulfillment_config_json=:cfg,stock_count=:stock,sort_order=:sort,updated_at=:t WHERE id=:id');$s->execute($vals);return $id;}
        $insertVals=$vals;$insertVals[':created_at']=$vals[':t'];$insertVals[':updated_at']=$vals[':t'];unset($insertVals[':t']);$s=$this->pdo->prepare('INSERT INTO itz_store_products(title,slug,description_text,price,currency,status_name,fulfillment_type,core_product_code,backend_type,backend_ref,parameter_schema_json,fulfillment_config_json,stock_count,sort_order,created_at,updated_at) VALUES(:title,:slug,:desc,:price,:currency,:status,:type,:core,:bt,:br,:params,:cfg,:stock,:sort,:created_at,:updated_at)');$s->execute($insertVals);return (int)$this->pdo->lastInsertId();
    }
    public function setProductStatus(int $id,string $status): void {$this->pdo->prepare('UPDATE itz_store_products SET status_name=:s,updated_at=:t WHERE id=:id')->execute([':s'=>$status==='active'?'active':'inactive',':t'=>time(),':id'=>$id]);}

    public function addProductParameter(int $id,array $field): array
    {
        $p=$this->product($id);if(!$p)throw new \RuntimeException('محصول پیدا نشد.');
        $schema=json_decode((string)($p['parameter_schema_json']??'[]'),true);if(!is_array($schema))$schema=[];
        $key=preg_replace('/[^a-zA-Z0-9_\-]/','_',trim((string)($field['key']??'')));if($key==='')throw new \InvalidArgumentException('کلید پارامتر معتبر نیست.');
        $new=['key'=>$key,'label'=>trim((string)($field['label']??$key))?:$key,'phase'=>in_array(($field['phase']??'after_payment'),['before_payment','after_payment'],true)?$field['phase']:'after_payment','required'=>!empty($field['required']),'type'=>in_array(($field['type']??'text'),['text','number','email','username','domain','textarea'],true)?$field['type']:'text'];
        $found=false;foreach($schema as &$f){if(is_array($f)&&($f['key']??'')===$key){$f=$new;$found=true;break;}}unset($f);if(!$found)$schema[]=$new;
        $this->pdo->prepare('UPDATE itz_store_products SET parameter_schema_json=:j,updated_at=:t WHERE id=:id')->execute([':j'=>json_encode($schema,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),':t'=>time(),':id'=>$id]);return $new;
    }

    public function upsertCustomer(array $d): ?int
    {
        $ext=trim((string)($d['external_customer_id']??''));$tg=trim((string)($d['telegram_id']??''));$phone=trim((string)($d['phone']??''));$email=trim((string)($d['email']??''));
        if($ext==='' && $tg==='' && $phone==='' && $email==='') return null;
        $existing=null;
        if($ext!==''){$s=$this->pdo->prepare('SELECT id FROM itz_store_customers WHERE external_customer_id=:v LIMIT 1');$s->execute([':v'=>$ext]);$existing=$s->fetchColumn();}
        if(!$existing&&$tg!==''){$s=$this->pdo->prepare('SELECT id FROM itz_store_customers WHERE telegram_id=:v LIMIT 1');$s->execute([':v'=>$tg]);$existing=$s->fetchColumn();}
        $vals=[':ext'=>$ext!==''?$ext:null,':tg'=>$tg!==''?$tg:null,':tu'=>trim((string)($d['telegram_username']??''))?:null,':fn'=>trim((string)($d['full_name']??''))?:null,':em'=>$email!==''?$email:null,':ph'=>$phone!==''?$phone:null,':m'=>json_encode($d['meta']??[],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),':t'=>time()];
        if($existing){$vals[':id']=$existing;$this->pdo->prepare('UPDATE itz_store_customers SET external_customer_id=COALESCE(:ext,external_customer_id),telegram_id=COALESCE(:tg,telegram_id),telegram_username=COALESCE(:tu,telegram_username),full_name=COALESCE(:fn,full_name),email=COALESCE(:em,email),phone=COALESCE(:ph,phone),meta_json=:m,updated_at=:t WHERE id=:id')->execute($vals);return (int)$existing;}
        $insertVals=$vals;$insertVals[':created_at']=$vals[':t'];$insertVals[':updated_at']=$vals[':t'];unset($insertVals[':t']);$this->pdo->prepare('INSERT INTO itz_store_customers(external_customer_id,telegram_id,telegram_username,full_name,email,phone,meta_json,created_at,updated_at) VALUES(:ext,:tg,:tu,:fn,:em,:ph,:m,:created_at,:updated_at)')->execute($insertVals);return (int)$this->pdo->lastInsertId();
    }

    public function createOrder(int $siteId,array $d): array
    {
        $external=trim((string)($d['external_order_id']??''));
        if($external==='') throw new \InvalidArgumentException('external_order_id برای جلوگیری از سفارش تکراری الزامی است.');
        $s=$this->pdo->prepare('SELECT * FROM itz_store_orders WHERE site_id=:s AND external_order_id=:e LIMIT 1');$s->execute([':s'=>$siteId,':e'=>$external]);$old=$s->fetch(PDO::FETCH_ASSOC);if($old)return $this->order((int)$old['id'])??$old;
        $productId=(int)($d['product_id']??0);$p=$productId?$this->product($productId):null;
        $title=$p ? (string)$p['title'] : trim((string)($d['product_title']??''));if($title==='')throw new \InvalidArgumentException('محصول سفارش مشخص نیست.');
        $qty=max(1,(int)($d['quantity']??1));$unit=$p?(float)$p['price']:(float)($d['unit_price']??0);$total=$unit*$qty;
        $customerId=$this->upsertCustomer(is_array($d['customer']??null)?$d['customer']:[]);
        $params=is_array($d['parameters']??null)?$d['parameters']:[];
        $public='ITZ-'.date('ymd').'-'.strtoupper(substr(bin2hex(random_bytes(6)),0,10));$now=time();
        $status=$this->missingRequiredParameters($p,$params,'before_payment')?'awaiting_parameters':'pending';
        $s=$this->pdo->prepare('INSERT INTO itz_store_orders(public_id,site_id,external_order_id,customer_id,product_id,product_title,quantity,unit_price,total_amount,currency,payment_method,payment_status,status_name,parameters_json,meta_json,created_at,updated_at) VALUES(:pub,:site,:ext,:cust,:pid,:title,:qty,:unit,:total,:cur,:pm,\'unpaid\',:st,:params,:meta,:created_at,:updated_at)');
        $s->execute([':pub'=>$public,':site'=>$siteId,':ext'=>$external!==''?$external:null,':cust'=>$customerId,':pid'=>$productId?:null,':title'=>$title,':qty'=>$qty,':unit'=>$unit,':total'=>$total,':cur'=>(string)($p['currency']??($d['currency']??'IRT')),':pm'=>trim((string)($d['payment_method']??''))?:null,':st'=>$status,':params'=>json_encode($params,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),':meta'=>json_encode($d['meta']??[],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),':created_at'=>$now,':updated_at'=>$now]);
        $id=(int)$this->pdo->lastInsertId();$this->event($id,'order.created','website',(string)$siteId,$d);
        return $this->order($id)??[];
    }

    private function missingRequiredParameters(?array $p,array $params,string $phase): bool
    {
        if(!$p)return false;$schema=json_decode((string)($p['parameter_schema_json']??'[]'),true);if(!is_array($schema))return false;
        foreach($schema as $f){if(!is_array($f)||empty($f['required'])||(string)($f['phase']??'before_payment')!==$phase)continue;$key=(string)($f['key']??'');if($key!=='' && trim((string)($params[$key]??''))==='')return true;}return false;
    }

    public function order(int $id): ?array
    {
        $s=$this->pdo->prepare('SELECT o.*,c.telegram_id,c.telegram_username,c.full_name,c.email,c.phone,s.site_name,s.base_url,p.fulfillment_type,p.parameter_schema_json,p.fulfillment_config_json,p.core_product_code,p.backend_type,p.backend_ref FROM itz_store_orders o LEFT JOIN itz_store_customers c ON c.id=o.customer_id LEFT JOIN itz_store_sites s ON s.id=o.site_id LEFT JOIN itz_store_products p ON p.id=o.product_id WHERE o.id=:id LIMIT 1');$s->execute([':id'=>$id]);$r=$s->fetch(PDO::FETCH_ASSOC);return $r?:null;
    }
    public function orderByPublic(string $public,int $siteId=0): ?array {$sql='SELECT id FROM itz_store_orders WHERE public_id=:p'.($siteId?' AND site_id=:s':'').' LIMIT 1';$s=$this->pdo->prepare($sql);$args=[':p'=>$public];if($siteId)$args[':s']=$siteId;$s->execute($args);$id=$s->fetchColumn();return $id?$this->order((int)$id):null;}
    public function recentOrders(int $limit=50): array {$limit=max(1,min(200,$limit));return $this->pdo->query('SELECT o.*,s.site_name,c.telegram_username,c.telegram_id,c.full_name FROM itz_store_orders o LEFT JOIN itz_store_sites s ON s.id=o.site_id LEFT JOIN itz_store_customers c ON c.id=o.customer_id ORDER BY o.id DESC LIMIT '.$limit)->fetchAll(PDO::FETCH_ASSOC)?:[];}

    public function updateParameters(int $orderId,array $params,string $actor='website'): array
    {
        $o=$this->order($orderId);if(!$o)throw new \RuntimeException('سفارش پیدا نشد.');$old=json_decode((string)($o['parameters_json']??'{}'),true);if(!is_array($old))$old=[];$merged=array_merge($old,$params);
        $status=(string)$o['status_name'];$p=$o['product_id']?$this->product((int)$o['product_id']):null;
        if($status==='awaiting_parameters'&&!$this->missingRequiredParameters($p,$merged,$o['payment_status']==='paid'?'after_payment':'before_payment'))$status=$o['payment_status']==='paid'?'processing':'pending';
        $this->pdo->prepare('UPDATE itz_store_orders SET parameters_json=:p,status_name=:s,updated_at=:t WHERE id=:id')->execute([':p'=>json_encode($merged,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),':s'=>$status,':t'=>time(),':id'=>$orderId]);$this->event($orderId,'order.parameters.updated',$actor,null,['keys'=>array_keys($params)]);return $this->order($orderId)??[];
    }

    public function markPayment(int $orderId,bool $success,array $d=[]): array
    {
        $o=$this->order($orderId);if(!$o)throw new \RuntimeException('سفارش پیدا نشد.');$now=time();
        if($success && (string)($o['payment_status']??'')==='paid') return $o;
        if(!$success && (string)($o['payment_status']??'')==='paid') return $o;
        if($success){$p=$o['product_id']?$this->product((int)$o['product_id']):null;$params=json_decode((string)($o['parameters_json']??'{}'),true);if(!is_array($params))$params=[];$status=$this->missingRequiredParameters($p,$params,'after_payment')?'awaiting_parameters':'processing';$this->pdo->prepare("UPDATE itz_store_orders SET payment_status='paid',status_name=:s,payment_method=COALESCE(:m,payment_method),payment_ref=COALESCE(:r,payment_ref),paid_at=:paid_at,updated_at=:updated_at WHERE id=:id")->execute([':s'=>$status,':m'=>trim((string)($d['payment_method']??''))?:null,':r'=>trim((string)($d['payment_ref']??''))?:null,':paid_at'=>$now,':updated_at'=>$now,':id'=>$orderId]);$this->event($orderId,'payment.succeeded','website',null,$d);$this->queueFulfillment($orderId);}
        else{$this->pdo->prepare("UPDATE itz_store_orders SET payment_status='failed',status_name='payment_failed',updated_at=:t WHERE id=:id")->execute([':t'=>$now,':id'=>$orderId]);$this->event($orderId,'payment.failed','website',null,$d);}
        return $this->order($orderId)??[];
    }

    public function setOrderStatus(int $id,string $status,string $actor='admin'): array
    {
        $allowed=['pending','awaiting_parameters','processing','completed','cancelled','refunded','payment_failed','failed'];if(!in_array($status,$allowed,true))throw new \InvalidArgumentException('وضعیت سفارش معتبر نیست.');
        $this->pdo->prepare('UPDATE itz_store_orders SET status_name=:s,updated_at=:t WHERE id=:id')->execute([':s'=>$status,':t'=>time(),':id'=>$id]);$this->event($id,'order.status.'.$status,$actor,null,[]);$o=$this->order($id)??[];$this->pushOrderStatus($o);if($status==='completed')$this->notifyCustomer($o,'completed');return $o;
    }

    private function queueFulfillment(int $orderId): void
    {
        $o=$this->order($orderId);if(!$o)return;if((string)$o['status_name']==='awaiting_parameters')return;
        $payload=['fulfillment_type'=>$o['fulfillment_type']??'manual','product_id'=>$o['product_id']??null,'core_product_code'=>$o['core_product_code']??null,'backend_type'=>$o['backend_type']??null,'backend_ref'=>$o['backend_ref']??null];
        $now=time();$this->pdo->prepare("INSERT INTO itz_store_jobs(order_id,job_type,status_name,payload_json,available_at,created_at,updated_at) VALUES(:o,'fulfillment','queued',:p,:available_at,:created_at,:updated_at)")->execute([':o'=>$orderId,':p'=>json_encode($payload,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),':available_at'=>$now,':created_at'=>$now,':updated_at'=>$now]);
    }

    public function event(int $orderId,string $event,string $actorType='system',?string $actorId=null,array $payload=[]): void
    {
        $this->pdo->prepare('INSERT INTO itz_store_order_events(order_id,event_name,actor_type,actor_id,payload_json,created_at) VALUES(:o,:e,:at,:ai,:p,:t)')->execute([':o'=>$orderId,':e'=>$event,':at'=>$actorType,':ai'=>$actorId,':p'=>json_encode($payload,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),':t'=>time()]);
        if(function_exists('itz_event')) \itz_event('commerce.'.$event,['order_id'=>$orderId]+$payload,$actorType,$actorId);
    }

    public function notifyAdmin(array $order,string $headline='🛒 سفارش جدید سایت'): void
    {
        if(!function_exists('telegram'))return;$admin=trim((string)($GLOBALS['adminnumber']??''));if($admin==='')return;
        $user=(string)($order['telegram_username']??'');if($user!=='')$user='@'.ltrim($user,'@');elseif(!empty($order['telegram_id']))$user=(string)$order['telegram_id'];else$user=(string)($order['full_name']??'مهمان');
        $txt=$headline."\n\n".
            "🆔 سفارش: <code>".htmlspecialchars((string)($order['public_id']??''),ENT_QUOTES,'UTF-8')."</code>\n".
            "🌐 سایت: ".htmlspecialchars((string)($order['site_name']??''),ENT_QUOTES,'UTF-8')."\n".
            "📦 محصول: ".htmlspecialchars((string)($order['product_title']??''),ENT_QUOTES,'UTF-8')."\n".
            "👤 مشتری: ".htmlspecialchars($user,ENT_QUOTES,'UTF-8')."\n".
            "💰 مبلغ: <b>".number_format((float)($order['total_amount']??0)).' '.htmlspecialchars((string)($order['currency']??'IRT'),ENT_QUOTES,'UTF-8')."</b>\n".
            "💳 پرداخت: ".htmlspecialchars((string)($order['payment_status']??'unpaid'),ENT_QUOTES,'UTF-8')."\n".
            "📍 وضعیت: ".htmlspecialchars((string)($order['status_name']??'pending'),ENT_QUOTES,'UTF-8');
        $keyboard = [
            'inline_keyboard' => [
                [['text'=>'🧾 مشاهده سفارش','callback_data'=>'commerce_order_'.(int)($order['id']??0)]],
                [['text'=>'🌐 Commerce Hub','callback_data'=>'admin_commerce']],
            ],
        ];
        \telegram('sendMessage',[
            'chat_id'=>$admin,
            'text'=>$txt,
            'parse_mode'=>'HTML',
            'reply_markup'=>json_encode($keyboard,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),
        ]);
    }

    public function notifyCustomer(array $order, string $kind='order'): void
    {
        if(!function_exists('telegram')) return;
        $chat=trim((string)($order['telegram_id']??''));
        if($chat==='') return;
        $public=htmlspecialchars((string)($order['public_id']??''),ENT_QUOTES,'UTF-8');
        $product=htmlspecialchars((string)($order['product_title']??''),ENT_QUOTES,'UTF-8');
        if($kind==='paid'){
            $text="✅ <b>پرداخت سفارش سایت تایید شد</b>\n\n🆔 <code>{$public}</code>\n📦 {$product}\n📍 وضعیت: <b>".htmlspecialchars((string)($order['status_name']??''),ENT_QUOTES,'UTF-8')."</b>";
            if(($order['status_name']??'')==='awaiting_parameters') $text.="\n\n📝 برای ادامه فعال‌سازی، اطلاعات درخواستی محصول را در سایت تکمیل کن.";
        } elseif($kind==='completed') {
            $text="🎉 <b>سفارش شما تکمیل شد</b>\n\n🆔 <code>{$public}</code>\n📦 {$product}";
        } else {
            $text="🛒 <b>سفارش سایت ثبت شد</b>\n\n🆔 <code>{$public}</code>\n📦 {$product}\n📍 وضعیت: ".htmlspecialchars((string)($order['status_name']??''),ENT_QUOTES,'UTF-8');
        }
        \telegram('sendMessage',['chat_id'=>$chat,'text'=>$text,'parse_mode'=>'HTML']);
    }

    public function pushOrderStatus(array $order): void
    {
        $siteId=(int)($order['site_id']??0);if(!$siteId)return;$site=$this->site($siteId);if(!$site||$site['status_name']!=='active')return;
        $secret=$this->decrypt((string)$site['secret_enc']);if($secret==='')return;$url=rtrim((string)$site['base_url'],'/').'/itz-commerce-webhook';
        $body=json_encode(['event'=>'order.status','order'=>['public_id'=>$order['public_id']??'','status'=>$order['status_name']??'','payment_status'=>$order['payment_status']??'','updated_at'=>time()]],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);if($body===false)return;$ts=(string)time();$sig=hash_hmac('sha256',$ts."\n".$body,$secret);
        $ch=curl_init($url);if(!$ch)return;curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>$body,CURLOPT_HTTPHEADER=>['Content-Type: application/json','X-iTz-Timestamp: '.$ts,'X-iTz-Signature: '.$sig],CURLOPT_CONNECTTIMEOUT=>3,CURLOPT_TIMEOUT=>6,CURLOPT_FOLLOWLOCATION=>false]);$res=curl_exec($ch);$err=curl_error($ch);$code=(int)curl_getinfo($ch,CURLINFO_HTTP_CODE);curl_close($ch);if($res===false||$code<200||$code>=300){$this->pdo->prepare('UPDATE itz_store_sites SET last_error=:e WHERE id=:id')->execute([':e'=>substr('HTTP '.$code.' '.$err,0,1000),':id'=>$siteId]);}
    }

    public function gatewaySnapshot(): array
    {
        $map=[
            ['key'=>'Cartstatus','label'=>'کارت به کارت','on'=>['oncard']],
            ['key'=>'nowpaymentstatus','label'=>'Plisio','on'=>['onnowpayment']],
            ['key'=>'statusnowpayment','label'=>'NowPayments','on'=>['1']],
            ['key'=>'digistatus','label'=>'Crypto / TRON','on'=>['ondigi']],
            ['key'=>'statusSwapWallet','label'=>'TetraPay / Rial API','on'=>['onSwapinoBot']],
            ['key'=>'statustarnado','label'=>'Tornado / Rial Gateway','on'=>['ontrnado','ontarnado','1','on']],
            ['key'=>'statusiranpay3','label'=>'IranPay 3','on'=>['oniranpay3']],
            ['key'=>'zarinpalstatus','label'=>'ZarinPal','on'=>['onzarinpal']],
            ['key'=>'zarinpeystatus','label'=>'ZarinPay','on'=>['onzarinpey']],
            ['key'=>'statusaqayepardakht','label'=>'AqayePardakht','on'=>['onaqayepardakht']],
            ['key'=>'statusstar','label'=>'Telegram Stars','on'=>['1']],
            ['key'=>'paymentstatussnotverify','label'=>'Manual / Unverified Payment','on'=>['onpayment','1','on']],
        ];$out=[];
        try{$rows=$this->pdo->query('SELECT NamePay,ValuePay FROM PaySetting')->fetchAll(PDO::FETCH_KEY_PAIR)?:[];}catch(Throwable $e){$rows=[];}
        foreach($map as $m){$v=(string)($rows[$m['key']]??'');$out[]=['key'=>$m['key'],'label'=>$m['label'],'enabled'=>in_array($v,$m['on'],true)];}return $out;
    }

    public function recentJobs(int $limit=80): array
    {
        $limit=max(1,min(200,$limit));
        return $this->pdo->query('SELECT j.*,o.public_id,o.product_title,o.site_id FROM itz_store_jobs j LEFT JOIN itz_store_orders o ON o.id=j.order_id ORDER BY j.id DESC LIMIT '.$limit)->fetchAll(PDO::FETCH_ASSOC)?:[];
    }

    public function retryJob(int $id): void
    {
        $now=time();$this->pdo->prepare("UPDATE itz_store_jobs SET status_name='queued',last_error=NULL,available_at=:available_at,updated_at=:updated_at WHERE id=:id")->execute([':available_at'=>$now,':updated_at'=>$now,':id'=>$id]);
    }

    public function stats(): array
    {
        $q=function(string $sql){try{return (int)$this->pdo->query($sql)->fetchColumn();}catch(Throwable $e){return 0;}};
        return ['sites'=>$q("SELECT COUNT(*) FROM itz_store_sites WHERE status_name='active'"),'products'=>$q("SELECT COUNT(*) FROM itz_store_products WHERE status_name='active'"),'orders'=>$q('SELECT COUNT(*) FROM itz_store_orders'),'paid'=>$q("SELECT COUNT(*) FROM itz_store_orders WHERE payment_status='paid'"),'pending'=>$q("SELECT COUNT(*) FROM itz_store_orders WHERE status_name IN('pending','awaiting_parameters','processing')")];
    }
}
