<?php

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

class AIEO_Courier_Driver_Geniki extends AIEO_Courier_Driver_Base {

    const ENDPOINT_LIVE = 'https://voucher.taxydromiki.gr/JobServicesV2.asmx';
    const ENDPOINT_TEST = 'https://testvoucher.taxydromiki.gr/JobServicesV2.asmx';

    
    const AUTHKEY_TTL = 43200;

    public function code()  { return 'geniki'; }
    public function label() { return 'Γενική Ταχυδρομική'; }

    public function capabilities() {
        return array('create', 'print', 'cancel', 'track', 'close_pending', 'close_pending_by_date', 'shop_list', 'calculate_cost');
    }

    public function settings_schema() {
        return array(
            'username'        => array('type' => 'text',     'label' => __('Username',           'ai-eshop-optimizer'), 'required' => true),
            'password'        => array('type' => 'password', 'label' => __('Password',           'ai-eshop-optimizer'), 'required' => true),
            'application_key' => array('type' => 'password', 'label' => __('Application key',    'ai-eshop-optimizer'), 'required' => true,
                                       'help'  => __('Issued by Geniki sales department.', 'ai-eshop-optimizer')),
            
            
            
            
            'customer_code'   => array('type' => 'text',     'label' => __('Customer code (ΚΩΔΙΚΟΣ ΠΕΛΑΤΗ)', 'ai-eshop-optimizer'),
                                       'help'  => __('Your business identifier with Γενική. Printed on the end-of-day closing manifest given to the dispatcher.', 'ai-eshop-optimizer')),
            'test_mode'       => array('type' => 'checkbox', 'label' => __('Use test endpoint (testvoucher.taxydromiki.gr)', 'ai-eshop-optimizer')),
            'default_pieces'  => array('type' => 'number',   'label' => __('Default pieces per voucher', 'ai-eshop-optimizer')),
            'default_weight'  => array('type' => 'number',   'label' => __('Default weight (kg)',        'ai-eshop-optimizer')),
            'default_services'=> array('type' => 'text',     'label' => __('Default services CSV',       'ai-eshop-optimizer'),
                                       'help'  => __('Two-character Geniki service codes, comma-separated. e.g. "5Σ" for Saturday, "1Σ,5Σ" for early-morning + Saturday. Empty = standard. "αμ"/"αν" (COD) and "ασ" (insurance) are added automatically when the order has a COD/insurance amount — no need to set them here.', 'ai-eshop-optimizer')),
        );
    }

