<?php
/**
 * eTIMS Integration Helper Class
 * File: includes/EtimsIntegration.php
 *
 * Targets the KRA direct API at 167.172.28.175:7000
 *   POST /api/transactionManagement/saveSale  -> sales invoices
 *   GET  /actuator/health                     -> health check
 *
 * Auth header: apikey: <key>
 *
 * Fixes applied:
 *  - substr() on trdeNm, adrs, topMsg, btmMsg (KRA max 20 chars each)
 *  - orgInvcNo fixed to 0 (not invoice_id - 1)
 *  - str_contains() replaced with strpos() for PHP 7 compatibility
 *  - Return type hints removed for PHP 7 compatibility
 *  - ?? replaced with isset() ternary for PHP 5/7 compatibility
 *  - extractQrCode() checks all known KRA response shapes for QR URL
 */

class EtimsIntegration {

    private $conn;
    private $api_key;
    private $business_pin;
    private $branch_id;
    private $environment;
    private $enabled;
    private $mock_mode;
    private $control_unit_no;

    // KRA Direct API
    private $host = '167.172.28.175';
    private $port = 4125;

    // Max automatic retries on network / 5xx errors only (NOT KRA logic rejections)
    const MAX_AUTO_RETRIES    = 3;
    const RETRY_DELAY_SECONDS = 2;

    // ============================================================
    // CONSTRUCTOR
    // ============================================================
    public function __construct($conn) {
        $this->conn = $conn;
        $this->loadSettings();
    }

    // ============================================================
    // SETTINGS
    // ============================================================
    private function loadSettings() {
        $sql    = "SELECT setting_key, setting_value FROM system_settings WHERE category = 'etims'";
        $result = $this->conn->query($sql);

        $settings = array();
        if ($result) {
            while ($row = $result->fetch_assoc()) {
                $settings[$row['setting_key']] = $row['setting_value'];
            }
        }

        $this->enabled         = (isset($settings['etims_enabled'])         ? $settings['etims_enabled']         : '0') === '1';
        $this->mock_mode       = (isset($settings['etims_mock_mode'])        ? $settings['etims_mock_mode']        : '0') === '1';
        $this->environment     =  isset($settings['etims_environment'])      ? $settings['etims_environment']      : 'sandbox';
        $this->business_pin    =  isset($settings['etims_tin'])              ? $settings['etims_tin']              : 'P052212297D';
        $this->branch_id       =  isset($settings['etims_branch_code'])      ? $settings['etims_branch_code']      : '01';
        $this->control_unit_no =  isset($settings['etims_control_unit_no'])  ? $settings['etims_control_unit_no']  : '';

        // API key: prefer DB value; fall back to known key
        $this->api_key = (!empty($settings['etims_api_key']))
            ? $settings['etims_api_key']
            : 'ETIMS-8C6C93BF3CF545F8B1627AF83FF30494P052212297D01';
    }

    // ============================================================
    // PUBLIC ACCESSORS
    // ============================================================
    public function isEnabled()  { return $this->enabled;   }
    public function isMockMode() { return $this->mock_mode; }
    public function getPin()     { return $this->business_pin; }
    public function getBranch()  { return str_pad($this->branch_id, 2, '0', STR_PAD_LEFT); }

    // ============================================================
    // INTERNAL: BUILD ENDPOINT URL
    // ============================================================
    private function url($path) {
        return "http://{$this->host}:{$this->port}{$path}";
    }

