<?php
namespace App\Models\OrderTest;

use App\Models\BaseModel;

class OrderTestModel extends BaseModel {
    protected $table = 'ordertest';
    protected $primaryKey = 'InternalOID';
    protected $useAutoIncrement = true;
    protected $allowedFields = [
        'InternalOID',
        'OrderID',
        'PlacerID',
        'InternalPID',
        'SiteID',
        'PVADTID',
        'ReqApp',
        'Priority',
        'TrnDate',
        'EffDate',
        'CreateDate',
        'EndDate',
        'ArchiveDate',
        'DelDate'
    ];

    public function generateOrderID(string $siteCode = '00'): string {
        $date = new \DateTime();
        $year = $date->format('y');
        $month = $date->format('m');
        $day = $date->format('d');

        $counter = $this->db->table('counter')
            ->where('CounterName', 'ORDER')
            ->get()
            ->getRow();

        if (!$counter) {
            $this->db->table('counter')->insert([
                'CounterName' => 'ORDER',
                'CounterValue' => 1
            ]);
            $seq = 1;
        } else {
            $seq = $counter->CounterValue + 1;
            $this->db->table('counter')
                ->where('CounterName', 'ORDER')
                ->update(['CounterValue' => $seq]);
        }

        $seqStr = str_pad($seq, 5, '0', STR_PAD_LEFT);
        return $siteCode . $year . $month . $day . $seqStr;
    }

    public function generateSpecimenID(string $orderID, int $seq): string {
        return $orderID . '-S' . str_pad($seq, 2, '0', STR_PAD_LEFT);
    }

    public function createOrder(array $data): string {
        $this->db->transStart();

        try {
            $orderID = !empty($data['OrderID']) ? $data['OrderID'] : $this->generateOrderID($data['SiteCode'] ?? '00');

            $orderData = [
                'OrderID' => $orderID,
                'PlacerID' => $data['PlacerID'] ?? null,
                'InternalPID' => $data['InternalPID'],
                'SiteID' => $data['SiteID'] ?? '1',
                'PVADTID' => $data['PatVisitID'] ?? $data['PVADTID'] ?? 0,
                'ReqApp' => $data['ReqApp'] ?? null,
                'Priority' => $data['Priority'] ?? 'R',
                'TrnDate' => $data['OrderDateTime'] ?? $data['TrnDate'] ?? date('Y-m-d H:i:s'),
                'EffDate' => $data['EffDate'] ?? date('Y-m-d H:i:s'),
                'CreateDate' => date('Y-m-d H:i:s')
            ];

            $internalOID = $this->insert($orderData);

            if (!$internalOID) {
                throw new \Exception('Failed to create order');
            }

            // Handle Order Comments
            if (!empty($data['Comment'])) {
                $this->db->table('ordercom')->insert([
                    'InternalOID' => $internalOID,
                    'Comment' => $data['Comment'],
                    'CreateDate' => date('Y-m-d H:i:s')
                ]);
            }
            
            // Process Tests Expansion
            $testToOrder = [];
            $specimenConDefMap = []; // Map ConDefID to specimen info
            
            if (isset($data['Tests']) && is_array($data['Tests'])) {
                $testModel = new \App\Models\Test\TestDefSiteModel();
                $grpModel = new \App\Models\Test\TestDefGrpModel();
                $calModel = new \App\Models\Test\TestDefCalModel();
                $testMapDetailModel = new \App\Models\Test\TestMapDetailModel();
                $containerDefModel = new \App\Models\Specimen\ContainerDefModel();
                
                foreach ($data['Tests'] as $test) {
                    $testSiteID = $test['TestSiteID'] ?? $test['TestID'] ?? null;
                    if ($testSiteID) {
                        $this->expandTest($testSiteID, $testToOrder, $testModel, $grpModel, $calModel);
                    }
                }
                
                // Group tests by container requirement
                $testsByContainer = [];
                foreach ($testToOrder as $tid => $tinfo) {
                    // Find container requirement for this test
                    $containerReq = $this->getContainerRequirement($tid, $testMapDetailModel, $containerDefModel);
                    $conDefID = $containerReq['ConDefID'] ?? null;
                    
                    if (!isset($testsByContainer[$conDefID])) {
                        $testsByContainer[$conDefID] = [
                            'tests' => [],
                            'containerInfo' => $containerReq
                        ];
                    }
                    $testsByContainer[$conDefID]['tests'][$tid] = $tinfo;
                }
                
                // Create specimens for each unique container requirement
                $specimenSeq = 1;
                foreach ($testsByContainer as $conDefID => $containerData) {
                    $specimenID = $this->generateSpecimenID($orderID, $specimenSeq++);
                    
                    $specimenData = [
                        'SID' => $specimenID,
                        'SiteID' => $data['SiteID'] ?? '1',
                        'OrderID' => $internalOID,
                        'ConDefID' => $conDefID,
                        'Qty' => 1,
                        'Unit' => 'tube',
                        'GenerateBy' => 'ORDER',
                        'CreateDate' => date('Y-m-d H:i:s')
                    ];
                    
                    $this->db->table('specimen')->insert($specimenData);
                    $internalSID = $this->db->insertID();
                    
                    // Create specimen status
                    $this->db->table('specimenstatus')->insert([
                        'SID' => $specimenID,
                        'OrderID' => $internalOID,
                        'SpcStatus' => 'PENDING',
                        'CreateDate' => date('Y-m-d H:i:s')
                    ]);
                    
                    // Store mapping for patres creation
                    foreach ($containerData['tests'] as $tid => $tinfo) {
                        $specimenConDefMap[$tid] = [
                            'InternalSID' => $internalSID,
                            'SID' => $specimenID,
                            'ConDefID' => $conDefID
                        ];
                    }
                }
                
                // Insert unique tests into patres with specimen links
                if (!empty($testToOrder)) {
                    $resModel = new \App\Models\PatResultModel();
                    $patientModel = new \App\Models\Patient\PatientModel();
                    $patient = $patientModel->find((int) $data['InternalPID']);
                    $age = null;
                    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;
                        }
                    }

                    $ruleEngine = new \App\Services\RuleEngineService();

                    foreach ($testToOrder as $tid => $tinfo) {
                        $specimenInfo = $specimenConDefMap[$tid] ?? null;
                        
                        $patResData = [
                            'OrderID' => $internalOID,
                            'TestSiteID' => $tid,
                            'TestSiteCode' => $tinfo['TestSiteCode'],
                            'SID' => $orderID, 
                            'SampleID' => $orderID,
                            'ResultDateTime' => $orderData['TrnDate'],
                            'CreateDate' => date('Y-m-d H:i:s')
                        ];
                        
                        if ($specimenInfo) {
                            $patResData['InternalSID'] = $specimenInfo['InternalSID'];
                        }
                        
                        $resModel->insert($patResData);

                        // Fire test_created rules after the test row is persisted
                        try {
                            $ruleEngine->run('test_created', [
                                'order' => [
                                    'InternalOID' => $internalOID,
                                    'Priority' => $orderData['Priority'] ?? 'R',
                                    'TestSiteID' => $tid,
                                ],
                                'patient' => [
                                    'Sex' => is_array($patient) ? ($patient['Sex'] ?? null) : null,
                                ],
                                'age' => $age,
                                'testSiteID' => $tid,
                            ]);
                        } catch (\Throwable $e) {
                            log_message('error', 'OrderTestModel::createOrder rule engine error: ' . $e->getMessage());
                        }
                    }
                }
            }
            
