<?php
namespace App\Models\Patient;

use App\Models\BaseModel;

use App\Models\Patient\PatAttModel;
use App\Models\Patient\PatComModel;
use App\Models\Patient\PatIdtModel;

class PatientModel extends BaseModel {
  protected $table      = 'patient';
  protected $primaryKey = 'InternalPID';
  protected $allowedFields = ['PatientID', 'AlternatePID', 'Prefix', 'NameFirst', 'NameMiddle', 'NameMaiden', 'NameLast', 'Suffix', 'NameAlias', 'Gender', 'Birthdate', 'PlaceOfBirth', 'Street_1', 'Street_2', 'Street_3', 
    'City', 'Province', 'ZIP', 'EmailAddress1', 'EmailAddress2', 'Phone', 'MobilePhone', 'Custodian', 'AccountNumber', 'Country', 'Race', 'MaritalStatus', 'Religion', 'Ethnic', 'Citizenship', 
    'DeathIndicator', 'TimeOfDeath', 'LinkTo', 'CreateDate', 'DelDate' ]; 
    
  protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';
  protected $useSoftDeletes = true;
  protected $deletedField  = 'DelDate';

  public function getPatients($filters = []) {
    $qname = "CONCAT_WS(' ', IFNULL(Prefix,''), IFNULL(NameFirst,''), IFNULL(NameMiddle,''), IFNULL(NameLast,''), IFNULL(NameMaiden,''), IFNULL(Suffix,''))";

    $this->select("InternalPID, PatientID, $qname as FullName, vs.VDesc as Gender, Birthdate, EmailAddress1 as Email, MobilePhone");
    $this->join('valueset vs', 'vs.vid = Gender', 'left');

    if (!empty($filters['Name'])) {
      $this->like($qname, $filters['Name'], 'both');
    }

    if (!empty($filters['InternalPID'])) {
      $this->where('InternalPID', $filters['InternalPID']);
    }

    if (!empty($filters['PatientID'])) {
      $this->like('PatientID', $filters['PatientID'], 'both');
    }

    if (!empty($filters['Birthdate'])) {
      $this->where('Birthdate', $filters['Birthdate']);
    }

    return $this->findAll();
  }

