<?php
namespace EasyChat\Model;

class SmsDevice extends Base {

    private const INBOUND_SIM_MODES = ['default_sim', 'all_sims'];
    private const INBOUND_HEALTH_STATUSES = ['ready', 'warning', 'error'];
    private const INBOUND_ERROR_CODES = [
        '',
        'local_persistence_failed',
        'local_decryption_failed',
        'local_outbox_capacity_exceeded',
        'local_outbox_retention_pruned',
        'inbound_filter_unavailable',
        'inbound_filter_stale',
        'inbound_filter_invalid',
        'sms_inbound_permission_revoked',
        'inbound_sim_filtered',
        'inbound_sim_unknown',
        'inbound_default_sim_unavailable',
        'upload_pending',
        'invalid_payload',
        'license_blocked',
        'session_changed',
    ];

    public function upsertFromAppSession(int $session_id, int $manager_id, array $data = []): array {
        $app_device_id = max(0, (int)($data['app_device_id'] ?? 0));
        $device_label = trim((string)($data['device_label'] ?? 'Android'));
        $app_version = trim((string)($data['app_version'] ?? ''));
        $android_permission = !empty($data['android_permission']) ? 'granted' : 'unknown';
        $send_available = array_key_exists('send_available', $data) ? !empty($data['send_available']) : true;
        $ready_status_sql = $send_available ? "'ready'" : "'device_unavailable'";
        $inbound_android_permission = !empty($data['inbound_android_permission']) ? 'granted' : 'denied';
        $has_reliability = array_key_exists('reliability_status', $data);
        $reliability_status = in_array((string)($data['reliability_status'] ?? ''), ['ready', 'warning', 'blocking'], true)
            ? (string)$data['reliability_status']
            : 'unknown';
        $android_sdk = max(0, min(999, (int)($data['android_sdk'] ?? 0)));
        $manufacturer = mb_substr(trim((string)($data['manufacturer'] ?? '')), 0, 80, 'UTF-8');
        $device_model = mb_substr(trim((string)($data['model'] ?? '')), 0, 80, 'UTF-8');
        $has_default_subscription = array_key_exists('default_sms_subscription_id', $data);
        $default_subscription_id = max(-1, min(2147483647, (int)($data['default_sms_subscription_id'] ?? -1)));
        $has_inbound_sim_mode = array_key_exists('inbound_sim_mode', $data);
        $inbound_sim_mode = $this->allowlistedValue($data['inbound_sim_mode'] ?? '', self::INBOUND_SIM_MODES, 'default_sim');
        $has_inbound_health_status = array_key_exists('inbound_health_status', $data);
        $inbound_health_status = $this->allowlistedValue($data['inbound_health_status'] ?? '', self::INBOUND_HEALTH_STATUSES, 'unknown');
        $has_inbound_error_code = array_key_exists('inbound_last_error_code', $data);
        $inbound_last_error_code = $this->allowlistedValue($data['inbound_last_error_code'] ?? '', self::INBOUND_ERROR_CODES, '');
        $client_error_at_ms = is_scalar($data['inbound_last_error_at_ms'] ?? null)
            ? trim((string)$data['inbound_last_error_at_ms'])
            : '';
        $inbound_last_error_at_sql = $inbound_last_error_code !== ''
            && preg_match('/^[1-9][0-9]{0,18}$/D', $client_error_at_ms) === 1
                ? 'NOW()'
                : 'NULL';
        $has_inbound_pending_count = array_key_exists('inbound_pending_count', $data);
        $pending_count_value = is_scalar($data['inbound_pending_count'] ?? null) ? (int)$data['inbound_pending_count'] : 0;
        $inbound_pending_count = max(0, min(10000, $pending_count_value));
        $checked_at_sql = $has_reliability ? 'NOW()' : 'NULL';
        $this->db->query(
            "INSERT INTO `{$this->prefix}sms_devices`
             SET `app_session_id` = " . (int)$session_id . ",
                 `app_device_id` = " . ($app_device_id > 0 ? (string)$app_device_id : 'NULL') . ",
                 `manager_id` = " . (int)$manager_id . ",
                 `device_label` = '" . $this->db->escape(mb_substr($device_label, 0, 120, 'UTF-8')) . "',
                 `app_version` = '" . $this->db->escape(mb_substr($app_version, 0, 32, 'UTF-8')) . "',
                 `android_sdk` = {$android_sdk},
                 `device_manufacturer` = '" . $this->db->escape($manufacturer) . "',
                 `device_model` = '" . $this->db->escape($device_model) . "',
                 `reliability_status` = '" . $this->db->escape($reliability_status) . "',
                 `battery_unrestricted` = " . (!empty($data['battery_unrestricted']) ? 1 : 0) . ",
                 `background_restricted` = " . (!empty($data['background_restricted']) ? 1 : 0) . ",
                 `notifications_allowed` = " . (!empty($data['notifications_allowed']) ? 1 : 0) . ",
                 `fcm_synced` = " . (!empty($data['fcm_synced']) ? 1 : 0) . ",
                 `default_sms_subscription_id` = {$default_subscription_id},
                 `reliability_checked_at` = {$checked_at_sql},
                 `admin_requested` = 0,
                 `operator_consent` = 'none',
                 `android_permission` = '" . $this->db->escape($android_permission) . "',
                 `status` = 'not_requested',
                 `inbound_admin_requested` = 0,
                 `inbound_operator_consent` = 'none',
                 `inbound_android_permission` = '" . $this->db->escape($inbound_android_permission) . "',
                 `inbound_status` = 'not_requested',
                 `inbound_sim_mode` = '" . $this->db->escape($inbound_sim_mode) . "',
                 `inbound_health_status` = '" . $this->db->escape($inbound_health_status) . "',
                 `inbound_last_error_code` = '" . $this->db->escape($inbound_last_error_code) . "',
                 `inbound_last_error_at` = {$inbound_last_error_at_sql},
                 `inbound_pending_count` = {$inbound_pending_count},
                 `last_seen_at` = NOW(),
                 `date_added` = NOW(),
                 `date_modified` = NOW()
             ON DUPLICATE KEY UPDATE
                 `app_session_id` = VALUES(`app_session_id`),
                 `app_device_id` = COALESCE(VALUES(`app_device_id`), `app_device_id`),
                 `manager_id` = VALUES(`manager_id`),
                 `device_label` = VALUES(`device_label`),
                 `app_version` = VALUES(`app_version`),
                 `android_sdk` = IF(VALUES(`reliability_checked_at`) IS NULL, `android_sdk`, VALUES(`android_sdk`)),
                 `device_manufacturer` = IF(VALUES(`reliability_checked_at`) IS NULL, `device_manufacturer`, VALUES(`device_manufacturer`)),
                 `device_model` = IF(VALUES(`reliability_checked_at`) IS NULL, `device_model`, VALUES(`device_model`)),
                 `reliability_status` = IF(VALUES(`reliability_checked_at`) IS NULL, `reliability_status`, VALUES(`reliability_status`)),
                 `battery_unrestricted` = IF(VALUES(`reliability_checked_at`) IS NULL, `battery_unrestricted`, VALUES(`battery_unrestricted`)),
                 `background_restricted` = IF(VALUES(`reliability_checked_at`) IS NULL, `background_restricted`, VALUES(`background_restricted`)),
                 `notifications_allowed` = IF(VALUES(`reliability_checked_at`) IS NULL, `notifications_allowed`, VALUES(`notifications_allowed`)),
                 `fcm_synced` = IF(VALUES(`reliability_checked_at`) IS NULL, `fcm_synced`, VALUES(`fcm_synced`)),
                 `default_sms_subscription_id` = IF(" . ($has_default_subscription ? 1 : 0) . " = 1, VALUES(`default_sms_subscription_id`), `default_sms_subscription_id`),
                 `reliability_checked_at` = IFNULL(VALUES(`reliability_checked_at`), `reliability_checked_at`),
                 `android_permission` = VALUES(`android_permission`),
                 `status` = IF(`operator_consent` = 'granted', IF(VALUES(`android_permission`) = 'granted', {$ready_status_sql}, 'permission_missing'), `status`),
                 `inbound_android_permission` = VALUES(`inbound_android_permission`),
                 `inbound_status` = IF(`inbound_operator_consent` = 'granted', IF(VALUES(`inbound_android_permission`) = 'granted', 'ready', 'permission_missing'), `inbound_status`),
                 `last_seen_at` = NOW(),
                 `date_modified` = NOW(),
                 `inbound_sim_mode` = IF(" . ($has_inbound_sim_mode ? 1 : 0) . " = 1, VALUES(`inbound_sim_mode`), `inbound_sim_mode`),
                 `inbound_health_status` = IF(" . ($has_inbound_health_status ? 1 : 0) . " = 1, VALUES(`inbound_health_status`), `inbound_health_status`),
                 `inbound_last_error_code` = IF(" . ($has_inbound_error_code ? 1 : 0) . " = 1, VALUES(`inbound_last_error_code`), `inbound_last_error_code`),
                 `inbound_last_error_at` = IF(" . ($has_inbound_error_code ? 1 : 0) . " = 1, VALUES(`inbound_last_error_at`), `inbound_last_error_at`),
                 `inbound_pending_count` = IF(" . ($has_inbound_pending_count ? 1 : 0) . " = 1, VALUES(`inbound_pending_count`), `inbound_pending_count`)"
        );
        return $app_device_id > 0
            ? $this->getByAppDeviceId($app_device_id)
            : $this->getByAppSessionId($session_id);
    }