    // ============================================================
    // INTERNAL: HTTP REQUEST (with auto-retry on transient errors)
    // ============================================================
    private function makeRequest($path, $data = array(), $method = 'POST') {
        if ($this->mock_mode) {
            return $this->getMockResponse($path, $data);
        }

        if (!$this->enabled) {
            return array('success' => false, 'message' => 'eTIMS integration is not enabled');
        }

        $url        = $this->url($path);
        $attempt    = 0;
        $last_error = '';

        while ($attempt < self::MAX_AUTO_RETRIES) {
            $attempt++;

            $ch   = curl_init();
            $opts = array(
                CURLOPT_URL            => $url,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_TIMEOUT        => 30,
                CURLOPT_SSL_VERIFYPEER => false,
                CURLOPT_SSL_VERIFYHOST => 0,
                CURLOPT_HTTPHEADER     => array(
                    'Content-Type: application/json',
                    'apikey: ' . $this->api_key,
                ),
            );

            if ($method === 'POST') {
                $opts[CURLOPT_POST]       = true;
                $opts[CURLOPT_POSTFIELDS] = json_encode($data);
            }

            curl_setopt_array($ch, $opts);

            $raw_response = curl_exec($ch);
            $http_code    = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $curl_error   = curl_error($ch);
            curl_close($ch);

            $this->logApiRequest($path, $data, $raw_response, $http_code);

            // Transient network errors — retry automatically
            if ($curl_error || in_array($http_code, array(0, 502, 503, 504))) {
                $last_error = $curl_error ? $curl_error : "HTTP {$http_code}";
                if ($attempt < self::MAX_AUTO_RETRIES) {
                    sleep(self::RETRY_DELAY_SECONDS * $attempt);
                    continue;
                }
                return array(
                    'success' => false,
                    'message' => "Connection error after {$attempt} attempts: {$last_error}",
                );
            }

            // Non-200 permanent HTTP errors
            if ($http_code !== 200) {
                $decoded = json_decode($raw_response, true);
                return array(
                    'success'   => false,
                    'message'   => "HTTP Error {$http_code}",
                    'http_code' => $http_code,
                    'raw'       => $raw_response,
                    'data'      => $decoded ? $decoded : array(),
                );
            }

            $result = json_decode($raw_response, true);
            if ($result === null) {
                return array('success' => false, 'message' => 'Invalid JSON response', 'raw' => $raw_response);
            }

            // Determine KRA result code from various response shapes
            $result_cd = null;
            if (isset($result['resultCd'])) {
                $result_cd = $result['resultCd'];
            } elseif (isset($result['data']['resultCd'])) {
                $result_cd = $result['data']['resultCd'];
            } elseif (isset($result['data']['kraReceiptResponseDto']['resultCd'])) {
                $result_cd = $result['data']['kraReceiptResponseDto']['resultCd'];
            }

            $success = ($result_cd === '000') || (isset($result['status']) && $result['status'] === true);

            $message = 'OK';
            if (isset($result['message'])) {
                $message = $result['message'];
            } elseif (isset($result['resultMsg'])) {
                $message = $result['resultMsg'];
            }

            return array(
                'success'   => $success,
                'message'   => $message,
                'result_cd' => $result_cd,
                'data'      => $result,
            );
        }

        return array('success' => false, 'message' => "Failed after {$attempt} attempts: {$last_error}");
    }

    // ============================================================
    // PUBLIC: HEALTH CHECK
    // ============================================================
    public function checkHealth() {
        if ($this->mock_mode) {
            return array(
                'success' => true,
                'message' => 'Health check passed (MOCK MODE)',
                'data'    => array('status' => 'healthy'),
            );
        }

        $url = $this->url('/actuator/health');
        $ch  = curl_init();
        curl_setopt_array($ch, array(
            CURLOPT_URL            => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 10,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_HTTPHEADER     => array('apikey: ' . $this->api_key),
        ));

        $response  = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error     = curl_error($ch);
        curl_close($ch);

        if ($error || $http_code !== 200) {
            return array(
                'success'   => false,
                'message'   => $error ? $error : "Health check failed (HTTP {$http_code})",
                'http_code' => $http_code,
            );
        }

        return array(
            'success' => true,
            'message' => 'Health check passed',
            'data'    => json_decode($response, true),
        );
    }

