<?php
declare(strict_types=1);

require_once __DIR__ . '/db.php';
require_once __DIR__ . '/users.php';
require_once __DIR__ . '/mailer.php';

function reminder_settings(int $tenantId): array
{
    $stmt = db()->prepare('SELECT * FROM reminder_settings WHERE tenant_id = ?');
    $stmt->execute([$tenantId]);
    $row = $stmt->fetch();
    if ($row) {
        return $row;
    }
    db()->prepare(
        'INSERT INTO reminder_settings (tenant_id, email_enabled, sms_enabled, hours_before)
         VALUES (?, 1, 0, 24)'
    )->execute([$tenantId]);
    $stmt->execute([$tenantId]);
    return $stmt->fetch() ?: [
        'tenant_id' => $tenantId,
        'email_enabled' => 1,
        'sms_enabled' => 0,
        'hours_before' => 24,
        'email_from_name' => 'BarberShop',
        'email_from_address' => null,
        'email_subject' => 'Appointment reminder',
        'sms_template' => null,
        'email_template' => null,
    ];
}

/**
 * True when this client can receive carrier email-to-SMS (not retired / has gateway domain).
 */
function reminder_user_sms_viable(array $userOrBookingRow): bool
{
    $phone = user_normalize_phone($userOrBookingRow['user_phone'] ?? $userOrBookingRow['customer_phone'] ?? $userOrBookingRow['phone'] ?? null);
    if (!$phone) {
        return false;
    }
    $status = (string) ($userOrBookingRow['carrier_status'] ?? '');
    if ($status === 'retired') {
        return false;
    }
    $domain = trim((string) ($userOrBookingRow['custom_sms_domain'] ?? ''));
    if ($domain === '') {
        $domain = trim((string) ($userOrBookingRow['carrier_domain'] ?? ''));
    }
    if ($domain === '' || strtolower($domain) === 'custom') {
        return false;
    }
    return sms_gateway_address($phone, $domain) !== null;
}

/**
 * Schedule email/SMS reminders for a booking (idempotent per channel).
 */
