<?php

namespace console\controllers;

use console\models\ApiToken;
use console\models\HisRequest;
use console\models\PatientCaseApprove;
use yii\console\Controller;
use Yii;

class LisSendPathoController extends Controller
{
    /**
     * Console action: รันกระบวนการส่งผลไปยัง external API
     * เรียกจาก CLI: php /path/to/yii lis-api-send/create
     */
    public function actionCreate()
    {
        error_log("=== Hanuman Result Sikarin Patho START ===");

        $query = HisRequest::find()
            // ->where(['LabNo', ['LabNo' => null]])
            ->where(['LabNo' => 'OR25121300814'])
            ->andWhere(['not', ['HN' => null]])
            ->andWhere(['<>', 'LabNo', ''])
            ->andWhere(['<>', 'HN', ''])
            ->asArray()
            ->orderBy(['ID' => SORT_DESC])
            ->one();

        error_log("Total records: " . count($query));

        $sendMapping = [];

        // config
        /*$cfg = Yii::$app->params['external_api'] ?? null;
        if (!$cfg || empty($cfg['receiveUrl'])) {
            error_log("Missing external_api config or receiveUrl");
            return 1;
        }

        // get token
        $tokenError = null;
        $token = $this->getValidApiToken($cfg, $tokenError);
        if (empty($token)) {
            error_log("Cannot obtain valid API token: " .
                (is_array($tokenError) ? json_encode($tokenError) : ($tokenError ?? 'unknown')));
            return 1;
        }

        $url = $cfg['receiveUrl'];
        error_log("Using API token (ready to send)");*/

        // ---------- build grouped payloads ----------
        $groupedByLabNo = [];
        $groupedRows = []; // เก็บแถวต้นฉบับต่อ labNo เพื่อใช้บันทึก LisResult ทีหลัง

        $labNo = $query['LabNo'];
        $labNoNumeric = substr($labNo, 2);

        $query_lab = PatientCaseApprove::find()
            ->alias('pc')
            ->select([
                'pc.id_case',
                'pc.ln',
                'pc.h_n',
                'pc.report_at',
                'pc.approve_at',
                'pc.pathologist',
                'pc.approve_status',

                'hr.ID AS HisRequestID',
                'hr.LabNo AS HisLabNo',

                'tl.TestCode',
                'tl.TestName',
            ])
            ->leftJoin(
                ['hr' => 'HisRequest'],
                'hr.LabNo LIKE CONCAT("%", :labno, "%")',
                [':labno' => $labNoNumeric]
            )
            ->leftJoin(
                ['tl' => 'HisRequestTestList'],
                'tl.HisRequestID = hr.ID'
            )
            ->where(['not', ['pc.id_case' => null]])
            ->andWhere(['<>', 'pc.id_case', ''])
            ->andWhere(['like', 'pc.ln', $labNoNumeric])
            ->andWhere(['not', ['pc.report_at' => null]])
            ->andWhere(['<>', 'pc.report_at', ''])
            ->andWhere(['not', ['pc.approve_at' => null]])
            ->andWhere(['<>', 'pc.approve_at', ''])
            ->andWhere(['pc.approve_status' => 4])
            ->orderBy(['pc.approve_at' => SORT_DESC])
            ->asArray()
            ->all();


        foreach ($query_lab as $rows) {
            $filesData = [];
            $timestamp = date('dmYHis');
            $labNo = $rows['LabNo'] ?? '';
            $idCase = $rows['id_case'] ?? '';
            $hn = $rows['h_n'] ?? '';

            if (!empty($idCase)) {
                $LisResultUrl = "https://pathology.prolab.co.th/reports/H1263/{$labNo}_{$hn}_Prolab-{$idCase}.pdf";
                $pdfContent = $this->fetchUrl($LisResultUrl, 20);
                if ($pdfContent !== false && strlen($pdfContent) > 0) {
                    $filesData[] = [
                        'FileName'   => "{$labNo}_{$timestamp}.pdf",
                        'Filebase64' => base64_encode($pdfContent),
                    ];
                }
            }

            // build test item
            $testItem = [
                'TestCode' => (string)($rows['TestCode'] ?? ''),
                'TestName' => (string)($rows['TestName'] ?? ''),
                'TestRemark' => '',
                'InformCriticalByCode' => '',
                'InformCriticalBy' => '',
                'InformCriticalTo' => '',
                'InformCriticalDateTime' => '',
                'CriticalFlag' => '',
                'ReportResultByCode' => '',
                'ReportResultBy' => (string)($rows['pathologist'] ?? ''),
                'ReportResultDateTime' => $this->dateTimeFormat($rows['report_at'] ?? ''),
                'ApproveResultByCode' => '',
                'ApproveResultBy' => (string)($rows['pathologist'] ?? ''),
                'ApproveResultDateTime' => $this->dateTimeFormat($rows['approve_at'] ?? ''),
                'Filedata' => $filesData,
                'ResultList' => [[
                    'ResultCode' => (string)($rows['ResultCode'] ?? ''),
                    'ResultName' => (string)($rows['ResultName'] ?? ''),
                    'ResultValue' => '',
                    'ResultUnit' => '',
                    'ResultFlag' => '',
                    'ReferenceRange' => '',
                    'ResultRemark' => '',
                ]],
            ];

            if (!isset($groupedByLabNo[$labNo])) {
                $groupedByLabNo[$labNo] = [
                    'LabNo' => $labNo,
                    'RequestRemark' => '',
                    'ResultStatus' => '',
                    'TestList' => [],
                ];
            }

            $groupedByLabNo[$labNo]['TestList'][] = $testItem;
            $groupedRows[$labNo][] = $rows; // เก็บแถวต้นฉบับต่อ lab
            $sendMapping[$rows['id_case']] = $rows['id_case'];
        } // end foreach build

        // ---------- ส่งแต่ละ payload ที่ grouped ----------
        /*foreach ($groupedByLabNo as $labNo => $payload) {
            if (empty($payload) || !isset($payload['LabNo'])) {
                $labKey = $payload['LabNo'] ?? ($labNo ?? '(unknown)');
                error_log("Skipping send: payload missing LabNo for key={$labKey}");
                continue;
            }

            $payloadJson = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            $payloadJson = ($payloadJson === false) ? var_export($payload, true) : $payloadJson;
            error_log("Payload (truncated): " . $this->truncateForLog($payloadJson, 8192));

            $tests = array_map(function ($t) {
                $code = $t['TestCode'] ?? '(noCode)';
                $name = $t['TestName'] ?? '(noName)';
                return $code . ':' . $name;
            }, $payload['TestList'] ?? []);

            error_log("Sending payload for LabNo={$labNo} (tests=" . json_encode($tests, JSON_UNESCAPED_UNICODE) . ")");

            $response = $this->postJsonCurl($url, $payload, [
                'Authorization' => "Bearer {$token}",
                'Content-Type' => 'application/json',
            ]);

            $lastResponse = $response;
            $rawResp = $response['response'] ?? '';
            $httpCode = (int)($response['http_code'] ?? 0);
            $curlErr = $response['curl_error'] ?? null;

            if (!empty($curlErr)) {
                error_log("WARNING: cURL error: {$curlErr}");
            }

            if ($httpCode === 401) {
                error_log("401 Unauthorized received — refreshing token and retrying");
                $token = $this->getValidApiToken($cfg, $tokenError);
                if (!empty($token)) {
                    error_log("Retrying with refreshed token. Payload (truncated): " . $this->truncateForLog($payloadJson, 8192));
                    $response = $this->postJsonCurl($url, $payload, [
                        'Authorization' => "Bearer {$token}",
                        'Content-Type' => 'application/json',
                    ]);
                    $lastResponse = $response;
                    $rawResp = $response['response'] ?? '';
                    $httpCode = (int)($response['http_code'] ?? 0);
                    $curlErr = $response['curl_error'] ?? null;
                    if (!empty($curlErr)) {
                        error_log("WARNING: cURL error on retry: {$curlErr}");
                    }
                } else {
                    error_log("ERROR: Failed to refresh token after 401");
                }
            }

            error_log("Response (http={$httpCode}): " . $this->truncateForLog((string)$rawResp, 8192));

            // decode and check success
            $decoded = json_decode($rawResp, true);
            $statusVal = $decoded['Status'] ?? $decoded['status'] ?? null;
            $messageVal = $decoded['Message'] ?? $decoded['message'] ?? '';

            $success = false;
            if (is_numeric($statusVal)) {
                $success = (intval($statusVal) === 0);
            } elseif (is_string($statusVal) && strtolower($statusVal) === 'success') {
                $success = true;
            } elseif (is_string($messageVal) && strpos(strtolower($messageVal), 'success') !== false) {
                $success = true;
            } elseif (is_string($rawResp) && strpos(strtolower($rawResp), '"status":0') !== false) {
                $success = true;
            }

            if ($success) {
                error_log("Success LabNo={$labNo}");

                // บันทึก LisResult สำหรับทุก TestList ใน lab นี้
                $testList = $payload['TestList'] ?? [];
                foreach ($testList as $testItem) {
                    $testCode = (string)($testItem['TestCode'] ?? '');
                    $testName = (string)($testItem['TestName'] ?? '');
                    $resultValue = $testItem['ResultList'][0]['ResultValue'] ?? '';
                    $resultUnit = $testItem['ResultList'][0]['ResultUnit'] ?? '';
                    $referenceRange = $testItem['ResultList'][0]['ReferenceRange'] ?? '';

                    // หาแถวต้นฉบับที่ตรงกับ testCode เพื่อดึง CustID/HN/RiaHDocID ถ้ามี
                    $matchRow = null;
                    if (!empty($groupedRows[$labNo])) {
                        foreach ($groupedRows[$labNo] as $r) {
                            if ((string)($r['TestCode'] ?? '') === $testCode) {
                                $matchRow = $r;
                                break;
                            }
                        }
                    }
                    // ถ้าไม่พบ ให้ใช้แถวแรกเป็น fallback (ถ้ามี)
                    if ($matchRow === null && !empty($groupedRows[$labNo])) {
                        $matchRow = $groupedRows[$labNo][0];
                    }

                    $custId = $matchRow['CustID'] ?? null;
                    $hn = $matchRow['PatHN'] ?? null;
                    $riahDocId = $matchRow['RiaHDocID'] ?? null;
                    $riahDocDate = $matchRow['RiaHDocDate'] ?? null;

                    // ตรวจสอบว่ามีอยู่แล้วหรือยัง (ใช้ lab_no + test_code + cust_id + hn)
                    $existsQuery = [
                        'lab_no' => $labNo,
                        'test_code' => $testCode,
                    ];
                    if ($custId !== null) $existsQuery['cust_id'] = $custId;
                    if ($hn !== null) $existsQuery['hn'] = $hn;

                    $exists = LisResult::find()->where($existsQuery)->exists();

                    if (!$exists) {
                        $LisResult = new LisResult([
                            'lab_no' => $labNo,
                            'riah_doc_id' => $riahDocId ?? '',
                            'riah_doc_date' => $riahDocDate ?? '',
                            'test_code' => $testCode,
                            'test_name' => $testName,
                            'cust_id' => $custId ?? '',
                            'hn' => $hn,
                            'result_value' => $resultValue ?? '',
                            'result_unit' => $resultUnit ?? '',
                            'reference_range' => $referenceRange ?? '',
                            'status' => 1,
                        ]);
                        $LisResult->save(false);
                    } else {
                        trigger_error("Record already exists in LisResult, skipping insert: LabNo={$labNo}, TestCode={$testCode}, CustID={$custId}, HN={$hn}", E_USER_WARNING);
                    }
                } // end foreach testList
            } else {
                $msg = $lastResponse['response'] ?? ($lastResponse['curl_error'] ?? 'ไม่พบข้อมูลตอบกลับ');
                error_log("FAIL LabNo={$labNo}; last_http=" . ($lastResponse['http_code'] ?? 'ไม่พบข้อมูลตอบกลับ') . "; message=" . $this->truncateForLog($msg, 1024));
            }

            // optional small delay: usleep(200000);
        } // end foreach groupedByLabNo*/

        error_log("=== Hanuman Result Sikarin Patho END ===");

        var_dump($groupedByLabNo);
        die();
        //return 0;
    }