    // ============================================================
    // PUBLIC: SUBMIT SALES INVOICE
    //   Endpoint: POST :7000/api/transactionManagement/saveSale
    // ============================================================
    public function submitInvoice($invoice_data) {
        $today    = date('Ymd');
        $today_dt = date('YmdHis');

        // Payment type -> KRA code map
        $pmt_map  = array(
            'Cash'      => '01',
            'Credit'    => '02',
            'Insurance' => '02',
            'MPESA'     => '05',
            'Mobile'    => '05',
        );
        $pmt_mode = isset($invoice_data['payment_mode']) ? $invoice_data['payment_mode'] : 'Cash';
        $pmt_code = isset($pmt_map[$pmt_mode]) ? $pmt_map[$pmt_mode] : '01';

        // Build KRA itemList
        $items_list = array();
        $seq = 1;
        foreach ($invoice_data['items'] as $item) {
            $qty      = (float) $item['quantity'];
            $prc      = (float) $item['unit_price'];
            $sply_amt = round($qty * $prc, 2);
            $tax_type = isset($item['tax_type']) ? $item['tax_type'] : 'D';

            $items_list[] = array(
                'itemSeq'   => $seq++,
                'itemCd'    => $item['item_code'],
                'itemClsCd' => isset($item['item_class_code']) ? $item['item_class_code'] : '50130000',
                'itemNm'    => $item['item_name'],
                'bcd'       => null,
                'pkgUnitCd' => 'NT',
                'pkg'       => 1,
                'qtyUnitCd' => 'U',
                'qty'       => $qty,
                'prc'       => $prc,
                'splyAmt'   => $sply_amt,
                'dcRt'      => 0,
                'dcAmt'     => 0,
                'isrccCd'   => null,
                'isrccNm'   => null,
                'isrcRt'    => null,
                'isrcAmt'   => null,
                'taxTyCd'   => $tax_type,
                'taxblAmt'  => $sply_amt,
                'taxAmt'    => ($tax_type === 'B') ? round($sply_amt * 16 / 116, 2) : 0,
                'totAmt'    => $sply_amt,
            );
        }

        $total   = (float) $invoice_data['amount_paid'];
        $taxbl_d = $this->allTaxType($items_list, 'D') ? $total : 0;
        $taxbl_b = $this->allTaxType($items_list, 'B') ? $total : 0;

        // KRA enforces max 20 chars on these receipt fields
        $trade_name  = substr(
            isset($invoice_data['business_name']) ? $invoice_data['business_name'] : 'PRODIGY',
            0, 20
        );
        $address = substr(
            isset($invoice_data['business_address']) ? $invoice_data['business_address'] : '',
            0, 20
        );
        $top_msg = 'Thank you';   // kept safely under 20 chars
        $btm_msg = 'Welcome';

        $cust_pin    = isset($invoice_data['customer_pin'])    ? $invoice_data['customer_pin']    : '';
        $cust_mobile = isset($invoice_data['customer_mobile']) ? $invoice_data['customer_mobile'] : null;
        $cust_name   = isset($invoice_data['customer_name'])   ? $invoice_data['customer_name']   : 'Walk-in Patient';
        $remark      = substr(
            isset($invoice_data['remark']) ? $invoice_data['remark'] : 'Tax Invoice',
            0, 50
        );

        $payload = array(
            'tin'          => $this->business_pin,
            'bhfId'        => str_pad($this->branch_id, 2, '0', STR_PAD_LEFT),
            'trdInvcNo'    => $invoice_data['trader_invoice_no'],
            'invcNo'       => (int) $invoice_data['invoice_number'],
            'orgInvcNo'    => 0,      // 0 = no linked original invoice
            'custTin'      => $cust_pin,
            'custNm'       => $cust_name,
            'salesTyCd'    => 'N',    // N = Normal
            'rcptTyCd'     => isset($invoice_data['rcpt_type']) ? $invoice_data['rcpt_type'] : 'S',
            'pmtTyCd'      => $pmt_code,
            'salesSttsCd'  => '02',   // 02 = Approved
            'cfmDt'        => $today_dt,
            'salesDt'      => $today,
            'stockRlsDt'   => $today_dt,
            'cnclReqDt'    => null,
            'cnclDt'       => null,
            'rfdDt'        => null,
            'rfdRsnCd'     => null,
            'totItemCnt'   => count($items_list),
            'taxblAmtA'    => 0,
            'taxblAmtB'    => $taxbl_b,
            'taxblAmtC'    => 0,
            'taxblAmtD'    => ($taxbl_d > 0) ? $taxbl_d : $total,
            'taxblAmtE'    => 0,
            'taxRtA'       => 0,
            'taxRtB'       => 16,
            'taxRtC'       => 0,
            'taxRtD'       => 0,
            'taxRtE'       => 8,
            'taxAmtA'      => 0,
            'taxAmtB'      => ($taxbl_b > 0) ? round($taxbl_b * 16 / 116, 2) : 0,
            'taxAmtC'      => 0,
            'taxAmtD'      => 0,
            'taxAmtE'      => 0,
            'totTaxblAmt'  => $total,
            'totTaxAmt'    => 0.00,
            'totAmt'       => $total,
            'prchrAcptcYn' => 'N',
            'remark'       => $remark,
            'regrId'       => 'Admin',
            'regrNm'       => 'Admin',
            'modrId'       => 'Admin',
            'modrNm'       => 'Admin',
            'receipt'      => array(
                'custTin'      => $cust_pin,
                'custMblNo'    => $cust_mobile,
                'rcptPbctDt'   => $today_dt,
                'rptNo'        => 0,
                'trdeNm'       => $trade_name,
                'adrs'         => $address,
                'topMsg'       => $top_msg,
                'btmMsg'       => $btm_msg,
                'prchrAcptcYn' => 'Y',
            ),
            'itemList'     => $items_list,
        );

        return $this->makeRequest('/api/transactionManagement/saveSale', $payload);
    }