    private function allowlistedValue($value, array $allowed, string $fallback): string {
        $value = is_scalar($value) ? strtolower(trim((string)$value)) : '';
        return in_array($value, $allowed, true) ? $value : $fallback;
    }

    public function getByAppSessionId(int $session_id): array {
        $query = $this->db->query("SELECT * FROM `{$this->prefix}sms_devices` WHERE `app_session_id` = " . (int)$session_id . " LIMIT 1");
        return $query->row ?: [];
    }

    public function getByAppDeviceId(int $app_device_id): array {
        $query = $this->db->query("SELECT * FROM `{$this->prefix}sms_devices` WHERE `app_device_id` = " . (int)$app_device_id . " LIMIT 1");
        return $query->row ?: [];
    }

    public function listForManager(int $manager_id): array {
        $query = $this->db->query(
            "SELECT d.*, s.fcm_token, s.last_push_at, s.last_push_error, s.date_added AS session_date_added
             FROM `{$this->prefix}sms_devices` d
             INNER JOIN `{$this->prefix}app_session` s ON s.session_id = d.app_session_id
             LEFT JOIN `{$this->prefix}app_devices` ad ON ad.device_id = d.app_device_id
             WHERE d.manager_id = " . (int)$manager_id . "
               AND (d.app_device_id IS NULL OR ad.status = 'active')
               AND s.revoked_at IS NULL
             ORDER BY d.last_seen_at DESC, d.device_id DESC"
        );
        return $query->rows;
    }

