<?php

declare(strict_types=1);

/**
 * DentalCare SMS readiness helpers.
 *
 * This layer is deliberately provider-light: it can operate in log-only mode
 * during setup/orientation and can be switched to supported providers when the
 * client supplies live credentials. All functions are defensive so the admin
 * pages can load during partial migrations.
 */

if (!function_exists('dental_sms_schema_ready')) {
    function dental_sms_schema_ready(): bool
    {
        try {
            return schema_table_exists(table('sms_outbox')) && schema_table_exists(table('sms_templates'));
        } catch (Throwable) {
            return false;
        }
    }
}

if (!function_exists('dental_sms_provider_options')) {
    function dental_sms_provider_options(): array
    {
        return [
            'log' => 'Log only / setup mode',
            'semaphore' => 'Semaphore PH',
            'twilio' => 'Twilio',
            'android_gateway' => 'Android phone/SIM gateway (TextBee)',
            'generic_http' => 'Generic HTTP webhook',
        ];
    }
}

if (!function_exists('dental_sms_settings')) {
    function dental_sms_settings(): array
    {
        $provider = (string)setting_get('sms', 'provider', 'log');
        if (!array_key_exists($provider, dental_sms_provider_options())) $provider = 'log';
        return [
            'enabled' => (string)setting_get('sms', 'enabled', '0') === '1',
            'provider' => $provider,
            'sender_name' => (string)setting_get('sms', 'sender_name', 'DentalCare'),
            'default_country_code' => (string)setting_get('sms', 'default_country_code', '+63'),
            'automatic_enabled' => (string)setting_get('sms', 'automatic_enabled', '0') === '1',
            'queue_batch_size' => max(1, min(50, (int)setting_get('sms', 'queue_batch_size', '10'))),
            'semaphore_api_key' => (string)setting_get('sms', 'semaphore_api_key', ''),
            'twilio_sid' => (string)setting_get('sms', 'twilio_sid', ''),
            'twilio_token' => (string)setting_get('sms', 'twilio_token', ''),
            'twilio_from' => (string)setting_get('sms', 'twilio_from', ''),
            'android_gateway_url' => (string)setting_get('sms', 'android_gateway_url', ''),
            'android_gateway_api_key' => (string)setting_get('sms', 'android_gateway_api_key', ''),
            'android_gateway_device_id' => (string)setting_get('sms', 'android_gateway_device_id', ''),
            'android_gateway_header_name' => (string)setting_get('sms', 'android_gateway_header_name', 'x-api-key'),
            'generic_url' => (string)setting_get('sms', 'generic_url', ''),
            'generic_method' => strtoupper((string)setting_get('sms', 'generic_method', 'POST')) === 'GET' ? 'GET' : 'POST',
            'generic_auth_header' => (string)setting_get('sms', 'generic_auth_header', ''),
        ];
    }
}

if (!function_exists('dental_sms_normalize_number')) {
    function dental_sms_normalize_number(string $raw, ?string $countryCode = null): string
    {
        $countryCode = $countryCode ?: (string)(dental_sms_settings()['default_country_code'] ?? '+63');
        $value = trim($raw);
        if ($value === '') return '';
        $value = preg_replace('/[^0-9+]/', '', $value) ?? '';
        if ($value === '') return '';
        if (str_starts_with($value, '+')) return $value;
        if (str_starts_with($value, '00')) return '+' . substr($value, 2);
        if ($countryCode === '+63') {
            if (preg_match('/^09\d{9}$/', $value)) return '+63' . substr($value, 1);
            if (preg_match('/^9\d{9}$/', $value)) return '+63' . $value;
            if (preg_match('/^63\d{10}$/', $value)) return '+' . $value;
        }
        $digits = ltrim($value, '0');
        return rtrim($countryCode) . $digits;
    }
}