function reminder_schedule_for_booking(int $bookingId): void
{
    $stmt = db()->prepare(
        'SELECT b.*, u.email AS user_email, u.phone AS user_phone, u.sms_opt_in, u.email_opt_in,
                u.name AS user_name, u.carrier_id, u.custom_sms_domain,
                c.sms_domain AS carrier_domain, c.status AS carrier_status, c.name AS carrier_name,
                s.name AS service_name, sb.name AS staff_name
         FROM bookings b
         LEFT JOIN users u ON u.id = b.user_id
         LEFT JOIN sms_carriers c ON c.id = u.carrier_id
         LEFT JOIN services s ON s.id = b.service_id
         LEFT JOIN staff_barbers sb ON sb.id = b.staff_id
         WHERE b.id = ?'
    );
    $stmt->execute([$bookingId]);
    $b = $stmt->fetch();
    if (!$b || in_array((string) $b['status'], ['cancelled', 'completed'], true)) {
        reminder_cancel_pending($bookingId);
        return;
    }

    $settings = reminder_settings((int) $b['tenant_id']);
    $hours = max(1, (int) ($settings['hours_before'] ?? 24));
    $starts = new DateTime((string) $b['starts_at']);
    $scheduled = (clone $starts)->modify('-' . $hours . ' hours');
    $now = new DateTime('now');

    // If reminder time already passed but appointment is still in the future, send ASAP
    if ($scheduled < $now && $starts > $now) {
        $scheduled = $now;
    }
    // Past appointments: skip
    if ($starts <= $now) {
        return;
    }

    $hasEmail = (bool) user_normalize_email($b['user_email'] ?? null);
    $smsViable = reminder_user_sms_viable($b);

    $channels = [];
    // Email is the reliable path — schedule whenever enabled and an address exists
    if (!empty($settings['email_enabled']) && $hasEmail && (empty($b['user_id']) || !empty($b['email_opt_in']))) {
        $channels[] = 'email';
    }
    // Carrier email-to-SMS only when gateway looks usable; otherwise clients use email
    if (!empty($settings['sms_enabled']) && $smsViable && (empty($b['user_id']) || !empty($b['sms_opt_in']))) {
        $channels[] = 'sms';
    }
    // If email was enabled but missing address, and SMS isn't viable, nothing to schedule
    // (booking still succeeds — admin can add email later)

    foreach ($channels as $channel) {
        $exists = db()->prepare(
            'SELECT id, status FROM booking_reminders
             WHERE booking_id = ? AND channel = ? AND status IN (\'pending\',\'sent\')
             LIMIT 1'
        );
        $exists->execute([$bookingId, $channel]);
        $row = $exists->fetch();
        if ($row) {
            if ($row['status'] === 'pending') {
                db()->prepare('UPDATE booking_reminders SET scheduled_for = ?, user_id = ? WHERE id = ?')
                    ->execute([$scheduled->format('Y-m-d H:i:s'), $b['user_id'] ?: null, $row['id']]);
            }
            continue;
        }
        db()->prepare(
            'INSERT INTO booking_reminders (tenant_id, booking_id, user_id, channel, scheduled_for, status)
             VALUES (?,?,?,?,?,\'pending\')'
        )->execute([
            (int) $b['tenant_id'],
            $bookingId,
            $b['user_id'] ?: null,
            $channel,
            $scheduled->format('Y-m-d H:i:s'),
        ]);
    }
}

function reminder_cancel_pending(int $bookingId): void
{
    db()->prepare(
        'UPDATE booking_reminders SET status = \'cancelled\'
         WHERE booking_id = ? AND status = \'pending\''
    )->execute([$bookingId]);
}

function reminder_render_template(string $template, array $vars): string
{
    $out = $template;
    foreach ($vars as $k => $v) {
        $out = str_replace('{' . $k . '}', (string) $v, $out);
    }
    return $out;
}

function reminder_booking_vars(array $b, array $settings): array
{
    $starts = new DateTime((string) $b['starts_at']);
    return [
        'name' => (string) ($b['user_name'] ?: $b['customer_name']),
        'service' => (string) ($b['service_name'] ?? 'your appointment'),
        'date' => $starts->format('D, M j'),
        'time' => $starts->format('g:i A'),
        'barber' => (string) ($b['staff_name'] ?? 'our team'),
        'shop' => (string) ($settings['email_from_name'] ?? 'the shop'),
        'phone' => (string) ($b['user_phone'] ?: $b['customer_phone'] ?: ''),
    ];
}

/**
 * Process due reminders. Returns summary counts.
 *
 * @return array{sent:int,failed:int,skipped:int,processed:int,details:list}
 */
function reminder_process_due(int $limit = 50, ?int $tenantId = null): array
{
    $sql = 'SELECT r.* FROM booking_reminders r
            WHERE r.status = \'pending\' AND r.scheduled_for <= NOW()';
    $params = [];
    if ($tenantId) {
        $sql .= ' AND r.tenant_id = ?';
        $params[] = $tenantId;
    }
    $sql .= ' ORDER BY r.scheduled_for ASC LIMIT ' . (int) $limit;
    $stmt = db()->prepare($sql);
    $stmt->execute($params);
    $rows = $stmt->fetchAll();

    $summary = ['sent' => 0, 'failed' => 0, 'skipped' => 0, 'processed' => 0, 'details' => []];
    foreach ($rows as $r) {
        $summary['processed']++;
        $result = reminder_send_one((int) $r['id']);
        $summary[$result['status'] === 'sent' ? 'sent' : ($result['status'] === 'skipped' ? 'skipped' : 'failed')]++;
        $summary['details'][] = $result;
    }
    return $summary;
}

function reminder_send_one(int $reminderId): array
{
    $stmt = db()->prepare(
        'SELECT r.*, b.customer_name, b.customer_phone, b.starts_at, b.status AS booking_status,
                u.name AS user_name, u.email AS user_email, u.phone AS user_phone,
                u.email_opt_in, u.sms_opt_in, u.carrier_id, u.custom_sms_domain,
                s.name AS service_name, sb.name AS staff_name,
                c.sms_domain AS carrier_domain, c.name AS carrier_name, c.status AS carrier_status
         FROM booking_reminders r
         INNER JOIN bookings b ON b.id = r.booking_id
         LEFT JOIN users u ON u.id = r.user_id
         LEFT JOIN sms_carriers c ON c.id = u.carrier_id
         LEFT JOIN services s ON s.id = b.service_id
         LEFT JOIN staff_barbers sb ON sb.id = b.staff_id
         WHERE r.id = ?'
    );
    $stmt->execute([$reminderId]);
    $r = $stmt->fetch();
    if (!$r) {
        return ['id' => $reminderId, 'status' => 'failed', 'error' => 'Reminder not found'];
    }
    if ($r['status'] !== 'pending') {
        return ['id' => $reminderId, 'status' => (string) $r['status'], 'error' => 'Not pending'];
    }
    if (in_array((string) $r['booking_status'], ['cancelled', 'completed'], true)) {
        db()->prepare('UPDATE booking_reminders SET status = \'cancelled\', error_message = ? WHERE id = ?')
            ->execute(['Booking cancelled/completed', $reminderId]);
        return ['id' => $reminderId, 'status' => 'skipped', 'error' => 'Booking not active'];
    }

    $settings = reminder_settings((int) $r['tenant_id']);
    $vars = reminder_booking_vars($r, $settings);
    $channel = (string) $r['channel'];

    try {
        if ($channel === 'email') {
            if (empty($r['email_opt_in']) && $r['user_id']) {
                throw new RuntimeException('User opted out of email', 100);
            }
            $to = user_normalize_email($r['user_email'] ?? null);
            if (!$to) {
                throw new RuntimeException('No email on user record', 100);
            }
            $subject = (string) ($settings['email_subject'] ?: 'Appointment reminder');
            $bodyTpl = (string) ($settings['email_template'] ?: 
                "Hi {name},\n\nThis is a reminder that you have {service} on {date} at {time} with {barber} at {shop}.\n\nSee you soon!");
            $body = reminder_render_template($bodyTpl, $vars);
            reminder_send_email($to, $subject, $body, $settings);
        } elseif ($channel === 'sms') {
            if (empty($r['sms_opt_in']) && $r['user_id']) {
                throw new RuntimeException('User opted out of SMS', 100);
            }
            $phone = user_normalize_phone($r['user_phone'] ?? $r['customer_phone'] ?? null);
            if (!$phone) {
                throw new RuntimeException('No phone on user record', 100);
            }
            $tpl = (string) ($settings['sms_template'] ?: 'Hi {name}, reminder: {service} on {date} at {time} with {barber}.');
            $msg = reminder_render_template($tpl, $vars);
            $ref = reminder_send_sms($phone, $msg, $r, $settings);
            db()->prepare('UPDATE booking_reminders SET provider_ref = ? WHERE id = ?')
                ->execute([$ref, $reminderId]);
        } else {
            throw new RuntimeException('Unknown channel');
        }

        db()->prepare(
            'UPDATE booking_reminders SET status = \'sent\', sent_at = NOW(), error_message = NULL WHERE id = ?'
        )->execute([$reminderId]);
        return ['id' => $reminderId, 'status' => 'sent', 'channel' => $channel];
    } catch (Throwable $e) {
        $code = (int) $e->getCode();
        $status = $code === 100 ? 'skipped' : 'failed';
        db()->prepare(
            'UPDATE booking_reminders SET status = ?, error_message = ?, sent_at = NOW() WHERE id = ?'
        )->execute([$status, mb_substr($e->getMessage(), 0, 500), $reminderId]);
        return ['id' => $reminderId, 'status' => $status, 'channel' => $channel, 'error' => $e->getMessage()];
    }
}

function reminder_send_email(string $to, string $subject, string $body, array $settings): void
{
    $fromName = (string) ($settings['email_from_name'] ?: 'BarberShop');
    $fromAddr = (string) ($settings['email_from_address'] ?: (defined('REMINDER_EMAIL_FROM') ? REMINDER_EMAIL_FROM : 'noreply@localhost'));
    mail_send($to, $subject, $body, $fromAddr, $fromName);
}

/**
 * Preferred path: email-to-SMS via carrier gateway (phone@vtext.com) using SMTP/mail.
 * Optional Twilio only if keys are set — not required.
 *
 * @return string provider reference
 */
function reminder_send_sms(string $phoneDigits, string $message, array $userRow = [], array $settings = []): string
{
    $logDir = APP_ROOT . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'reminders';
    if (!is_dir($logDir)) {
        mkdir($logDir, 0775, true);
    }

    $domain = trim((string) ($userRow['custom_sms_domain'] ?? ''));
    if ($domain === '') {
        $domain = trim((string) ($userRow['carrier_domain'] ?? ''));
    }
    if ($domain === 'custom') {
        $domain = '';
    }

    $gateway = sms_gateway_address($phoneDigits, $domain);
    $carrierStatus = (string) ($userRow['carrier_status'] ?? '');

    if ($gateway) {
        if ($carrierStatus === 'retired') {
            throw new RuntimeException(
                'Carrier email-to-SMS gateway is retired (' . ($userRow['carrier_name'] ?? $domain) . '). Use email reminders instead.',
                100
            );
        }

        file_put_contents(
            $logDir . DIRECTORY_SEPARATOR . 'sms.log',
            sprintf("[%s] GATEWAY=%s STATUS=%s MSG=%s\n", date('c'), $gateway, $carrierStatus ?: 'unknown', $message),
            FILE_APPEND
        );

        // Short subject helps some gateways; body is the SMS text
        $fromName = (string) ($settings['email_from_name'] ?: 'BarberShop');
        $fromAddr = (string) ($settings['email_from_address'] ?: (defined('REMINDER_EMAIL_FROM') ? REMINDER_EMAIL_FROM : 'noreply@localhost'));
        mail_send($gateway, 'Appt', $message, $fromAddr, $fromName);
        return 'gateway:' . $gateway;
    }

    // Optional Twilio fallback (not recommended / not required)
    $sid = defined('TWILIO_ACCOUNT_SID') ? TWILIO_ACCOUNT_SID : '';
    $token = defined('TWILIO_AUTH_TOKEN') ? TWILIO_AUTH_TOKEN : '';
    $from = defined('TWILIO_FROM_NUMBER') ? TWILIO_FROM_NUMBER : '';
    if ($sid !== '' && $token !== '' && $from !== '') {
        $to = (str_starts_with($phoneDigits, '1') || strlen($phoneDigits) > 10)
            ? '+' . ltrim($phoneDigits, '+')
            : '+1' . $phoneDigits;
        $url = 'https://api.twilio.com/2010-04-01/Accounts/' . rawurlencode($sid) . '/Messages.json';
        $post = http_build_query(['To' => $to, 'From' => $from, 'Body' => $message]);
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $post,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_USERPWD => $sid . ':' . $token,
            CURLOPT_TIMEOUT => 20,
        ]);
        $raw = curl_exec($ch);
        $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        $data = json_decode(is_string($raw) ? $raw : '', true);
        if ($code >= 200 && $code < 300 && !empty($data['sid'])) {
            return (string) $data['sid'];
        }
        throw new RuntimeException('Twilio error: ' . (is_string($raw) ? mb_substr($raw, 0, 200) : 'unknown'));
    }

    file_put_contents(
        $logDir . DIRECTORY_SEPARATOR . 'sms.log',
        sprintf("[%s] SKIP phone=%s — no carrier gateway on user (set carrier for email-to-SMS)\n", date('c'), $phoneDigits),
        FILE_APPEND
    );
    throw new RuntimeException('No mobile carrier selected for this client. Set carrier (e.g. Verizon → vtext.com) to send SMS via email gateway.', 100);
}