    public function getById(int $device_id): array {
        $query = $this->db->query("SELECT * FROM `{$this->prefix}sms_devices` WHERE `device_id` = " . (int)$device_id . " LIMIT 1");
        return $query->row ?: [];
    }

    public function listReady(): array {
        $query = $this->db->query(
            "SELECT d.*, s.fcm_token
             FROM `{$this->prefix}sms_devices` d
             INNER JOIN `{$this->prefix}app_session` s ON s.session_id = d.app_session_id
             LEFT JOIN `{$this->prefix}app_devices` ad ON ad.device_id = d.app_device_id
             WHERE d.status = 'ready'
               AND d.admin_requested = 1
               AND d.operator_consent = 'granted'
               AND d.android_permission = 'granted'
               AND (d.reliability_status = 'unknown' OR (d.background_restricted = 0 AND d.fcm_synced = 1))
               AND s.fcm_token IS NOT NULL
               AND s.fcm_token != ''
               AND s.revoked_at IS NULL
               AND (s.refresh_token_hash IS NULL OR s.refresh_expires_at > NOW())
               AND (d.app_device_id IS NULL OR ad.status = 'active')
               AND d.last_seen_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
             ORDER BY (d.reliability_status = 'ready') DESC,
                      d.fcm_synced DESC, d.background_restricted ASC,
                      d.last_seen_at DESC, d.device_id DESC"
        );
        return $query->rows;
    }

     
    public function listInboundReadySessionIds(): array {
        $query = $this->db->query(
            "SELECT DISTINCT d.`app_session_id`
             FROM `{$this->prefix}sms_devices` d
             INNER JOIN `{$this->prefix}app_session` s ON s.`session_id` = d.`app_session_id`
             LEFT JOIN `{$this->prefix}app_devices` ad ON ad.`device_id` = d.`app_device_id`
             WHERE d.`inbound_status` = 'ready'
               AND d.`inbound_admin_requested` = 1
               AND d.`inbound_operator_consent` = 'granted'
               AND d.`inbound_android_permission` = 'granted'
               AND s.`fcm_token` IS NOT NULL
               AND s.`fcm_token` != ''
               AND s.`revoked_at` IS NULL
               AND (s.`refresh_token_hash` IS NULL OR s.`refresh_expires_at` > NOW())
               AND (d.`app_device_id` IS NULL OR ad.`status` = 'active')"
        );
        return array_values(array_map(static function (array $row): int {
            return (int)($row['app_session_id'] ?? 0);
        }, $query->rows ?: []));
    }

    public function canSend(array $device): bool {
        return !empty($device)
            && (int)($device['admin_requested'] ?? 0) === 1
            && (string)($device['operator_consent'] ?? '') === 'granted'
            && (string)($device['android_permission'] ?? '') === 'granted'
            && (string)($device['status'] ?? '') === 'ready'
            && ((string)($device['reliability_status'] ?? 'unknown') === 'unknown'
                || (empty($device['background_restricted']) && !empty($device['fcm_synced'])));
    }