if (!function_exists('dental_sms_template_fallbacks')) {
    function dental_sms_template_fallbacks(): array
    {
        return [
            'appointment_received' => [
                'name' => 'Appointment request received', 'category' => 'Appointments',
                'body' => '{{clinic_name}} received your appointment request {{reference}} for {{service_name}}. We will contact you after review. Reply through the clinic contact number for changes.',
                'variables' => ['clinic_name','recipient_name','reference','service_name','appointment_datetime','support_phone'],
            ],
            'appointment_confirmed' => [
                'name' => 'Appointment confirmed', 'category' => 'Appointments',
                'body' => '{{clinic_name}}: Your appointment is confirmed on {{appointment_datetime}} for {{service_name}} with {{provider_name}}. Please arrive 10 minutes early.',
                'variables' => ['clinic_name','recipient_name','appointment_datetime','service_name','provider_name','branch_name','support_phone'],
            ],
            'appointment_rescheduled' => [
                'name' => 'Appointment rescheduled', 'category' => 'Appointments',
                'body' => '{{clinic_name}}: Your appointment was rescheduled to {{appointment_datetime}}. Service: {{service_name}}. Contact us if this schedule is not suitable.',
                'variables' => ['clinic_name','recipient_name','appointment_datetime','service_name','provider_name','support_phone'],
            ],
            'appointment_cancelled' => [
                'name' => 'Appointment cancelled', 'category' => 'Appointments',
                'body' => '{{clinic_name}}: Your appointment on {{appointment_datetime}} was cancelled. Contact the clinic if you need to book another schedule.',
                'variables' => ['clinic_name','recipient_name','appointment_datetime','service_name','support_phone'],
            ],
            'appointment_reminder' => [
                'name' => 'Appointment reminder', 'category' => 'Reminders',
                'body' => '{{clinic_name}} reminder: Your appointment is on {{appointment_datetime}} for {{service_name}}. Please contact us early if you need to reschedule.',
                'variables' => ['clinic_name','recipient_name','lead_time','appointment_datetime','service_name','provider_name','support_phone'],
            ],
            'payment_receipt' => [
                'name' => 'Payment receipt', 'category' => 'Billing',
                'body' => '{{clinic_name}} received your payment of {{amount}}. Receipt: {{receipt_number}}. Thank you.',
                'variables' => ['clinic_name','recipient_name','amount','receipt_number','payment_date','support_phone'],
            ],
            'followup_reminder' => [
                'name' => 'Follow-up reminder', 'category' => 'Reminders',
                'body' => '{{clinic_name}} reminder: Your dental follow-up is due on {{due_date}}. Purpose: {{purpose}}. Please contact the clinic to schedule.',
                'variables' => ['clinic_name','recipient_name','due_date','purpose','provider_name','support_phone'],
            ],
            'recall_reminder' => [
                'name' => 'Recall reminder', 'category' => 'Reminders',
                'body' => '{{clinic_name}} reminder: Your {{recall_type}} dental recall is due on {{due_date}}. Please contact the clinic to book a visit.',
                'variables' => ['clinic_name','recipient_name','recall_type','due_date','support_phone'],
            ],
        ];
    }
}

if (!function_exists('dental_sms_template_get')) {
    function dental_sms_template_get(string $key): array
    {
        $fallbacks = dental_sms_template_fallbacks();
        $fallback = $fallbacks[$key] ?? [
            'name' => ucwords(str_replace('_', ' ', $key)), 'category' => 'General',
            'body' => '{{clinic_name}}: {{message}}', 'variables' => ['clinic_name','recipient_name','message','support_phone'],
        ];
        if (!dental_sms_schema_ready()) return $fallback;
        try {
            $stmt = db()->prepare('SELECT * FROM `' . table('sms_templates') . '` WHERE template_key=? AND is_active=1 LIMIT 1');
            $stmt->execute([$key]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!$row) return $fallback;
            $variables = json_decode((string)($row['allowed_variables_json'] ?? '[]'), true);
            return [
                'id' => (int)$row['id'], 'key' => (string)$row['template_key'], 'name' => (string)$row['name'],
                'category' => (string)$row['category'], 'body' => (string)$row['body_template'],
                'variables' => is_array($variables) ? $variables : ($fallback['variables'] ?? []),
            ];
        } catch (Throwable) {
            return $fallback;
        }
    }
}

