<?php

if (!defined('ABSPATH')) { exit; }

class AIEO_Push_Dispatcher {

    
    const MAX_PER_TICK = 200;

    
    const PER_TRIGGER_LIMIT = 200;

    
    const QUIET_START = 22;
    const QUIET_END   = 9;

    
    private static $instance = null;
    public static function instance() {
        if (self::$instance === null) { self::$instance = new self(); }
        return self::$instance;
    }
    private function __construct() {}

    
    public function process_tick() {
        $stats = array(
            'processed'   => 0,
            'sent'        => 0,
            'suppressed'  => 0,
            'failed'      => 0,
            'by_trigger'  => array(),
        );

        
        if (class_exists('AIEO_DMM_Web_Push') && !\AIEO_DMM_Web_Push::is_engine_live()) {
            return array_merge($stats, array('skipped' => 'engine_paused'));
        }
        if (!class_exists('AIEO_Push_Trigger_Registry') || !class_exists('AIEO_Push_Sender')) {
            return array_merge($stats, array('skipped' => 'classes_missing'));
        }

        $registry = AIEO_Push_Trigger_Registry::instance();
        $sender   = AIEO_Push_Sender::instance();

        $send_budget = self::MAX_PER_TICK;
        foreach ($registry->all() as $trigger_id => $trigger) {
            $by = array('candidates' => 0, 'sent' => 0, 'suppressed' => 0, 'failed' => 0);
            $candidates = $trigger->produce_candidates(self::PER_TRIGGER_LIMIT);
            $by['candidates'] = count($candidates);
            foreach ($candidates as $c) {
                if ($send_budget <= 0) { break 2; }
                $stats['processed']++;
                $result = $this->process_candidate($trigger, $c, $sender);
                if ($result === 'sent') {
                    $stats['sent']++; $by['sent']++; $send_budget--;
                } elseif ($result === 'failed') {
                    $stats['failed']++; $by['failed']++;
                } else {
                    $stats['suppressed']++; $by['suppressed']++;
                }
            }
            $stats['by_trigger'][$trigger_id] = $by;
        }
        return $stats;
    }

    
    private function process_candidate(AIEO_Push_Trigger_Base $trigger, array $c, AIEO_Push_Sender $sender) {
        global $wpdb;
        $tbl_dispatch = $wpdb->prefix . 'aieo_push_dispatch';
        $tbl_subs     = $wpdb->prefix . 'aieo_push_subscriptions';
        $tbl_caps     = $wpdb->prefix . 'aieo_push_caps';
        $tbl_tpl      = $wpdb->prefix . 'aieo_push_templates';

        $sub_id = (int) ($c['subscription_id'] ?? 0);
        $user_id = (int) ($c['user_id'] ?? 0);
        $target_pid = (int) ($c['target_product_id'] ?? 0);
        $payload_data = is_array($c['payload_data'] ?? null) ? $c['payload_data'] : array();
        $target_url = (string) ($c['target_url'] ?? '');
        $now = current_time('mysql', true);

        
        if ($trigger->respects_quiet_hours() && $this->is_quiet_now()) {
            $this->log_suppressed($trigger->get_id(), $sub_id, $user_id, $target_pid, $target_url, $payload_data, 'quiet_hours');
            return 'suppressed';
        }

        
        $today = wp_date('Y-m-d');
        $sent_today = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT sent_count FROM {$tbl_caps}
             WHERE user_id = %d AND window_kind = 'day' AND window_start = %s",
            $user_id, $today
        ));
        if ($sent_today >= $trigger->cap_per_day()) {
            $this->log_suppressed($trigger->get_id(), $sub_id, $user_id, $target_pid, $target_url, $payload_data, 'cap_day');
            return 'suppressed';
        }
        
        $week_start = wp_date('Y-m-d', strtotime('monday this week'));
        $sent_this_week = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT sent_count FROM {$tbl_caps}
             WHERE user_id = %d AND window_kind = 'week' AND window_start = %s",
            $user_id, $week_start
        ));
        if ($sent_this_week >= $trigger->cap_per_week()) {
            $this->log_suppressed($trigger->get_id(), $sub_id, $user_id, $target_pid, $target_url, $payload_data, 'cap_week');
            return 'suppressed';
        }

        
        $dedup_days = max(1, (int) $trigger->dedup_days());
        $dedup_hit = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT dispatch_id FROM {$tbl_dispatch}
             WHERE trigger_id = %s
               AND target_product_id = %d
               AND user_id = %d
               AND state IN ('sent','clicked','dismissed')
               AND sent_at >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY)
             LIMIT 1",
            $trigger->get_id(), $target_pid, $user_id, $dedup_days
        ));
        if ($dedup_hit > 0) {
            $this->log_suppressed($trigger->get_id(), $sub_id, $user_id, $target_pid, $target_url, $payload_data, 'dedup_recent');
            return 'suppressed';
        }

        
        
        $segment = $this->resolve_user_segment($user_id);
        $template = $this->pick_template($trigger->get_id(), $segment);
        if ($template === null) {
            $this->log_suppressed($trigger->get_id(), $sub_id, $user_id, $target_pid, $target_url, $payload_data, 'no_template');
            return 'suppressed';
        }

        
        $sub = $wpdb->get_row($wpdb->prepare(
            "SELECT subscription_id, endpoint, p256dh, auth_secret
             FROM {$tbl_subs}
             WHERE subscription_id = %d AND status = 'active'
             LIMIT 1",
            $sub_id
        ), ARRAY_A);
        if (!$sub || empty($sub['endpoint'])) {
            $this->log_suppressed($trigger->get_id(), $sub_id, $user_id, $target_pid, $target_url, $payload_data, 'not_subscribed');
            return 'suppressed';
        }

        
        $title = $this->render_template((string) $template['title_template'], $payload_data);
        $body  = $this->render_template((string) $template['body_template'],  $payload_data);
        $icon  = (string) ($template['icon_url'] ?? '');

        
        
        $wpdb->insert(
            $tbl_dispatch,
            array(
                'subscription_id'   => $sub_id,
                'user_id'           => $user_id,
                'trigger_id'        => $trigger->get_id(),
                'template_id'       => (int) ($template['template_id'] ?? 0),
                'target_product_id' => $target_pid,
                'target_url'        => $target_url,
                'payload_json'      => wp_json_encode($payload_data),
                'state'             => 'queued',
                'queued_at'         => $now,
                'created_at'        => $now,
            ),
            array('%d','%d','%s','%d','%d','%s','%s','%s','%s','%s')
        );
        $dispatch_id = (int) $wpdb->insert_id;

        
        
        
        
        
        $tracked_url = $this->append_attribution_params($target_url, array(
            'aieo_src'         => 'push',
            'aieo_dispatch_id' => (string) $dispatch_id,
            'utm_source'       => 'aieo_push',
            'utm_medium'       => 'push',
            'utm_campaign'     => $trigger->get_id(),
        ));

        $payload = wp_json_encode(array(
            'title'      => $title,
            'body'       => $body,
            'icon'       => $icon ?: '',
            'tag'        => $trigger->get_id() . '-' . $target_pid,
            'url'        => $tracked_url,
            'dispatchId' => $dispatch_id,
        ));

        $resp = $sender->send(array(
            'endpoint'    => (string) $sub['endpoint'],
            'p256dh'      => (string) $sub['p256dh'],
            'auth_secret' => (string) $sub['auth_secret'],
        ), (string) $payload, 86400);

        if (!empty($resp['ok'])) {
            $wpdb->update(
                $tbl_dispatch,
                array(
                    'state'           => 'sent',
                    'sent_at'         => $now,
                    'provider_status' => (int) $resp['status'],
                ),
                array('dispatch_id' => $dispatch_id),
                array('%s','%s','%d'),
                array('%d')
            );
            $this->bump_caps($user_id, $today, $week_start);
            return 'sent';
        }

        
        
        if (in_array((int) $resp['status'], array(404, 410), true)) {
            $wpdb->update(
                $tbl_subs,
                array('status' => 'revoked', 'bounced_at' => $now),
                array('subscription_id' => $sub_id),
                array('%s','%s'),
                array('%d')
            );
        }
        $wpdb->update(
            $tbl_dispatch,
            array(
                'state'           => 'failed',
                'provider_status' => (int) $resp['status'],
                'provider_error'  => substr((string) $resp['error'], 0, 200),
            ),
            array('dispatch_id' => $dispatch_id),
            array('%s','%d','%s'),
            array('%d')
        );
        return 'failed';
    }

    private function log_suppressed($trigger_id, $sub_id, $user_id, $pid, $url, $data, $reason) {
        global $wpdb;
        $tbl = $wpdb->prefix . 'aieo_push_dispatch';
        $now = current_time('mysql', true);
        $wpdb->insert(
            $tbl,
            array(
                'subscription_id'   => (int) $sub_id,
                'user_id'           => (int) $user_id,
                'trigger_id'        => (string) $trigger_id,
                'target_product_id' => (int) $pid,
                'target_url'        => (string) $url,
                'payload_json'      => wp_json_encode($data),
                'state'             => 'suppressed',
                'suppress_reason'   => (string) $reason,
                'queued_at'         => $now,
                'created_at'        => $now,
            ),
            array('%d','%d','%s','%d','%s','%s','%s','%s','%s','%s')
        );
    }

    private function bump_caps($user_id, $today, $week_start) {
        global $wpdb;
        $tbl = $wpdb->prefix . 'aieo_push_caps';
        $now = current_time('mysql', true);
        
        $sql = "INSERT INTO {$tbl} (user_id, window_kind, window_start, sent_count, updated_at)
                VALUES (%d, %s, %s, 1, %s)
                ON DUPLICATE KEY UPDATE sent_count = sent_count + 1, updated_at = VALUES(updated_at)";
        $wpdb->query($wpdb->prepare($sql, $user_id, 'day',  $today,      $now));
        $wpdb->query($wpdb->prepare($sql, $user_id, 'week', $week_start, $now));
    }

    
    private function pick_template($trigger_id, $segment) {
        global $wpdb;
        $tbl = $wpdb->prefix . 'aieo_push_templates';

        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT template_id, segment_id, variant_label, weight,
                    title_template, body_template, icon_url, cta_label
             FROM {$tbl}
             WHERE trigger_id = %s
               AND is_active = 1
               AND segment_id IN (%s, '*')",
            $trigger_id, $segment ?: '*'
        ), ARRAY_A);
        if (empty($rows)) { return null; }

        
        $specific = array_filter($rows, function ($r) use ($segment) {
            return $segment !== '' && $r['segment_id'] === $segment;
        });
        $pool = !empty($specific) ? $specific : $rows;

        $total = 0;
        foreach ($pool as $r) { $total += max(1, (int) $r['weight']); }
        if ($total <= 0) { return $pool[0] ?? null; }

        $pick = wp_rand(1, $total);
        $acc = 0;
        foreach ($pool as $r) {
            $acc += max(1, (int) $r['weight']);
            if ($pick <= $acc) { return $r; }
        }
        return $pool[0] ?? null;
    }

    
    private function resolve_user_segment($user_id) {
        if ($user_id <= 0) { return ''; }
        global $wpdb;
        $tbl = $wpdb->prefix . 'aieo_customer_intelligence';
        $row = $wpdb->get_row($wpdb->prepare(
            "SELECT segments_json FROM {$tbl} WHERE wp_user_id = %d LIMIT 1",
            $user_id
        ), ARRAY_A);
        if (!$row || empty($row['segments_json'])) { return ''; }
        $segs = json_decode((string) $row['segments_json'], true);
        if (!is_array($segs)) { return ''; }
        
        foreach (array('vip', 'loyalist', 'brand_devoted', 'discount_hunter', 'replenisher', 'newbie', 'dormant', 'lapsed') as $pri) {
            if (in_array($pri, $segs, true)) { return $pri; }
        }
        return '';
    }

    
    private function append_attribution_params($url, array $params) {
        if ($url === '' || strpos($url, 'http') !== 0) { return $url; }
        $clean = array();
        foreach ($params as $k => $v) {
            $v = (string) $v;
            if ($v !== '') { $clean[$k] = $v; }
        }
        if (empty($clean)) { return $url; }
        return add_query_arg($clean, $url);
    }

    
    private function render_template($template, array $data) {
        if ($template === '' || empty($data)) { return $template; }
        return preg_replace_callback('/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/', function ($m) use ($data) {
            $key = $m[1];
            return isset($data[$key]) ? (string) $data[$key] : $m[0];
        }, $template);
    }

    private function is_quiet_now() {
        
        
        
        
        
        $start = self::QUIET_START * 60;
        $end   = self::QUIET_END   * 60;
        if (class_exists('AIEO_DMM_Settings')) {
            $start = self::hhmm_to_minutes((string) AIEO_DMM_Settings::get('contact_quiet_start', '22:00'), $start);
            $end   = self::hhmm_to_minutes((string) AIEO_DMM_Settings::get('contact_quiet_end',   '09:00'), $end);
        }
        if ($start === $end) { return false; } 
        try {
            $now = new \DateTime('now', new \DateTimeZone('Europe/Athens'));
            $m   = (int) $now->format('G') * 60 + (int) $now->format('i');
        } catch (\Throwable $e) { return false; }
        if ($start < $end) {
            return $m >= $start && $m < $end;
        }
        return $m >= $start || $m < $end;
    }

    
    private static function hhmm_to_minutes($hhmm, $fallback) {
        if (preg_match('/^(\d{1,2}):(\d{2})$/', trim((string) $hhmm), $p)
            && (int) $p[1] <= 23 && (int) $p[2] <= 59) {
            return (int) $p[1] * 60 + (int) $p[2];
        }
        return (int) $fallback;
    }
}