    public function tracking_url($voucher_no) {
        return AIEO_Runtime_Fulfillment_URLs::tracking_url('geniki', (string) $voucher_no);
    }

    
    protected function code_map() {
        return array(
            
            'C_NW'    => 'created',
            'C_A1'    => 'in_transit',
            'C_A3'    => 'out_for_delivery',
            'C_E1'    => 'returning',
            'C_E2'    => 'in_transit',          
            'C_L1'    => 'in_transit',
            'C_L2'    => 'in_transit',          
            'C_K8'    => 'in_transit',
            'C_K9'    => 'in_transit',          
            'C_KK'    => 'created',
            'C_W2'    => 'delivered',
            'C_W3'    => 'in_transit',          
            'C_H1'    => 'in_transit',
            'C_H2'    => 'in_transit',
            'C_P1'    => 'in_transit',
            'C_P4'    => 'canceled',
            'C_D2'    => 'on_hold',
            
            'C_EA_AG' => 'attempted',
            'C_EA_AK' => 'attempted',
            'C_EA_AP' => 'attempted',
            'C_EA_AS' => 'attempted',
            'C_EA_DA' => 'attempted',
            'C_EA_DD' => 'attempted',
            'C_EA_DP' => 'attempted',
            'C_EA_EP' => 'attempted',
            'C_EA_KS' => 'attempted',
            'C_EA_LA' => 'attempted',
            'C_EA_LD' => 'attempted',
            
            'C_S0'    => 'out_for_delivery',
            'C_S1'    => 'in_transit',
            'C_S2'    => 'delivered',
            'C_S3'    => 'returned',
            'C_S4'    => 'on_hold',
            'C_S5'    => 'canceled',
            'C_S6'    => 'failed',
            'C_S7'    => 'returning',
            'C_S8'    => 'canceled',
            'C_S9'    => 'on_hold',
            'C_SA'    => 'failed',
            'C_SB'    => 'failed',
            'C_SC'    => 'created',
            'C_SD'    => 'in_transit',
            'C_SE'    => 'attempted',
            'C_SF'    => 'attempted',
        );
    }

    
    public function authenticate(array $creds) {
        $cfg = array_merge($this->credentials(), $creds);
        if (empty($cfg['username']) || empty($cfg['password']) || empty($cfg['application_key'])) {
            return array('ok' => false, 'error' => 'Geniki credentials missing.');
        }

        $key = $this->get_cached_authkey($cfg);
        if ($key !== null) {
            return array('ok' => true, 'auth_token' => $key, 'expires_at' => null, 'cached' => true);
        }

        $client = $this->soap_client();
        if ($client instanceof WP_Error) { return array('ok' => false, 'error' => $client->get_error_message()); }

        try {
            
            
            
            
            
            
            $r = $client->Authenticate(array(
                'sUsrName'       => (string) $cfg['username'],
                'sUsrPwd'        => (string) $cfg['password'],
                'applicationKey' => (string) $cfg['application_key'],
            ));
        } catch (\SoapFault $e) {
            
            return array('ok' => false, 'error' => sprintf( __('SOAP fault: %s', 'ai-eshop-optimizer'), $e->getMessage() ));
        }

        $result = isset($r->AuthenticateResult) ? $r->AuthenticateResult : null;
        if (!$result || (int) ($result->Result ?? 99) !== 0 || empty($result->Key)) {
            $code = isset($result->Result) ? (int) $result->Result : -1;
            
            return array('ok' => false, 'error' => sprintf(__('Geniki auth failed (code %d).', 'ai-eshop-optimizer'), $code));
        }

        $this->set_cached_authkey($cfg, (string) $result->Key);
        return array('ok' => true, 'auth_token' => (string) $result->Key, 'expires_at' => time() + self::AUTHKEY_TTL);
    }

    
    public function create_voucher(array $payload) {
        $auth = $this->authenticate(array());
        if (!$auth['ok']) { return $auth; }

        $cfg       = $this->credentials();
        $recipient = isset($payload['recipient']) && is_array($payload['recipient']) ? $payload['recipient'] : array();
        $services  = (string) ($payload['services_csv'] ?? ($cfg['default_services'] ?? ''));

        
        
        
        
        
        
        
        $cod_amount = (float) ($payload['cod_amount'] ?? 0);
        $ins_amount = isset($payload['ins_amount']) ? (float) $payload['ins_amount'] : 0;
        if ($cod_amount > 0 && !self::services_csv_has($services, array('αμ', 'αν'))) {
            $services = self::services_csv_append($services, 'αμ');
        }
        if ($ins_amount > 0 && !self::services_csv_has($services, array('ασ'))) {
            $services = self::services_csv_append($services, 'ασ');
        }

        
        
        
        
        
        
        
        
        
        $received_date = isset($payload['received_date']) && $payload['received_date'] !== ''
            ? (string) $payload['received_date']
            : gmdate('Y-m-d\TH:i:s');

        $record = array(
            'OrderId'             => (string) ($payload['reference_1'] ?? ''),
            'Name'                => (string) ($recipient['name']    ?? ''),
            'Address'             => (string) ($recipient['address'] ?? ''),
            'City'                => (string) ($recipient['city']    ?? ''),
            'Country'             => (string) ($recipient['country'] ?? 'Greece'),
            'Email'               => (string) ($recipient['email']   ?? ''),
            'Telephone'           => (string) ($recipient['phone']   ?? ''),
            'Zip'                 => (string) ($recipient['zip']     ?? ''),
            'Weight'              => (float)  ($payload['weight']    ?? ($cfg['default_weight'] ?? 0.5)),
            'Pieces'              => max(1, (int) ($payload['pieces']  ?? ($cfg['default_pieces'] ?? 1))),
            'Comments'            => (string) ($payload['comments'] ?? ''),
            'Services'            => $services !== '' ? $services : 'STD',
            
            
            'CodAmount'           => (float) ($payload['cod_amount'] ?? 0),
            'InsAmount'           => isset($payload['ins_amount']) ? (float) $payload['ins_amount'] : 0,
            'SubCode'             => (string) ($payload['cost_center']        ?? ''),
            'ReceivedDate'        => $received_date,
            'ContentsDescription' => (string) ($payload['content_description'] ?? ''),
        );

        return $this->call_with_retry(function ($client, $auth_key) use ($record) {
            $r = $client->CreateJob(array(
                'sAuthKey' => $auth_key,
                'oVoucher' => $record,
                'eType'    => 'Voucher',
            ));
            $result = isset($r->CreateJobResult) ? $r->CreateJobResult : null;
            $code   = isset($result->Result) ? (int) $result->Result : -1;
            if ($code !== 0) {
                
                return array('ok' => false, 'code' => $code, 'error' => sprintf(__('Geniki CreateJob result %d', 'ai-eshop-optimizer'), $code));
            }
            $subs = array();
            if (!empty($result->SubVouchers)) {
                foreach ((array) $result->SubVouchers as $sv) {
                    if (isset($sv->VoucherNo) && $sv->VoucherNo !== '') { $subs[] = (string) $sv->VoucherNo; }
                }
            }
            return array(
                'ok'          => true,
                'voucher_no'  => (string) ($result->Voucher ?? ''),
                'subvouchers' => $subs,
                'job_id'      => isset($result->JobId) ? (int) $result->JobId : null,
            );
        });
    }

    
    public function get_label_pdf($voucher_no, $format = 'A4') {
        $auth = $this->authenticate(array());
        if (!$auth['ok']) { return $auth; }

        $auth_key = (string) $auth['auth_token'];
        $endpoint = $this->endpoint();
        $media    = (strtoupper($format) === 'THERMAL') ? 'Sticker' : 'Flyer';

        $url = $endpoint . '/GetVoucherPdf?' . http_build_query(array(
            'authKey'         => $auth_key,
            'voucherNo'       => (string) $voucher_no,
            'format'          => $media,
            'extraInfoFormat' => 'None',
        ));

        $resp = wp_remote_get($url, array('timeout' => 30, 'redirection' => 0));
        if (is_wp_error($resp)) { return array('ok' => false, 'error' => $resp->get_error_message()); }
        $code  = (int) wp_remote_retrieve_response_code($resp);
        $body  = (string) wp_remote_retrieve_body($resp);
        $ctype = (string) wp_remote_retrieve_header($resp, 'content-type');

        if ($code !== 200 || stripos($ctype, 'application/pdf') === false) {
            
            
            $err = trim($body);
            if (in_array($err, array('1', '11'), true)) {
                $this->forget_authkey();
                $auth2 = $this->authenticate(array());
                if (!$auth2['ok']) { return $auth2; }
                $url = $endpoint . '/GetVoucherPdf?' . http_build_query(array(
                    'authKey'         => (string) $auth2['auth_token'],
                    'voucherNo'       => (string) $voucher_no,
                    'format'          => $media,
                    'extraInfoFormat' => 'None',
                ));
                $resp  = wp_remote_get($url, array('timeout' => 30, 'redirection' => 0));
                if (is_wp_error($resp)) { return array('ok' => false, 'error' => $resp->get_error_message()); }
                $code  = (int) wp_remote_retrieve_response_code($resp);
                $body  = (string) wp_remote_retrieve_body($resp);
                $ctype = (string) wp_remote_retrieve_header($resp, 'content-type');
            }
        }

        if ($code !== 200 || stripos($ctype, 'application/pdf') === false) {
            return array('ok' => false, 'error' => 'Geniki PDF fetch failed: HTTP ' . $code . ' body=' . substr($body, 0, 80));
        }
        return array('ok' => true, 'pdf' => $body, 'pdf_url' => null);
    }