if (!function_exists('dental_sms_render_value')) {
    function dental_sms_render_value(string $template, array $variables): string
    {
        $replace = [];
        foreach ($variables as $key => $value) {
            if (is_array($value) || is_object($value)) continue;
            $replace['{{' . $key . '}}'] = (string)$value;
        }
        $body = strtr($template, $replace);
        return trim(preg_replace('/\s+/', ' ', $body) ?? $body);
    }
}

if (!function_exists('dental_sms_template_message')) {
    function dental_sms_template_message(string $key, array $variables = []): array
    {
        $clinic = public_clinic_profile();
        $variables = array_merge([
            'clinic_name' => (string)($clinic['name'] ?? app_config('app.name', 'Dental Clinic')),
            'support_phone' => (string)($clinic['phone'] ?? ''),
            'booking_url' => app_absolute_url('appointment-returning.php'),
        ], $variables);
        $template = dental_sms_template_get($key);
        return [
            'body' => dental_sms_render_value((string)$template['body'], $variables),
            'template' => $template,
            'variables' => $variables,
        ];
    }
}

if (!function_exists('dental_sms_queue')) {
    function dental_sms_queue(string $to, string $body, array $meta = [], bool $attemptNow = false): array
    {
        $settings = dental_sms_settings();
        $number = dental_sms_normalize_number($to, (string)$settings['default_country_code']);
        if ($number === '' || !preg_match('/^\+[1-9]\d{7,14}$/', $number)) {
            throw new RuntimeException('Enter a valid mobile number in international format or local PH mobile format.');
        }
        $body = trim($body);
        if ($body === '') throw new RuntimeException('SMS message body is required.');
        if (mb_strlen($body) > 480) throw new RuntimeException('SMS message is too long. Keep it within 480 characters.');
        if (!dental_sms_schema_ready()) throw new RuntimeException('Apply the SMS readiness update before queueing SMS.');
        $pdo = db();
        $status = ((bool)$settings['enabled']) ? 'pending' : 'held';
        $meta = array_merge($meta, ['provider' => $settings['provider']]);
        $stmt = $pdo->prepare('INSERT INTO `' . table('sms_outbox') . '` (branch_id,recipient_number,body,status,provider,metadata_json,created_by,available_at,created_at,updated_at) VALUES (?,?,?,?,?,?,?,NOW(),NOW(),NOW())');
        $stmt->execute([(int)(current_user()['branch_id'] ?? 0) ?: null, $number, $body, $status, (string)$settings['provider'], json_encode($meta, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE), (int)(current_user()['id'] ?? 0) ?: null]);
        $id = (int)$pdo->lastInsertId();
        if ($attemptNow) return dental_sms_process_one($id);
        return ['id' => $id, 'status' => $status, 'recipient' => $number, 'error' => null];
    }
}

if (!function_exists('dental_sms_queue_template')) {
    function dental_sms_queue_template(string $key, string $to, array $variables = [], array $meta = [], bool $attemptNow = false): array
    {
        $message = dental_sms_template_message($key, $variables);
        $meta = array_merge($meta, ['template_key' => $key]);
        return dental_sms_queue($to, (string)$message['body'], $meta, $attemptNow);
    }
}