    public function requestPermission(int $device_id): void {
        $this->db->query(
            "UPDATE `{$this->prefix}sms_devices`
             SET `admin_requested` = 1,
                 `operator_consent` = 'pending',
                 `status` = 'pending_operator',
                 `requested_at` = NOW(),
                 `date_modified` = NOW()
             WHERE `device_id` = " . (int)$device_id
        );
    }

    public function revokePermission(int $device_id): void {
        $this->db->query(
            "UPDATE `{$this->prefix}sms_devices`
             SET `admin_requested` = 0,
                 `operator_consent` = 'none',
                 `status` = 'disabled_by_admin',
                 `requested_at` = NULL,
                 `responded_at` = NULL,
                 `date_modified` = NOW()
             WHERE `device_id` = " . (int)$device_id
        );
    }

    public function saveOperatorConsent(int $session_id, string $consent, bool $android_permission, bool $send_available = true): array {
        $consent = in_array($consent, ['granted', 'denied'], true) ? $consent : 'denied';
        $permission = $android_permission ? 'granted' : 'denied';
        if ($consent === 'granted' && $permission === 'granted') {
            $status = $send_available ? 'ready' : 'device_unavailable';
        } else {
            $status = $consent === 'denied' ? 'denied' : 'permission_missing';
        }
        $this->db->query(
            "UPDATE `{$this->prefix}sms_devices`
             SET `operator_consent` = '" . $this->db->escape($consent) . "',
                 `android_permission` = '" . $this->db->escape($permission) . "',
                 `status` = '" . $this->db->escape($status) . "',
                 `responded_at` = NOW(),
                 `last_seen_at` = NOW(),
                 `date_modified` = NOW()
             WHERE `app_session_id` = " . (int)$session_id
        );
        return $this->getByAppSessionId($session_id);
    }

    public function requestInboundPermission(int $device_id): void {
        $this->db->query(
            "UPDATE `{$this->prefix}sms_devices`
             SET `inbound_admin_requested` = 1,
                 `inbound_operator_consent` = 'pending',
                 `inbound_status` = 'pending_operator',
                 `inbound_requested_at` = NOW(),
                 `date_modified` = NOW()
             WHERE `device_id` = " . (int)$device_id
        );
    }

    public function revokeInboundPermission(int $device_id): void {
        $this->db->query(
            "UPDATE `{$this->prefix}sms_devices`
             SET `inbound_admin_requested` = 0,
                 `inbound_operator_consent` = 'none',
                 `inbound_status` = 'disabled_by_admin',
                 `inbound_requested_at` = NULL,
                 `inbound_responded_at` = NULL,
                 `date_modified` = NOW()
             WHERE `device_id` = " . (int)$device_id
        );
    }

    public function saveInboundOperatorConsent(int $session_id, string $consent, bool $android_permission): array {
        $consent = in_array($consent, ['granted', 'denied'], true) ? $consent : 'denied';
        $permission = $android_permission ? 'granted' : 'denied';
        $status = $consent === 'granted' && $permission === 'granted'
            ? 'ready'
            : ($consent === 'denied' ? 'denied' : 'permission_missing');
        $this->db->query(
            "UPDATE `{$this->prefix}sms_devices`
             SET `inbound_operator_consent` = '" . $this->db->escape($consent) . "',
                 `inbound_android_permission` = '" . $this->db->escape($permission) . "',
                 `inbound_status` = '" . $this->db->escape($status) . "',
                 `inbound_responded_at` = NOW(),
                 `last_seen_at` = NOW(),
                 `date_modified` = NOW()
             WHERE `app_session_id` = " . (int)$session_id
        );
        return $this->getByAppSessionId($session_id);
    }

    public function markUsed(int $device_id): void {
        $this->db->query("UPDATE `{$this->prefix}sms_devices` SET `last_used_at` = NOW(), `sent_today` = `sent_today` + 1, `date_modified` = NOW() WHERE `device_id` = " . (int)$device_id);
    }

    public function markInboundUsed(int $device_id, int $subscription_id = -1): void {
        $subscription_id = max(-1, min(2147483647, $subscription_id));
        $this->db->query(
            "UPDATE `{$this->prefix}sms_devices`
             SET `last_inbound_at` = NOW(),
                 `last_inbound_subscription_id` = " . (int)$subscription_id . ",
                 `last_seen_at` = NOW(),
                 `date_modified` = NOW()
             WHERE `device_id` = " . (int)$device_id
        );
    }
}