  public function getPatient($InternalPID) {
    $rows = $this->select("
          patient.*,
          country.VDesc as Country,
          country.VID as CountryVID,
          race.VDesc as Race,
          race.VID as RaceVID,
          religion.VDesc as Religion,
          religion.VID as ReligionVID,
          ethnic.VDesc as Ethnic,
          ethnic.VID as EthnicVID,
          gender.VDesc as Gender,
          gender.VID as GenderVID,
          deathindicator.VDesc as DeathIndicator,
          deathindicator.VID as DeathIndicatorVID,
          maritalstatus.VDesc as MaritalStatus,
          maritalstatus.VID as MaritalStatusVID,
          patcom.Comment as Comment,
          patidt.IdentifierType,
          patidt.Identifier,
          patatt.Address,
          areageo1.AreaGeoID as ProvinceID,
          areageo1.AreaName as Province,
          areageo2.AreaGeoID as CityID,
          areageo2.AreaName as City

      ")
      ->join('valueset country', 'country.VID = patient.Country', 'left')
      ->join('valueset race', 'race.VID = patient.Race', 'left')
      ->join('valueset religion', 'religion.VID = patient.Religion', 'left')
      ->join('valueset ethnic', 'ethnic.VID = patient.Ethnic', 'left')
      ->join('valueset gender', 'gender.VID = patient.Gender', 'left')
      ->join('valueset deathindicator', 'deathindicator.VID = patient.DeathIndicator', 'left')
      ->join('valueset maritalstatus', 'maritalstatus.VID = patient.MaritalStatus', 'left')
      ->join('patcom', 'patcom.InternalPID = patient.InternalPID', 'left')
      ->join('patidt', 'patidt.InternalPID = patient.InternalPID', 'left')
      ->join('patatt', 'patatt.InternalPID = patient.InternalPID and patatt.DelDate is null', 'left')
      ->join('areageo areageo1', 'areageo1.AreaGeoID = patient.Province', 'left')
      ->join('areageo areageo2', 'areageo2.AreaGeoID = patient.City', 'left')
      ->where('patient.InternalPID', (int) $InternalPID)
      ->findAll();

    if (empty($rows)) { return null; }

    $patient = $rows[0];
    
    if (method_exists($this, 'transformPatientData')) { $patient = $this->transformPatientData($patient); }
    unset($patient['Address']);
    unset($patient['IdentifierType']);
    unset($patient['Identifier']);
    unset($patient['Comment']);

    // Default nested structures
    $patient['PatIdt'] = null;
    $patient['PatAtt'] = [];
    
    foreach ($rows as $row) {
      if ($row['IdentifierType'] && $row['Identifier'] && !$patient['PatIdt']) {
        $patient['PatIdt'] = [
          'IdentifierType' => $row['IdentifierType'],
          'Identifier'     => $row['Identifier'],
        ];
      }

      if ($row['Address']) {
        $patient['PatAtt'][] = ['Address' => $row['Address']];
      }
    }

    if (empty($patient['PatIdt'])) { $patient['PatIdt'] = null; }
    if (empty($patient['PatAtt'])) { $patient['PatAtt'] = null; }

    return $patient;
  }

  public function createPatient($input) {
    $db = \Config\Database::connect();

    $modelPatAtt = new PatAttModel();
    $modelPatCom = new PatComModel();
    $modelPatIdt = new PatIdtModel();
  
    $input['LinkTo'] = empty($input['LinkTo']) ? null : $input['LinkTo'];

    $db->transBegin();

    try {

      // Insert Data ke Tabel Patient, get ID dan cek apa ada error
      $this->insert($input);
      $newInternalPID = $this->getInsertID();
      $this->checkDbError($db, 'Insert patient');

      // Insert Data ke Tabel PatIdt
      if (!empty($input['PatIdt'])) {
        $modelPatIdt->createPatIdt($input['PatIdt'], $newInternalPID);
        $this->checkDbError($db, 'Insert PatIdt');
      }

      // Insert Data ke Tabel PatCom
      if (!empty($input['PatCom'])) {
        $modelPatCom->createPatCom($input['PatCom'], $newInternalPID);
        $this->checkDbError($db, 'Insert PatCom');
      }

      // Insert Data ke Tabel PatAtt
      if (!empty($input['PatAtt'])) {
        $modelPatAtt->createPatAtt($input['PatAtt'], $newInternalPID);
        $this->checkDbError($db, 'Insert PatAtt');
      }

      $db->transCommit();

      return $newInternalPID;

    } catch (\Exception $e) {
        $db->transRollback();
        throw $e; 
    }
  }

  public function updatePatient($input) {
    $db = \Config\Database::connect();
    $modelPatIdt = new PatIdtModel();
    $modelPatCom = new PatComModel();
    $modelPatAtt = new PatAttModel();

    $input['LinkTo'] = empty($input['LinkTo']) ? null : $input['LinkTo'];

    $db->transBegin();

    try {

      // Update Patient
      $InternalPID = $input['InternalPID'];
      $this->where('InternalPID',$InternalPID)->set($input)->update();
      $this->checkDbError($db, 'Update patient');

      // Update Patidt
      if (!empty($input['PatIdt'])) {
        $modelPatIdt->updatePatIdt($input['PatIdt'], $InternalPID);
        $this->checkDbError($db, 'Update patIdt');
      } else {
        $modelPatIdt->deletePatIdt($InternalPID);
        $this->checkDbError($db, 'Update patidt');
      }

      // Update Patcom
      if (!empty($input['PatCom'])) {
        $modelPatCom->updatePatCom($input['PatCom'], $InternalPID);
        $this->checkDbError($db, 'Update PatCom');
      } else {
        $modelPatCom->deletePatCom($InternalPID);
        $this->checkDbError($db, 'Update patcom');
      }

      // Update Patatt
      if (!empty($input['PatAtt'])) {
        $modelPatAtt->updatePatAtt($input['PatAtt'], $InternalPID);
        $this->checkDbError($db, 'Update PatAtt');
      } else {
        $modelPatAtt->deletePatAtt($InternalPID);
        $this->checkDbError($db, 'Update/Delete patatt');
      }

      $db->transCommit();

      return $InternalPID;

    } catch (\Exception $e) {
        $db->transRollback();
        throw $e; 
    }
  }

  private function transformPatientData(array $patient): array {
    $patient["Age"] = $this->calculateAgeFromBirthdate($patient["Birthdate"], $patient["TimeOfDeath"]);
    $patient["TimeOfDeath"] = $this->formattedDate($patient["TimeOfDeath"]);
    $patient["CreateDate"] = $this->formattedDate($patient["CreateDate"]);
    $patient["BirthdateConversion"] = $this->formatedDateForDisplay($patient["Birthdate"]);
    $patient["LinkTo"]    = $this->getLinkedPatients($patient['LinkTo']);
    $patient["Custodian"] = $this->getCustodian($patient['Custodian']);
    $patient['PatCom'] = $patient['Comment'];

    return $patient;
  }

  private function getLinkedPatients(?string $linkTo): ?array {
    if (empty($linkTo)) { return null; }

    $ids = array_filter(explode(',', $linkTo));

    return $this->db->table('patient')
      ->select('InternalPID, PatientID')
      ->whereIn('InternalPID', $ids)
      ->get()
      ->getResultArray() ?: null;
  }

  private function getCustodian($custodianId): ?array {
    if (empty($custodianId)) {
      return null;
    }

    return $this->select('InternalPID, PatientID')
      ->where('InternalPID', (int) $custodianId)
      ->first() ?: null;
  }

  // Conversion to (Years Months Days) - For Age
  private function calculateAgeFromBirthdate($birthdate, $deathdate) {
    $dob = new \DateTime($birthdate);

    // Cek DeathTime
    if ($deathdate == null) {
      $today = new \DateTime();
    } else {
      $deathdate = new \DateTime($deathdate);
      $today = $deathdate;
    }

    $diff = $today->diff($dob);
    $formattedAge = "";
    if ($diff->y > 0){
      $formattedAge .= "{$diff->y} Years ";
    }
    if ($diff->m > 0){
      $formattedAge .= "{$diff->m} Months ";
    }
    if ($diff->d > 0){
      $formattedAge .= "{$diff->d} Days";
    }

    return $formattedAge;
  }

  // Conversion Time to Format Y-m-d\TH:i:s\Z
  private function formattedDate(?string $dateString): ?string {
    try {
        if (empty($dateString)) {return null;}

        $dt = new \DateTime($dateString, new \DateTimeZone("UTC"));
        return $dt->format('Y-m-d\TH:i:s\Z'); // ISO 8601 UTC
    } catch (\Exception $e) {
        return null; 
    }
  }

  // Conversion Time to Format j M Y - For BirthdateConversion
  private function formatedDateForDisplay($dateString) {    
    $date = \DateTime::createFromFormat('Y-m-d H:i', $dateString);

    if (!$date) {
      $timestamp = strtotime($dateString);
      if ($timestamp) {
        return date('j M Y', $timestamp);
      }
      return null;
    }

    return $date->format('j M Y');
  }

  // Check Error and Send Spesific Messages
  private function checkDbError($db, string $context) {
      $error = $db->error();
      if (!empty($error['code'])) {
          throw new \Exception(
              "{$context} failed: {$error['code']} - {$error['message']}"
          );
      }
  }

  // Preventif 0000-00-00
  private function isValidDateTime($datetime) {
    if (empty($datetime) || $datetime=="") {return null; }
    try {
      // Kalau input hanya Y-m-d (tanpa jam)
      if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $datetime)) {
          $dt = \DateTime::createFromFormat('Y-m-d', $datetime);
          return $dt ? $dt->format('Y-m-d') : null; // hanya tanggal
      }

      // Selain itu (ISO 8601 atau datetime lain), format ke Y-m-d H:i:s
      $dt = new \DateTime($datetime);
      return $dt->format('Y-m-d H:i:s');

    } catch (\Exception $e) {
      return null;
    }
  }

}
