<?php
defined('BASEPATH') or exit('No direct script access allowed');

class Fonnte_webhook extends CI_Controller
{
    public function status()
    {
        if (strtoupper($this->input->method()) !== 'POST') {
            return $this->respond(['status' => false, 'message' => 'Method not allowed'], 405);
        }

        $payload = json_decode($this->input->raw_input_stream, true);
        if (!is_array($payload)) {
            $payload = $this->input->post(NULL, true);
        }

        $messageId = isset($payload['id']) ? trim((string) $payload['id']) : '';
        $stateId = isset($payload['stateid']) ? trim((string) $payload['stateid']) : '';
        $status = isset($payload['status']) ? strtolower(trim((string) $payload['status'])) : '';
        $state = isset($payload['state']) ? strtolower(trim((string) $payload['state'])) : '';
        if ($state === '' && isset($payload['ack'])) {
            $state = strtolower(trim((string) $payload['ack']));
        }
        $device = isset($payload['device']) ? preg_replace('/\D+/', '', (string) $payload['device']) : '';

        // Sebagian perangkat Fonnte masih mengirim kode ACK numerik.
        $numericStates = ['0' => 'pending', '1' => 'sent', '2' => 'delivered', '3' => 'read'];
        if (isset($numericStates[$state])) {
            $state = $numericStates[$state];
        }

        if (($messageId === '' && $stateId === '') || ($status === '' && $state === '')) {
            return $this->respond(['status' => false, 'message' => 'Invalid payload'], 422);
        }

        $allowed = ['pending', 'waiting', 'processing', 'sent', 'delivered', 'read', 'invalid', 'expired', 'failed', 'url unreachable'];
        if (($status !== '' && !in_array($status, $allowed, true)) || ($state !== '' && !in_array($state, $allowed, true))) {
            return $this->respond(['status' => false, 'message' => 'Unknown status'], 422);
        }

        $schemas = [
            'report_pesan' => $this->ensureTrackingColumns('report_pesan'),
            'report' => $this->ensureTrackingColumns('report')
        ];
        $report = [];
        $reportTable = '';
        foreach ($schemas as $table => $trackingColumns) {
            if ($messageId !== '') $candidate = $this->db->get_where($table, ['id_kirim' => $messageId])->row_array();
            else $candidate = [];
            if (empty($candidate) && $stateId !== '' && $trackingColumns['stateid']) $candidate = $this->db->get_where($table, ['stateid' => $stateId])->row_array();
            if (!empty($candidate)) { $report = $candidate; $reportTable = $table; break; }
        }
        if (empty($report)) {
            return $this->respond(['status' => true, 'updated' => false]);
        }

        // ID pesan Fonnte adalah kunci utama callback. Field `device` pada
        // webhook adalah nomor device API dan pada beberapa akun berbeda dari
        // nomor WhatsApp yang tersambung. Jangan menolak callback yang ID-nya
        // valid hanya karena representasi nomor device berbeda.

        $trackingColumns = $schemas[$reportTable];
        $currentStatus = strtolower(trim((string) ($report['status'] ?? '')));
        $currentState = strtolower(trim((string) ($report['state'] ?? '')));
        $update = [];

        // Status pengiriman Fonnte dan state penerimaan WhatsApp adalah dua
        // informasi berbeda. Jangan timpa status "sent" dengan state delivery.
        if ($status !== '' && ($this->statusRank($status) >= $this->statusRank($currentStatus) || $this->isFailure($status))) {
            $update['status'] = $status;
        }
        if ($stateId !== '' && $trackingColumns['stateid']) {
            $update['stateid'] = $stateId;
        }
        if ($state !== '' && $trackingColumns['state'] && ($this->statusRank($state) >= $this->statusRank($currentState) || $this->isFailure($state))) {
            $update['state'] = $state;
        }

        $callbackTime = $this->callbackDateTime($payload);
        if ($callbackTime !== '' && $this->isMidnightDate($report['create_date'] ?? '')) {
            $update['create_date'] = $callbackTime;
        }

        if (!empty($update)) {
            $this->db->where('id', $report['id'])->update($reportTable, $update);
        }

		$audit = 'Fonnte status callback id='.$messageId.' stateid='.$stateId.' status='.$status.' state='.$state.' table='.$reportTable.' matched='.(string) $report['id'].' updated='.(!empty($update) ? '1' : '0');
		log_message('error', $audit);
		// Tetap masuk ke error log server ketika log_threshold CodeIgniter = 0.
		error_log($audit);

        return $this->respond(['status' => true, 'updated' => !empty($update)]);
    }

    /**
     * Deployment lama belum mempunyai kolom state/stateid. Tambahkan sekali
     * secara aman ketika callback pertama diterima agar laporan tidak blank.
     */
    private function ensureTrackingColumns($table)
    {
        $this->load->dbforge();

        if (!$this->hasTrackingColumn($table, 'state')) {
            $this->dbforge->add_column($table, [
                'state' => ['type' => 'VARCHAR', 'constraint' => 30, 'null' => true, 'after' => 'status']
            ]);
        }
        if (!$this->hasTrackingColumn($table, 'stateid')) {
            $this->dbforge->add_column($table, [
                'stateid' => ['type' => 'VARCHAR', 'constraint' => 100, 'null' => true, 'after' => 'state']
            ]);
        }

        if (!$this->db->field_exists('create_date', $table)) $this->dbforge->add_column($table, ['create_date'=>['type'=>'DATETIME','null'=>true,'after'=>'stateid']]);
        else {
            $dateColumn = $this->db->query("SHOW COLUMNS FROM `{$table}` LIKE 'create_date'")->row_array();
            if (!empty($dateColumn) && strtolower((string) $dateColumn['Type']) === 'date') $this->db->query("ALTER TABLE `{$table}` MODIFY `create_date` DATETIME NULL");
        }

        return [
            'state' => $this->hasTrackingColumn($table, 'state'),
            'stateid' => $this->hasTrackingColumn($table, 'stateid')
        ];
    }

    private function hasTrackingColumn($table, $column)
    {
        return $this->db->query("SHOW COLUMNS FROM `{$table}` LIKE ?", [$column])->num_rows() > 0;
    }

    private function statusRank($status)
    {
        $ranks = ['pending' => 10, 'waiting' => 20, 'processing' => 30, 'sent' => 40, 'delivered' => 50, 'read' => 60];
        return isset($ranks[$status]) ? $ranks[$status] : 0;
    }

    private function isFailure($status)
    {
        return in_array($status, ['invalid', 'expired', 'failed', 'url unreachable'], true);
    }

    private function callbackDateTime(array $payload)
    {
        foreach (['timestamp', 'time', 'date'] as $key) {
            if (!isset($payload[$key]) || $payload[$key] === '') {
                continue;
            }
            $value = $payload[$key];
            if (is_numeric($value)) {
                $timestamp = (int) $value;
                if ($timestamp > 9999999999) {
                    $timestamp = (int) floor($timestamp / 1000);
                }
            } else {
                $timestamp = strtotime((string) $value);
            }
            if ($timestamp !== false && $timestamp > 0) {
                return date('Y-m-d H:i:s', $timestamp);
            }
        }
        return '';
    }

    private function isMidnightDate($value)
    {
        return $value === '' || preg_match('/\s00:00:00$/', (string) $value) === 1;
    }

    private function respond(array $data, $httpStatus = 200)
    {
        return $this->output
            ->set_status_header($httpStatus)
            ->set_content_type('application/json', 'utf-8')
            ->set_output(json_encode($data, JSON_UNESCAPED_UNICODE));
    }
}