    public function cancel($voucher_no) {
        
        
        
        
        
        return array('ok' => false, 'error' => 'Geniki cancel: use cancel_by_job($job_id) — Geniki cancels by JobId, not VoucherNo.');
    }

    public function cancel_by_job($job_id) {
        $auth = $this->authenticate(array());
        if (!$auth['ok']) { return $auth; }

        return $this->call_with_retry(function ($client, $auth_key) use ($job_id) {
            $r = $client->CancelJob(array(
                'sAuthKey' => $auth_key,
                'nJobId'   => (int) $job_id,
                'bCancel'  => true,
            ));
            $code = isset($r->CancelJobResult) ? (int) $r->CancelJobResult : -1;
            if ($code !== 0) {
                
                return array('ok' => false, 'code' => $code, 'error' => sprintf(__('Geniki CancelJob result %d', 'ai-eshop-optimizer'), $code));
            }
            return array('ok' => true);
        });
    }

    
    public function calculate_cost(array $payload) {
        $auth = $this->authenticate(array());
        if (!$auth['ok']) { return $auth; }

        $recipient = isset($payload['recipient']) && is_array($payload['recipient']) ? $payload['recipient'] : array();
        $country   = strtolower((string) ($recipient['country'] ?? 'Greece'));
        $is_domestic = ($country === '' || $country === 'greece' || $country === 'gr' || $country === 'ελλάδα' || $country === 'ελλαδα');
        if (!$is_domestic) {
            return array('ok' => false, 'error' => 'CalculateCost: non-domestic shipping requires a routeId mapping not yet configured.');
        }

        $cfg     = $this->credentials();
        $weight  = (float) ($payload['weight'] ?? ($cfg['default_weight'] ?? 0.5));
        if ($weight <= 0) { $weight = 0.5; }
        $services_csv = (string) ($payload['services_csv'] ?? '');
        $services_arr = array();
        if ($services_csv !== '') {
            foreach (explode(',', $services_csv) as $s) {
                $s = trim($s);
                if ($s !== '') { $services_arr[] = $s; }
            }
        }

        return $this->call_with_retry(function ($client, $auth_key) use ($weight, $services_arr) {
            $args = array(
                'authKey' => $auth_key,
                'routeId' => 1, 
                'weight'  => $weight,
            );
            
            
            
            if (!empty($services_arr)) {
                $args['services'] = $services_arr;
            }
            $r = $client->CalculateCost($args);
            $result = isset($r->CalculateCostResult) ? $r->CalculateCostResult : null;
            $code   = isset($result->Result) ? (int) $result->Result : -1;
            if ($code !== 0) {
                
                return array('ok' => false, 'code' => $code, 'error' => sprintf(__('Geniki CalculateCost result %d', 'ai-eshop-optimizer'), $code));
            }
            return array(
                'ok'       => true,
                'cost'     => isset($result->Cost) ? (float) $result->Cost : null,
                'vat'      => isset($result->Vat)  ? (float) $result->Vat  : null,
                'currency' => 'EUR',
            );
        });
    }

    
    public function track_simple($voucher_no) {
        $auth = $this->authenticate(array());
        if (!$auth['ok']) { return $auth; }

        return $this->call_with_retry(function ($client, $auth_key) use ($voucher_no) {
            $r = $client->TrackDeliveryStatus(array(
                'authKey'   => $auth_key,
                'voucherNo' => (string) $voucher_no,
                'language'  => 'el',
            ));
            $result = isset($r->TrackDeliveryStatusResult) ? $r->TrackDeliveryStatusResult : null;
            $code   = isset($result->Result) ? (int) $result->Result : -1;
            if ($code !== 0) {
                
                return array('ok' => false, 'code' => $code, 'error' => sprintf(__('Geniki TrackDeliveryStatus result %d', 'ai-eshop-optimizer'), $code));
            }
            
            
            
            
            $delivery_date = isset($result->DeliveryDate) ? strtotime((string) $result->DeliveryDate) : null;
            return array(
                'ok'           => true,
                'status_code'  => isset($result->Status) ? (string) $result->Status : '',
                'consignee'    => isset($result->Consignee)   ? (string) $result->Consignee   : null,
                'delivered_at' => is_int($delivery_date) ? $delivery_date : null,
                'delivered_at_location' => isset($result->DeliveredAt) ? (string) $result->DeliveredAt : null,
            );
        });
    }