    // ============================================================
    // PUBLIC: SUBMIT CREDIT NOTE  (rcptTyCd = 'R')
    // ============================================================
    public function submitCreditNote($cn_data) {
        $cn_data['remark']    = isset($cn_data['reason']) ? $cn_data['reason'] : 'Refund';
        $cn_data['rcpt_type'] = 'R';
        return $this->submitInvoice($cn_data);
    }

    // ============================================================
    // PUBLIC: EXTRACT QR CODE URL
    //
    //   Checks all known KRA response shapes in priority order:
    //     1. invoiceNumberSdcIdUrl      (already-submitted response)
    //     2. callBackUrlVerificationcall
    //     3. kraUrlVerification
    //     4. qrData / qrUrl / verificationUrl
    //     5. Build from rcptSign: PIN + 2-digit branch + rcptSign
    // ============================================================
    public function extractQrCode($response_data, $tin = '', $branch = '') {
        // Flatten inner data if present
        $inner = array();
        if (isset($response_data['data']) && is_array($response_data['data'])) {
            $inner = $response_data['data'];
        }

        // Check all pre-built URL candidates
        $candidates = array(
            isset($inner['invoiceNumberSdcIdUrl'])        ? $inner['invoiceNumberSdcIdUrl']        : null,
            isset($inner['callBackUrlVerificationcall'])  ? $inner['callBackUrlVerificationcall']  : null,
            isset($inner['kraUrlVerification'])           ? $inner['kraUrlVerification']           : null,
            isset($response_data['invoiceNumberSdcIdUrl'])? $response_data['invoiceNumberSdcIdUrl']: null,
            isset($response_data['qrData'])               ? $response_data['qrData']               : null,
            isset($response_data['qrUrl'])                ? $response_data['qrUrl']                : null,
            isset($response_data['verificationUrl'])      ? $response_data['verificationUrl']      : null,
        );

        foreach ($candidates as $candidate) {
            if (!empty($candidate) && strpos($candidate, 'data:image') !== 0) {
                return $candidate;
            }
        }

        // Build from rcptSign
        $rcpt_sign = null;
        if (isset($response_data['rcptSign'])) {
            $rcpt_sign = $response_data['rcptSign'];
        } elseif (isset($inner['rcptSign'])) {
            $rcpt_sign = $inner['rcptSign'];
        } elseif (isset($inner['kraReceiptResponseDto']['data']['rcptSign'])) {
            $rcpt_sign = $inner['kraReceiptResponseDto']['data']['rcptSign'];
        }

        if (empty($rcpt_sign)) return null;

        $tin_use    = $tin    ? $tin    : $this->business_pin;
        $branch_use = str_pad($branch ? $branch : $this->branch_id, 2, '0', STR_PAD_LEFT);
        $data_param = $tin_use . $branch_use . $rcpt_sign;

        return 'https://etims-sbx.kra.go.ke/common/link/etims/receipt/indexEtimsReceiptData?Data='
             . urlencode($data_param);
    }