            $this->db->transComplete();
            
            if ($this->db->transStatus() === false) {
                throw new \Exception('Transaction failed');
            }
            
            return $orderID;
        } catch (\Exception $e) {
            $this->db->transRollback();
            throw $e;
        }
    }

    private function getContainerRequirement($testSiteID, $testMapDetailModel, $containerDefModel): array {
        // Try to find container requirement from test mapping
        $containerDef = $this->db->table('testmapdetail tmd')
            ->select('tmd.ConDefID, cd.ConCode, cd.ConName')
            ->join('containerdef cd', 'cd.ConDefID = tmd.ConDefID', 'left')
            ->where('tmd.ClientTestCode', function($builder) use ($testSiteID) {
                return $builder->select('TestSiteCode')
                    ->from('testdefsite')
                    ->where('TestSiteID', $testSiteID);
            })
            ->where('tmd.EndDate IS NULL')
            ->get()
            ->getRowArray();
        
        if ($containerDef) {
            return [
                'ConDefID' => $containerDef['ConDefID'],
                'ConCode' => $containerDef['ConCode'],
                'ConName' => $containerDef['ConName']
            ];
        }
        
        return [
            'ConDefID' => null,
            'ConCode' => 'DEFAULT',
            'ConName' => 'Default Container'
        ];
    }

    private function expandTest($testSiteID, &$testToOrder, $testModel, $grpModel, $calModel) {
        if (isset($testToOrder[$testSiteID])) return;

        $testInfo = $testModel->find($testSiteID);
        if (!$testInfo) return;

        $testToOrder[$testSiteID] = [
            'TestSiteCode' => $testInfo['TestSiteCode'],
            'TestType' => $testInfo['TestType']
        ];

        // Handle Group Expansion
        if ($testInfo['TestType'] === 'GROUP') {
            $members = $grpModel->where('TestSiteID', $testSiteID)->findAll();
            foreach ($members as $m) {
                $this->expandTest($m['Member'], $testToOrder, $testModel, $grpModel, $calModel);
            }
        }

        // Handle Calculated Test Dependencies
        if ($testInfo['TestType'] === 'CALC') {
            $members = $grpModel->getGroupMembers($testSiteID);
            foreach ($members as $member) {
                $memberID = $member['TestSiteID'] ?? null;
                if ($memberID) {
                    $this->expandTest($memberID, $testToOrder, $testModel, $grpModel, $calModel);
                }
            }
        }
    }

    public function getOrder(string $orderID): ?array {
        return $this->select('*')
            ->where('OrderID', $orderID)
            ->where('DelDate', null)
            ->get()
            ->getRowArray();
    }

    public function getOrdersByPatient(int $internalPID): array {
        return $this->select('*')
            ->where('InternalPID', $internalPID)
            ->where('DelDate', null)
            ->orderBy('TrnDate', 'DESC')
            ->get()
            ->getResultArray();
    }

    public function updateStatus(string $orderID, string $status): bool {
        $order = $this->getOrder($orderID);
        if (!$order) return false;

        return (bool)$this->db->table('orderstatus')->insert([
            'InternalOID' => $order['InternalOID'],
            'OrderStatus' => $status,
            'CreateDate' => date('Y-m-d H:i:s')
        ]);
    }

    public function softDelete(string $orderID): bool {
        return $this->where('OrderID', $orderID)->update(null, ['DelDate' => date('Y-m-d H:i:s')]);
    }
}