    public function track($voucher_no) {
        $auth = $this->authenticate(array());
        if (!$auth['ok']) { return $auth; }

        return $this->call_with_retry(function ($client, $auth_key) use ($voucher_no) {
            $r = $client->TrackAndTrace(array(
                'authKey'   => $auth_key,
                'voucherNo' => (string) $voucher_no,
                'language'  => 'el',
            ));
            $result = isset($r->TrackAndTraceResult) ? $r->TrackAndTraceResult : null;
            $code   = isset($result->Result) ? (int) $result->Result : -1;
            if ($code !== 0) {
                
                return array('ok' => false, 'code' => $code, 'error' => sprintf(__('Geniki TrackAndTrace result %d', 'ai-eshop-optimizer'), $code));
            }
            $checkpoints = array();
            if (!empty($result->Checkpoints)) {
                
                
                
                
                $cp_list = isset($result->Checkpoints->Checkpoint) ? $result->Checkpoints->Checkpoint : array();
                if (is_object($cp_list)) { $cp_list = array($cp_list); }
                foreach ((array) $cp_list as $cp) {
                    $when = isset($cp->StatusDate) ? strtotime((string) $cp->StatusDate) : null;
                    $checkpoints[] = array(
                        'code'          => isset($cp->StatusCode) ? (string) $cp->StatusCode : '',
                        'description'  => isset($cp->Status)     ? (string) $cp->Status     : '',
                        'location'     => isset($cp->Shop)       ? (string) $cp->Shop       : '',
                        'checkpoint_at' => is_int($when) ? $when : null,
                    );
                }
            }
            $delivery_date = isset($result->DeliveryDate) ? strtotime((string) $result->DeliveryDate) : null;
            $latest_code   = '';
            if (!empty($checkpoints)) {
                
                $last = end($checkpoints);
                $latest_code = (string) ($last['code'] ?? '');
            }
            
            
            
            
            
            
            
            
            
            
            $arrived_at   = null;
            $arrived_code = '';
            $first_a3 = null; $first_a1 = null;
            foreach ($checkpoints as $cp) {
                if (!is_int($cp['checkpoint_at'])) { continue; }
                $code = (string) ($cp['code'] ?? '');
                if ($code === 'C_A3' && $first_a3 === null) { $first_a3 = $cp['checkpoint_at']; }
                if ($code === 'C_A1' && $first_a1 === null) { $first_a1 = $cp['checkpoint_at']; }
            }
            if ($first_a3 !== null) {
                $arrived_at   = $first_a3;
                $arrived_code = 'C_A3';
            } elseif ($first_a1 !== null) {
                $arrived_at   = $first_a1;
                $arrived_code = 'C_A1';
            }
            return array(
                'ok'             => true,
                'status_code'    => $latest_code,
                'checkpoints'    => $checkpoints,
                'consignee'      => isset($result->Consignee) ? (string) $result->Consignee : null,
                'delivered_at'   => is_int($delivery_date) ? $delivery_date : null,
                'arrived_at'     => $arrived_at,
                'arrived_status' => $arrived_code,
            );
        });
    }

    
    