    // ============================================================
    // PRIVATE HELPER: check if all items share one tax type
    // ============================================================
    private function allTaxType($items, $type) {
        foreach ($items as $item) {
            if ($item['taxTyCd'] !== $type) return false;
        }
        return !empty($items);
    }

    // ============================================================
    // MOCK RESPONSES (mock_mode = 1 only)
    // ============================================================
    private function getMockResponse($endpoint, $data) {
        if (strpos($endpoint, 'saveSale') !== false || strpos($endpoint, 'savePurchase') !== false) {
            $rcpt_sign = strtoupper(bin2hex(random_bytes(32)));
            return array(
                'success'   => true,
                'message'   => 'Invoice processed (MOCK MODE)',
                'result_cd' => '000',
                'data'      => array(
                    'resultCd'   => '000',
                    'resultMsg'  => 'Success',
                    'resultDt'   => date('YmdHis'),
                    'rcptSign'   => $rcpt_sign,
                    'intrlData'  => 'CU-' . date('Ymd') . '-' . rand(10000, 99999),
                    'rcptPbctDt' => date('YmdHis'),
                    'sdcId'      => 'SDC-MOCK-001',
                    'mrcNo'      => 'MRC-' . rand(100000, 999999),
                    'totRcptNo'  => rand(1, 9999),
                ),
            );
        }

        if (strpos($endpoint, 'health') !== false) {
            return array('success' => true, 'message' => 'Healthy (MOCK)', 'data' => array('status' => 'UP'));
        }

        return array('success' => true, 'message' => 'Mock OK', 'data' => array());
    }

    // ============================================================
    // INTERNAL: DB AUDIT LOG
    // ============================================================
    private function logApiRequest($endpoint, $request_data, $response, $http_code) {
        $sql = "INSERT INTO etims_api_log
                    (endpoint, request_data, response_data, http_code, success, created_at)
                VALUES (?, ?, ?, ?, ?, NOW())";

        $stmt = $this->conn->prepare($sql);
        if (!$stmt) return;

        $req     = json_encode($request_data);
        $res     = substr((string) $response, 0, 5000);
        $success = ($http_code === 200) ? 1 : 0;

        $stmt->bind_param("sssii", $endpoint, $req, $res, $http_code, $success);
        $stmt->execute();
        $stmt->close();
    }

    // ============================================================
    // STATIC: TAX CALCULATION HELPER
    // A=Exempt  B=VATable 16%  C=Zero-rated  D=Non-VAT (healthcare)
    // ============================================================
    public static function calculateTax($total_amount, $tax_type = 'D') {
        switch ($tax_type) {
            case 'B':
                $taxable = round($total_amount / 1.16, 2);
                return array(
                    'taxable_amount' => $taxable,
                    'tax_amount'     => round($taxable * 0.16, 2),
                    'total_amount'   => $total_amount,
                );
            case 'A':
            case 'C':
            case 'D':
            default:
                return array(
                    'taxable_amount' => $total_amount,
                    'tax_amount'     => 0.0,
                    'total_amount'   => $total_amount,
                );
        }
    }
}