<?php
namespace App\Models;

use App\Models\RefRange\RefNumModel;
use App\Models\OrderTest\OrderTestModel;
use App\Models\Patient\PatientModel;

class PatResultModel extends BaseModel {
    protected $table = 'patres';
    protected $primaryKey = 'ResultID';
    protected $allowedFields = [
        'SiteID',
        'OrderID',
        'InternalSID',
        'SID',
        'SampleID',
        'TestSiteID',
        'TestSiteCode',
        'AspCnt',
        'ResultCode',
        'Result',
        'ResultValue',
        'SampleType',
        'ResultDateTime',
        'WorkstationID',
        'EquipmentID',
        'RefNumID',
        'RefTxtID',
        'CreateDate',
        'EndDate',
        'ArchiveDate',
        'DelDate'
    ];

    /**
     * Validate result value against reference range and return flag
     * 
     * @param float|string $resultValue The result value to validate
     * @param int $refNumID The reference range ID
     * @param int $orderID The order ID to get patient info
     * @return string|null 'L' for low, 'H' for high, or null for normal/no match
     */
    public function validateAndFlag($resultValue, int $refNumID, int $orderID): ?string {
        if (!is_numeric($resultValue)) {
            return null;
        }

        $refNumModel = new RefNumModel();
        $ref = $refNumModel->find($refNumID);
        
        if (!$ref) {
            return null;
        }

        // Get patient info from order
        $orderModel = new OrderTestModel();
        $order = $orderModel->find($orderID);
        
        if (!$order) {
            return null;
        }

        $patientModel = new PatientModel();
        $patient = $patientModel->find($order['InternalPID']);
        
        if (!$patient) {
            return null;
        }

        // Check if patient matches criteria (sex)
        if (!empty($ref['Sex']) && $ref['Sex'] !== 'ALL') {
            if ($patient['Sex'] !== $ref['Sex']) {
                return null;
            }
        }

        // Check age criteria (AgeStart/AgeEnd stored in days)
        if ($ref['AgeStart'] !== null || $ref['AgeEnd'] !== null) {
            $birthdate = new \DateTime($patient['Birthdate']);
            $today = new \DateTime();
            $ageInDays = $birthdate->diff($today, true)->days;

            if ($ref['AgeStart'] !== null && $ageInDays < (int) $ref['AgeStart']) {
                return null;
            }
            if ($ref['AgeEnd'] !== null && $ageInDays > (int) $ref['AgeEnd']) {
                return null;
            }
        }

        $value = floatval($resultValue);
        $low = floatval($ref['Low']);
        $high = floatval($ref['High']);

        // Check low
        if ($ref['LowSign'] === '<=' && $value <= $low) {
            return 'L';
        }
        if ($ref['LowSign'] === '<' && $value < $low) {
            return 'L';
        }

        // Check high
        if ($ref['HighSign'] === '>=' && $value >= $high) {
            return 'H';
        }
        if ($ref['HighSign'] === '>' && $value > $high) {
            return 'H';
        }

        return null; // Normal
    }