    public function close_pending_by_date($from_iso, $to_iso) {
        $auth = $this->authenticate(array());
        if (!$auth['ok']) { return $auth; }

        $from_iso = (string) $from_iso;
        $to_iso   = (string) $to_iso;
        if ($from_iso === '' || $to_iso === '') {
            return array('ok' => false, 'error' => 'close_pending_by_date: date_from and date_to required (ISO 8601).');
        }

        return $this->call_with_retry(function ($client, $auth_key) use ($from_iso, $to_iso) {
            $r = $client->ClosePendingJobsByDate(array(
                'sAuthKey' => $auth_key,
                'dFr'      => $from_iso,
                'dTo'      => $to_iso,
            ));
            $code = isset($r->ClosePendingJobsByDateResult) ? (int) $r->ClosePendingJobsByDateResult : -1;
            if ($code !== 0) {
                
                return array('ok' => false, 'code' => $code, 'error' => sprintf(__('Geniki ClosePendingJobsByDate result %d', 'ai-eshop-optimizer'), $code));
            }
            return array('ok' => true);
        });
    }

    public function close_pending($voucher_nos = null) {
        $auth = $this->authenticate(array());
        if (!$auth['ok']) { return $auth; }

        return $this->call_with_retry(function ($client, $auth_key) {
            $r = $client->ClosePendingJobs(array('sAuthKey' => $auth_key));
            $code = isset($r->ClosePendingJobsResult) ? (int) $r->ClosePendingJobsResult : -1;
            if ($code !== 0) {
                
                return array('ok' => false, 'code' => $code, 'error' => sprintf(__('Geniki ClosePendingJobs result %d', 'ai-eshop-optimizer'), $code));
            }
            return array('ok' => true);
        });
    }