if (!function_exists('dental_sms_provider_send')) {
    function dental_sms_provider_send(string $to, string $body, array $settings): array
    {
        if (function_exists('app_orientation_mode_active') && app_orientation_mode_active()) {
            return ['ok' => false, 'status' => 'held', 'message' => 'Orientation mode is active; real SMS delivery is paused.', 'provider_reference' => null, 'raw' => ''];
        }
        if (empty($settings['enabled'])) {
            return ['ok' => false, 'status' => 'held', 'message' => 'SMS sending is disabled. Message is held in the outbox.', 'provider_reference' => null, 'raw' => ''];
        }
        $provider = (string)($settings['provider'] ?? 'log');
        if ($provider === 'log') {
            return ['ok' => true, 'status' => 'sent', 'message' => 'Log-only SMS mode recorded the message. No carrier delivery was attempted.', 'provider_reference' => 'LOG-' . date('YmdHis'), 'raw' => 'log'];
        }
        if (!function_exists('curl_init')) {
            return ['ok' => false, 'status' => 'failed', 'message' => 'PHP cURL extension is required for live SMS delivery.', 'provider_reference' => null, 'raw' => ''];
        }
        if ($provider === 'semaphore') {
            $apiKey = trim((string)($settings['semaphore_api_key'] ?? ''));
            if ($apiKey === '') return ['ok'=>false,'status'=>'failed','message'=>'Semaphore API key is missing.','provider_reference'=>null,'raw'=>''];
            $payload = ['apikey'=>$apiKey,'number'=>$to,'message'=>$body,'sendername'=>substr((string)($settings['sender_name'] ?? 'DentalCare'),0,11)];
            $ch = curl_init('https://api.semaphore.co/api/v4/messages');
            curl_setopt_array($ch, [CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>http_build_query($payload),CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>30]);
            $raw = (string)curl_exec($ch); $err = curl_error($ch); $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
            if ($raw === '' && $err !== '') return ['ok'=>false,'status'=>'failed','message'=>$err,'provider_reference'=>null,'raw'=>$raw];
            $decoded = json_decode($raw, true); $reference = is_array($decoded) ? (string)($decoded[0]['message_id'] ?? $decoded['message_id'] ?? '') : '';
            $ok = $code >= 200 && $code < 300;
            return ['ok'=>$ok,'status'=>$ok?'sent':'failed','message'=>$ok?'Semaphore accepted the SMS.':'Semaphore rejected the SMS. HTTP '.$code,'provider_reference'=>$reference?:null,'raw'=>substr($raw,0,4000)];
        }
        if ($provider === 'twilio') {
            $sid = trim((string)($settings['twilio_sid'] ?? '')); $token = (string)($settings['twilio_token'] ?? ''); $from = trim((string)($settings['twilio_from'] ?? ''));
            if ($sid === '' || $token === '' || $from === '') return ['ok'=>false,'status'=>'failed','message'=>'Twilio SID, token, and From number are required.','provider_reference'=>null,'raw'=>''];
            $url = 'https://api.twilio.com/2010-04-01/Accounts/' . rawurlencode($sid) . '/Messages.json';
            $ch = curl_init($url);
            curl_setopt_array($ch, [CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>http_build_query(['From'=>$from,'To'=>$to,'Body'=>$body]),CURLOPT_RETURNTRANSFER=>true,CURLOPT_USERPWD=>$sid . ':' . $token,CURLOPT_TIMEOUT=>30]);
            $raw = (string)curl_exec($ch); $err = curl_error($ch); $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
            if ($raw === '' && $err !== '') return ['ok'=>false,'status'=>'failed','message'=>$err,'provider_reference'=>null,'raw'=>$raw];
            $decoded = json_decode($raw, true); $reference = is_array($decoded) ? (string)($decoded['sid'] ?? '') : '';
            $ok = $code >= 200 && $code < 300;
            return ['ok'=>$ok,'status'=>$ok?'sent':'failed','message'=>$ok?'Twilio accepted the SMS.':'Twilio rejected the SMS. HTTP '.$code,'provider_reference'=>$reference?:null,'raw'=>substr($raw,0,4000)];
        }
        if ($provider === 'android_gateway') {
            $url = trim((string)($settings['android_gateway_url'] ?? ''));
            if ($url === '' || !filter_var($url, FILTER_VALIDATE_URL)) return ['ok'=>false,'status'=>'failed','message'=>'Android/TextBee gateway URL is missing or invalid.','provider_reference'=>null,'raw'=>''];
            $apiKey = trim((string)($settings['android_gateway_api_key'] ?? ''));
            $deviceId = trim((string)($settings['android_gateway_device_id'] ?? ''));
            $headerName = trim((string)($settings['android_gateway_header_name'] ?? 'x-api-key')) ?: 'x-api-key';
            $payload = [
                'to' => $to,
                'number' => $to,
                'recipient' => $to,
                'recipients' => [$to],
                'message' => $body,
                'body' => $body,
                'sender' => (string)($settings['sender_name'] ?? 'DentalCare'),
            ];
            if ($deviceId !== '') $payload['device_id'] = $deviceId;
            $headers = ['Content-Type: application/json', 'Accept: application/json'];
            if ($apiKey !== '') $headers[] = $headerName . ': ' . $apiKey;
            $ch = curl_init($url);
            curl_setopt_array($ch, [CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>json_encode($payload, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE),CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>30,CURLOPT_HTTPHEADER=>$headers]);
            $raw = (string)curl_exec($ch); $err = curl_error($ch); $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
            if ($raw === '' && $err !== '') return ['ok'=>false,'status'=>'failed','message'=>$err,'provider_reference'=>null,'raw'=>$raw];
            $decoded = json_decode($raw, true);
            $reference = is_array($decoded) ? (string)($decoded['id'] ?? $decoded['message_id'] ?? $decoded['sms_id'] ?? $decoded['reference'] ?? '') : '';
            $ok = $code >= 200 && $code < 300;
            return ['ok'=>$ok,'status'=>$ok?'sent':'failed','message'=>$ok?'Android/TextBee gateway accepted the SMS request.':'Android/TextBee gateway rejected the SMS. HTTP '.$code,'provider_reference'=>$reference?:null,'raw'=>substr($raw,0,4000)];
        }
        if ($provider === 'generic_http') {
            $url = trim((string)($settings['generic_url'] ?? ''));
            if ($url === '' || !filter_var($url, FILTER_VALIDATE_URL)) return ['ok'=>false,'status'=>'failed','message'=>'Generic HTTP SMS URL is missing or invalid.','provider_reference'=>null,'raw'=>''];
            $method = (string)($settings['generic_method'] ?? 'POST');
            $payload = ['to'=>$to,'message'=>$body,'sender'=>(string)($settings['sender_name'] ?? 'DentalCare')];
            $target = $method === 'GET' ? $url . (str_contains($url,'?')?'&':'?') . http_build_query($payload) : $url;
            $ch = curl_init($target);
            $headers = [];
            $auth = trim((string)($settings['generic_auth_header'] ?? ''));
            if ($auth !== '') $headers[] = $auth;
            curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>30,CURLOPT_HTTPHEADER=>$headers]);
            if ($method !== 'GET') { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($payload)); }
            $raw = (string)curl_exec($ch); $err = curl_error($ch); $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
            if ($raw === '' && $err !== '') return ['ok'=>false,'status'=>'failed','message'=>$err,'provider_reference'=>null,'raw'=>$raw];
            $ok = $code >= 200 && $code < 300;
            return ['ok'=>$ok,'status'=>$ok?'sent':'failed','message'=>$ok?'SMS webhook accepted the request.':'SMS webhook rejected the request. HTTP '.$code,'provider_reference'=>null,'raw'=>substr($raw,0,4000)];
        }
        return ['ok'=>false,'status'=>'failed','message'=>'Unsupported SMS provider selected.','provider_reference'=>null,'raw'=>''];
    }
}