    /**
     * Get all results for an order with test names and reference ranges
     * 
     * @param int $orderID
     * @return array
     */
    public function getByOrder(int $orderID): array {
        $builder = $this->db->table('patres pr');
        $builder->select('
            pr.ResultID,
            pr.OrderID,
            pr.TestSiteID,
            pr.TestSiteCode,
            pr.Result,
            pr.ResultDateTime,
            pr.RefNumID,
            pr.RefTxtID,
            pr.CreateDate,
            tds.TestSiteName,
            tds.Unit1,
            tds.Unit2,
            rn.Low,
            rn.High,
            rn.LowSign,
            rn.HighSign,
            rn.Display as RefDisplay
        ');
        $builder->join('testdefsite tds', 'tds.TestSiteID = pr.TestSiteID', 'left');
        $builder->join('refnum rn', 'rn.RefNumID = pr.RefNumID', 'left');
        $builder->where('pr.OrderID', $orderID);
        $builder->where('pr.DelDate', null);
        $builder->orderBy('tds.SeqScr', 'ASC');
        
        return $builder->get()->getResultArray();
    }

    /**
     * Get cumulative patient results across all orders
     * 
     * @param int $internalPID
     * @return array
     */
    public function getByPatient(int $internalPID): array {
        $builder = $this->db->table('patres pr');
        $builder->select('
            pr.ResultID,
            pr.OrderID,
            pr.TestSiteID,
            pr.TestSiteCode,
            pr.Result,
            pr.ResultDateTime,
            pr.RefNumID,
            tds.TestSiteName,
            tds.Unit1,
            tds.Unit2,
            ot.OrderID as OrderNumber,
            ot.TrnDate as OrderDate
        ');
        $builder->join('testdefsite tds', 'tds.TestSiteID = pr.TestSiteID', 'left');
        $builder->join('ordertest ot', 'ot.InternalOID = pr.OrderID', 'left');
        $builder->where('ot.InternalPID', $internalPID);
        $builder->where('pr.DelDate', null);
        $builder->orderBy('pr.ResultDateTime', 'DESC');
        
        return $builder->get()->getResultArray();
    }

    /**
     * Update result with validation and return flag (not stored)
     * 
     * @param int $resultID
     * @param array $data
     * @return array ['success' => bool, 'flag' => string|null, 'message' => string]
     */
    public function updateWithValidation(int $resultID, array $data): array {
        $this->db->transStart();

        try {
            $result = $this->find($resultID);
            if (!$result) {
                throw new \Exception('Result not found');
            }

            $flag = null;

            // If result value is being updated, validate it
            if (isset($data['Result']) && !empty($data['Result'])) {
                $refNumID = $data['RefNumID'] ?? $result['RefNumID'];
                
                if ($refNumID) {
                    $flag = $this->validateAndFlag($data['Result'], $refNumID, $result['OrderID']);
                }
            }

            // Set update timestamp
            $data['StartDate'] = date('Y-m-d H:i:s');

            $updated = $this->update($resultID, $data);

            if (!$updated) {
                throw new \Exception('Failed to update result');
            }

            $this->db->transComplete();

            // Fire result_updated rules (non-blocking)
            try {
                $fresh = $this->find($resultID);
                if (is_array($fresh) && !empty($fresh['OrderID']) && !empty($fresh['TestSiteID'])) {
                    $orderModel = new \App\Models\OrderTest\OrderTestModel();
                    $order = $orderModel->find((int) $fresh['OrderID']);

                    $patient = null;
                    $age = null;
                    if (is_array($order) && !empty($order['InternalPID'])) {
                        $patientModel = new \App\Models\Patient\PatientModel();
                        $patient = $patientModel->find((int) $order['InternalPID']);
                        if (is_array($patient) && !empty($patient['Birthdate'])) {
                            try {
                                $birthdate = new \DateTime((string) $patient['Birthdate']);
                                $age = (new \DateTime())->diff($birthdate)->y;
                            } catch (\Throwable $e) {
                                $age = null;
                            }
                        }
                    }

                    $engine = new \App\Services\RuleEngineService();
                    $engine->run('result_updated', [
                        'order' => [
                            'InternalOID' => (int) ($fresh['OrderID'] ?? 0),
                            'Priority' => is_array($order) ? ($order['Priority'] ?? null) : null,
                            'TestSiteID' => (int) ($fresh['TestSiteID'] ?? 0),
                        ],
                        'patient' => [
                            'Sex' => is_array($patient) ? ($patient['Sex'] ?? null) : null,
                        ],
                        'age' => $age,
                        'testSiteID' => (int) ($fresh['TestSiteID'] ?? 0),
                        'result' => $fresh,
                    ]);
                }
            } catch (\Throwable $e) {
                log_message('error', 'PatResultModel::updateWithValidation rule engine error: ' . $e->getMessage());
            }

            return [
                'success' => true,
                'flag' => $flag,
                'message' => 'Result updated successfully'
            ];
        } catch (\Exception $e) {
            $this->db->transRollback();
            return [
                'success' => false,
                'flag' => null,
                'message' => $e->getMessage()
            ];
        }
    }

    /**
     * Get single result with related data
     * 
     * @param int $resultID
     * @return array|null
     */
    public function getWithRelations(int $resultID): ?array {
        $builder = $this->db->table('patres pr');
        $builder->select('
            pr.*,
            tds.TestSiteName,
            tds.TestSiteCode,
            tds.Unit1,
            tds.Unit2,
            rn.Low,
            rn.High,
            rn.LowSign,
            rn.HighSign,
            rn.Display as RefDisplay,
            ot.OrderID as OrderNumber,
            ot.InternalPID
        ');
        $builder->join('testdefsite tds', 'tds.TestSiteID = pr.TestSiteID', 'left');
        $builder->join('refnum rn', 'rn.RefNumID = pr.RefNumID', 'left');
        $builder->join('ordertest ot', 'ot.InternalOID = pr.OrderID', 'left');
        $builder->where('pr.ResultID', $resultID);
        $builder->where('pr.DelDate', null);
        
        $result = $builder->get()->getRowArray();
        return $result ?: null;
    }

    /**
     * Soft delete result
     * 
     * @param int $resultID
     * @return bool
     */
    public function softDelete(int $resultID): bool {
        return $this->update($resultID, ['DelDate' => date('Y-m-d H:i:s')]);
    }
}