    public function shop_list() {
        $auth = $this->authenticate(array());
        if (!$auth['ok']) { return $auth; }

        return $this->call_with_retry(function ($client, $auth_key) {
            $r = $client->GetShopsList(array('authKey' => $auth_key));
            $result = isset($r->GetShopsListResult) ? $r->GetShopsListResult : null;
            $code   = isset($result->Result) ? (int) $result->Result : -1;
            if ($code !== 0) {
                
                return array('ok' => false, 'code' => $code, 'error' => sprintf(__('Geniki GetShopsList result %d', 'ai-eshop-optimizer'), $code));
            }
            $shops = array();
            if (!empty($result->Shops)) {
                foreach ((array) $result->Shops as $s) {
                    $shops[] = array(
                        'code'      => isset($s->Code) ? (string) $s->Code : '',
                        'name'      => isset($s->Name) ? (string) $s->Name : '',
                        'state'     => isset($s->State)    ? (string) $s->State    : '',
                        'city'      => isset($s->City)     ? (string) $s->City     : '',
                        'address'   => isset($s->Address)  ? (string) $s->Address  : '',
                        'zip'       => isset($s->Zip)      ? (string) $s->Zip      : '',
                        'phone'     => isset($s->Telephone)? (string) $s->Telephone: '',
                        'email'     => isset($s->Email)    ? (string) $s->Email    : '',
                        'lng'       => isset($s->Longitude)? (float)  $s->Longitude: null,
                        'lat'       => isset($s->Latitude) ? (float)  $s->Latitude : null,
                        'subshop'   => !empty($s->SubShop),
                        'active'    => !empty($s->Active),
                    );
                }
            }
            return array('ok' => true, 'shops' => $shops);
        });
    }

    

    private function credentials() {
        if (!class_exists('AIEO_Fulfillment_Settings')) { return array(); }
        $cfg = AIEO_Fulfillment_Settings::get('courier_geniki', array());
        return is_array($cfg) ? $cfg : array();
    }