if (!function_exists('dental_sms_process_one')) {
    function dental_sms_process_one(int $id): array
    {
        if ($id < 1 || !dental_sms_schema_ready()) return ['id'=>$id,'status'=>'failed','error'=>'SMS outbox unavailable.'];
        $pdo = db();
        $stmt = $pdo->prepare('SELECT * FROM `' . table('sms_outbox') . '` WHERE id=? LIMIT 1');
        $stmt->execute([$id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$row) return ['id'=>$id,'status'=>'failed','error'=>'SMS outbox record not found.'];
        if (in_array((string)$row['status'], ['sent','cancelled'], true)) return ['id'=>$id,'status'=>(string)$row['status'],'error'=>null];
        $settings = dental_sms_settings();
        $result = dental_sms_provider_send((string)$row['recipient_number'], (string)$row['body'], $settings);
        $status = (string)($result['status'] ?? (!empty($result['ok']) ? 'sent' : 'failed'));
        $pdo->prepare('UPDATE `' . table('sms_outbox') . '` SET status=?,attempts=attempts+1,last_attempt_at=NOW(),sent_at=IF(?=\'sent\',NOW(),sent_at),last_error=?,provider_reference=?,provider_response=?,updated_at=NOW() WHERE id=?')
            ->execute([$status,$status, $status === 'sent' ? null : substr((string)($result['message'] ?? ''),0,1000), (string)($result['provider_reference'] ?? ''), (string)($result['raw'] ?? ''), $id]);
        return ['id'=>$id,'status'=>$status,'error'=>$status === 'sent' ? null : (string)($result['message'] ?? ''),'message'=>(string)($result['message'] ?? '')];
    }
}

if (!function_exists('dental_sms_process_queue')) {
    function dental_sms_process_queue(?PDO $pdo = null, int $limit = 10, string $trigger = 'manual'): array
    {
        $pdo ??= db();
        $result = ['selected'=>0,'sent'=>0,'failed'=>0,'held'=>0,'skipped'=>0];
        if (!dental_sms_schema_ready()) return $result;
        $limit = max(1, min(50, $limit));
        $rows = $pdo->query('SELECT id FROM `' . table('sms_outbox') . '` WHERE status IN (\'pending\',\'retry\') AND available_at<=NOW() ORDER BY created_at ASC LIMIT ' . $limit)->fetchAll(PDO::FETCH_COLUMN);
        foreach ($rows as $id) {
            $result['selected']++;
            $one = dental_sms_process_one((int)$id);
            $status = (string)($one['status'] ?? 'failed');
            if (isset($result[$status])) $result[$status]++; else $result['failed']++;
        }
        setting_set('sms','last_run_at',date('Y-m-d H:i:s'));
        setting_set('sms','last_trigger',$trigger);
        setting_set('sms','last_result_json',json_encode($result,JSON_UNESCAPED_SLASHES));
        return $result;
    }
}

if (!function_exists('dental_sms_health')) {
    function dental_sms_health(?PDO $pdo = null): array
    {
        $pdo ??= db();
        $settings = dental_sms_settings();
        $health = ['ready'=>true,'issues'=>[],'pending'=>0,'sent'=>0,'failed'=>0,'held'=>0,'provider'=>$settings['provider']];
        if (!dental_sms_schema_ready()) { $health['ready']=false; $health['issues'][]='SMS outbox/templates table is unavailable.'; return $health; }
        if (empty($settings['enabled'])) $health['issues'][]='SMS sending is disabled; messages will be held or logged.';
        if (function_exists('app_orientation_mode_active') && app_orientation_mode_active()) $health['issues'][]='Orientation Mode is active; live SMS delivery is paused.';
        if ($settings['provider'] === 'semaphore' && trim((string)$settings['semaphore_api_key']) === '') $health['issues'][]='Semaphore API key is missing.';
        if ($settings['provider'] === 'twilio' && (trim((string)$settings['twilio_sid']) === '' || trim((string)$settings['twilio_token']) === '' || trim((string)$settings['twilio_from']) === '')) $health['issues'][]='Twilio SID, token, and From number are required.';
        if ($settings['provider'] === 'android_gateway' && trim((string)$settings['android_gateway_url']) === '') $health['issues'][]='Android/TextBee gateway URL is missing.';
        if ($settings['provider'] === 'generic_http' && trim((string)$settings['generic_url']) === '') $health['issues'][]='Generic HTTP provider URL is missing.';
        try {
            $rows = $pdo->query('SELECT status,COUNT(*) total FROM `' . table('sms_outbox') . '` GROUP BY status')->fetchAll(PDO::FETCH_KEY_PAIR);
            foreach (['pending','sent','failed','held'] as $key) $health[$key] = (int)($rows[$key] ?? 0);
        } catch (Throwable) {}
        $health['ready'] = dental_sms_schema_ready() && ($settings['provider'] === 'log' || empty($health['issues']) || (empty($settings['enabled']) && count($health['issues']) === 1));
        return $health;
    }
}