    /** helper: ส่ง JSON ผ่าน cURL (คืนข้อมูล debug เต็ม) */
    protected function postJsonCurl($url, $payload, $headers = [])
    {
        $ch = curl_init($url);
        $jsonPayload = json_encode($payload, JSON_UNESCAPED_UNICODE);

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonPayload);

        // security: allow config to control SSL verify
        $verifySsl = Yii::$app->params['external_api']['verify_ssl'] ?? true;
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verifySsl);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verifySsl ? 2 : 0);

        curl_setopt($ch, CURLOPT_TIMEOUT, 60);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

        $curlHeaders = ['Content-Type: application/json', 'Accept: application/json', 'Expect:', 'User-Agent: MyLabSender/1.0'];
        foreach ($headers as $k => $v) {
            if (is_int($k)) {
                $curlHeaders[] = $v;
            } else {
                $curlHeaders[] = "{$k}: {$v}";
            }
        }
        curl_setopt($ch, CURLOPT_HTTPHEADER, $curlHeaders);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlErrno = curl_errno($ch);
        $curlError = $curlErrno ? curl_error($ch) : null;
        curl_close($ch);

        return [
            'http_code' => $httpCode,
            'response' => $response,
            'curl_error' => $curlError,
            'curl_errno' => $curlErrno,
            'url' => $url,
            'payload_size' => strlen($jsonPayload),
        ];
    }

    /**
     * helper: fetch URL via cURL (robust)
     */
    protected function fetchUrl($url, $timeout = 10)
    {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        $data = curl_exec($ch);
        $http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $err = curl_errno($ch) ? curl_error($ch) : null;
        curl_close($ch);

        if ($data === false || $http >= 400) {
            //$this->logConsole("fetchUrl failed: {$url} http={$http} err=" . ($err ?? 'none'), 'warning');
            error_log("fetchUrl failed: {$url} http={$http} err=" . ($err ?? 'none'), 'warning');
            return false;
        }
        return $data;
    }

    /**
     * Log helper — writes to STDOUT with timestamp and also to Yii log.
     * $level: 'info'|'warning'|'error'
     */
    protected function logConsole($message, $level = 'info')
    {
        $time = date('Y-m-d H:i:s');
        $line = "[{$time}] {$message}" . PHP_EOL;
        // write to console
        $this->stdout($line);
        // write to Yii logger with appropriate level
        switch (strtolower($level)) {
            case 'error':
                Yii::error($message, __METHOD__);
                break;
            case 'warning':
                Yii::warning($message, __METHOD__);
                break;
            default:
                Yii::info($message, __METHOD__);
        }
    }

    /**
     * Truncate long strings for logging. $maxBytes = null => no truncation.
     */
    protected function truncateForLog($text, $maxBytes = 4096)
    {
        if ($text === null) return '';
        if ($maxBytes === null) return $text;
        $len = strlen($text);
        if ($len <= $maxBytes) return $text;
        $prefix = substr($text, 0, $maxBytes);
        return $prefix . "\n...TRUNCATED... (original_length={$len})";
    }

    /** helper: แปลง datetime เป็น dd/mm/YYYY H:i:s */
    public function dateTimeFormat($value)
    {
        if (empty($value)) return '';
        try {
            $dt = new \DateTime($value);
            return $dt->format('d/m/Y H:i:s');
        } catch (\Throwable $e) {
            return '';
        }
    }

    /**
     * ขอ token จาก API (คืนค่า associative array แบบ {ok, token, expires_at, http_code, raw, error})
     */
    protected function getApiToken($cfg)
    {
        $tokenUrl = $cfg['tokenUrl'] ?? Yii::$app->params['external_api']['tokenUrl'] ?? null;
        $username = $cfg['username'] ?? Yii::$app->params['external_api']['username'] ?? null;
        $password = $cfg['password'] ?? Yii::$app->params['external_api']['password'] ?? null;

        $payload = ['username' => $username, 'password' => $password];

        $ch = curl_init($tokenUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Accept: application/json']);
        curl_setopt($ch, CURLOPT_TIMEOUT, 15);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

        $raw = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlErr = curl_errno($ch) ? curl_error($ch) : null;
        curl_close($ch);

        $result = ['ok' => false, 'token' => null, 'expires_at' => null, 'http_code' => $httpCode, 'raw' => $raw, 'error' => null];

        if ($raw === false || $curlErr) {
            $result['error'] = 'cURL error: ' . ($curlErr ?? 'unknown');
            return $result;
        }

        $decoded = json_decode($raw, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            $result['error'] = 'Invalid JSON response from token endpoint';
            return $result;
        }

        if (!empty($decoded['access_token'])) {
            $result['ok'] = true;
            $result['token'] = $decoded['access_token'];
            $expiresIn = intval($decoded['expires_in'] ?? 3600);
            $result['expires_at'] = date('Y-m-d H:i:s', time() + $expiresIn);
        } elseif (!empty($decoded['token'])) {
            $result['ok'] = true;
            $result['token'] = $decoded['token'];
            $expiresIn = intval($decoded['expires_in'] ?? 3600);
            $result['expires_at'] = date('Y-m-d H:i:s', time() + $expiresIn);
        } else {
            $result['error'] = $decoded['error'] ?? ($decoded['message'] ?? 'No token in response');
        }

        return $result;
    }

    /**
     * ดึง token ที่ valid (คืน token string หรือ null และตั้ง $tokenError หากมีปัญหา)
     * พร้อม debug log วันที่หมดอายุของ Token
     */
    protected function getValidApiToken($cfg, &$tokenError = null)
    {
        $apiName = $cfg['apiName'] ?? 'gems_api';
        $tokenModel = ApiToken::find()->where(['api_name' => $apiName])->one();
        $token = null;
        $nowTs = time();
        $refreshMargin = $cfg['refreshMargin'] ?? 300; // 5 นาที

        $needRequest = !$tokenModel || (strtotime($tokenModel->expires_at) <= ($nowTs + $refreshMargin));

        if ($tokenModel) {
            //$this->logConsole("Token in DB found for {$apiName}: expires_at={$tokenModel->expires_at}, token_prefix=" . substr($tokenModel->token ?? '', 0, 15) . "...");
            error_log("Token in DB found for {$apiName}: expires_at={$tokenModel->expires_at}, token_prefix=" . substr($tokenModel->token ?? '', 0, 15) . "...");
        } else {
            error_log("No token found in DB for {$apiName}, will request new token");
            //$this->logConsole("No token found in DB for {$apiName}, will request new token");
        }

        if ($needRequest) {
            $tokenResp = $this->getApiToken($cfg);

            if (empty($tokenResp) || empty($tokenResp['ok'])) {
                $tokenError = [
                    'message' => 'Failed to get token from API',
                    'tokenUrl' => $cfg['tokenUrl'] ?? null,
                    'error' => $tokenResp['error'] ?? 'Unknown error',
                    'http_code' => $tokenResp['http_code'] ?? 0,
                    'raw' => $tokenResp['raw'] ?? null,
                ];

                if ($tokenModel && !empty($tokenModel->token)) {
                    error_log("Using old token as fallback (expires_at={$tokenModel->expires_at})");
                    //$this->logConsole("Using old token as fallback (expires_at={$tokenModel->expires_at})");
                    return $tokenModel->token; // fallback
                }

                return null;
            }

            $token = $tokenResp['token'];
            $expiresAt = $tokenResp['expires_at'] ?? date('Y-m-d H:i:s', time() + 3600);

            if (!$tokenModel) {
                $tokenModel = new ApiToken();
                $tokenModel->api_name = $apiName;
            }

            $tokenModel->token = $token;
            $tokenModel->expires_at = $expiresAt;

            try {
                $tokenModel->save(false);
                error_log("New token saved: api_name={$apiName}, expires_at={$expiresAt}, token_prefix=" . substr($token ?? '', 0, 15) . "...");
                //$this->logConsole("New token saved: api_name={$apiName}, expires_at={$expiresAt}, token_prefix=" . substr($token, 0, 15) . "...");
            } catch (\Throwable $e) {
                //$this->logConsole("Error saving new token: " . $e->getMessage(), 'error');
                error_log("Error saving new token: " . $e->getMessage(), 'error');
                if ($tokenModel && !empty($tokenModel->token)) {
                    return $tokenModel->token; // fallback
                }
                $tokenError = $e->getMessage();
                return null;
            }
        } else {
            $token = $tokenModel->token;
            //$this->logConsole("Reusing valid token: expires_at={$tokenModel->expires_at}, token_prefix=" . substr($token, 0, 15) . "...");
            error_log("Reusing valid token: expires_at={$tokenModel->expires_at}, token_prefix=" . substr($token, 0, 15) . "...");
        }
        return $token;
    }
}