    private function endpoint() {
        $cfg = $this->credentials();
        return !empty($cfg['test_mode']) ? self::ENDPOINT_TEST : self::ENDPOINT_LIVE;
    }

    
    private function soap_client() {
        static $cache = array();
        if (!class_exists('SoapClient')) {
            return new WP_Error('soap_missing', __('PHP SOAP extension not available — install php-soap to use the Geniki driver.', 'ai-eshop-optimizer'));
        }
        $endpoint = $this->endpoint();
        if (isset($cache[$endpoint])) { return $cache[$endpoint]; }
        try {
            $client = new SoapClient($endpoint . '?WSDL', array(
                'soap_version'   => SOAP_1_1,
                'connection_timeout' => 15,
                'cache_wsdl'     => WSDL_CACHE_BOTH,
                'trace'          => false,
                'exceptions'     => true,
                'keep_alive'     => true,
            ));
        } catch (\SoapFault $e) {
            
            return new WP_Error('soap_init', sprintf(__('Could not init SoapClient: %s', 'ai-eshop-optimizer'), $e->getMessage()));
        }
        $cache[$endpoint] = $client;
        return $client;
    }

    
    private function call_with_retry(callable $fn) {
        $client = $this->soap_client();
        if ($client instanceof WP_Error) { return array('ok' => false, 'error' => $client->get_error_message()); }

        $auth = $this->authenticate(array());
        if (!$auth['ok']) { return $auth; }

        try {
            $out = $fn($client, (string) $auth['auth_token']);
        } catch (\SoapFault $e) {
            
            return array('ok' => false, 'error' => sprintf( __('SOAP fault: %s', 'ai-eshop-optimizer'), $e->getMessage() ));
        }

        
        if (is_array($out) && empty($out['ok']) && isset($out['code']) && in_array((int) $out['code'], array(1, 11), true)) {
            $this->forget_authkey();
            $auth2 = $this->authenticate(array());
            if (!$auth2['ok']) { return $auth2; }
            try {
                $out = $fn($client, (string) $auth2['auth_token']);
            } catch (\SoapFault $e) {
                return array('ok' => false, 'error' => 'SOAP fault on retry: ' . $e->getMessage());
            }
        }

        return $out;
    }

    private function authkey_cache_key(array $cfg) {
        
        
        return 'aieo_geniki_authkey_' . substr(md5(($cfg['username'] ?? '') . '|' . $this->endpoint()), 0, 24);
    }

    private function get_cached_authkey(array $cfg) {
        $val = get_transient($this->authkey_cache_key($cfg));
        return is_string($val) && $val !== '' ? $val : null;
    }

    private function set_cached_authkey(array $cfg, $key) {
        set_transient($this->authkey_cache_key($cfg), (string) $key, self::AUTHKEY_TTL);
    }

    private function forget_authkey() {
        delete_transient($this->authkey_cache_key($this->credentials()));
    }

    
    private static function services_csv_has($csv, array $needles) {
        $csv = (string) $csv;
        if ($csv === '') { return false; }
        $parts = array_filter(array_map(static function ($s) {
            return mb_strtolower(trim((string) $s));
        }, explode(',', $csv)));
        foreach ($needles as $n) {
            if (in_array(mb_strtolower((string) $n), $parts, true)) { return true; }
        }
        return false;
    }

    
    private static function services_csv_append($csv, $code) {
        $csv  = trim((string) $csv);
        $code = trim((string) $code);
        if ($code === '') { return $csv; }
        if ($csv === '' || strcasecmp($csv, 'STD') === 0) { return $code; }
        return $csv . ',' . $code;
    }
}

add_filter('aieo_fulfillment_couriers', static function (array $drivers) {
    if (!isset($drivers['geniki']) && class_exists('AIEO_Courier_Driver_Geniki')) {
        $drivers['geniki'] = new AIEO_Courier_Driver_Geniki();
    }
    return $drivers;
});
