<?php

namespace Instinctor\Core;

/**
 * One-way transactional SMS through the platform relay (Telnyx).
 * Shared by bookings + form inquiries + any other owner notification.
 * Standalone sites authenticate by site_url; the platform sends from its
 * shared number. Consent-gated (auto-true on standalone).
 */
class Sms
{
    /** Send an SMS. Returns true on relay success, false otherwise (never throws). */
    public static function send(string $to, string $message): bool
    {
        if (!PlatformConsent::granted()) {
            return false;
        }
        // Free-publish gate: on the managed multi-tenant app, texting is a Pro feature.
        // Free tenants (owner set, not Pro) never send SMS; single-tenant installs and
        // the vendor (no owner context) are unaffected. Platform flips the Pro flag.
        if (!TenantPlan::smsAllowed()) {
            return false;
        }
        return self::dispatch($to, $message);
    }

    /**
     * Text an OWNER that a lead just landed. The only SMS a free tenant ever gets.
     *
     * SMS is the Pro driver an owner cannot imagine, only feel, so each free owner gets
     * their first few real lead alerts (TenantPlan::FREE_CUSTOMERS) and then silence.
     * The silence is the pitch: the fourth lead arrives by email saying what they just
     * stopped getting.
     *
     * Strictly owner-inbound. Anything a tenant sends a CUSTOMER still goes through send()
     * and stays fully Pro, so this cannot be turned into a free SMS gateway: the only
     * number it ever reaches is the owner's own notify number.
     *
     * The counter burns only on a relay success, so a bad number costs them nothing.
     *
     * $conversationId ties the text to a chat so the owner's reply routes back into it.
     * Zero for leads that have no thread to return to (forms, bookings, quotes).
     */
    public static function sendLeadAlert(string $to, string $message, int $conversationId = 0): bool
    {
        if (!PlatformConsent::granted()) {
            return false;
        }
        if (!TenantPlan::leadAlertAllowed()) {
            return false;
        }
        $message .= self::freePlanTail();
        $ok = self::dispatch($to, $message, $conversationId);
        if ($ok) {
            TenantPlan::recordLeadAlert();
        }
        return $ok;
    }

    /**
     * The "you are on the free plan" tail, or '' when it does not apply.
     *
     * Tells a free TENANT where they stand, in the text itself: silence after the
     * last alert is the pitch, but silence they did not see coming reads as a broken
     * site (Sergei 2026-07-26). One place, so form, booking, quote and chat alerts
     * all say the same thing.
     *
     * A null owner is NOT a free tenant. It is instinctor.com itself, a standalone
     * client install, or DIY WordPress, and none of them are on our plans. isPro()
     * returns false for null, so the old `if (!isPro())` appended the pitch to the
     * vendor's own lead alerts: Sergei got "Customer 1 of 10 on the free plan" for a
     * genuine prospect on instinctor.com, with the counter frozen at 1 because
     * leadAlertsUsed() also returns 0 for null. Require a real tenant (2026-08-13).
     */
    private static function freePlanTail(): string
    {
        $ownerId = (int) \Instinctor\Core\Tenant::currentOwnerId();
        if ($ownerId <= 0) return '';
        if (TenantPlan::isPro($ownerId)) return '';

        $cap = (int) TenantPlan::FREE_CUSTOMERS;
        $n   = min((int) TenantPlan::leadAlertsUsed($ownerId) + 1, $cap);
        return "\n\n" . ($n >= $cap
            ? 'That was the last of your ' . $cap . ' free customers. New ones still appear in your dashboard, and Pro texts you and your client every time: https://instinctor.com/dashboard/pro'
            : 'Customer ' . $n . ' of ' . $cap . ' on the free plan. Pro texts you and your client every time: https://instinctor.com/dashboard/pro');
    }

    /** The relay transport. Both doors above share it so there is one auth shape and one
     *  error path; they differ only in which gate they ask. */
    private static function dispatch(string $to, string $message, int $conversationId = 0): bool
    {
        $to = self::normalize($to);
        if ($to === '') {
            return false;
        }
        $siteUrl = function_exists('home_url') ? home_url() : (defined('INSTINCTOR_STANDALONE_URL') ? INSTINCTOR_STANDALONE_URL : '');
        // Channel by destination: the Telnyx long code only reaches +1, so every
        // alert to an EU-numbered owner died with a 409 for months (found via
        // Klevez, +48, 2026-08-11). Everywhere WhatsApp-normal gets the approved
        // template instead; the profile whitelist (US/CA/PL) still has the last word.
        $channel = str_starts_with($to, '+1') ? 'sms' : 'whatsapp';
        // conversation_id is what the relay needs to write p_relay_sms_threads, the
        // lookup that routes the owner's reply back into the chat. Without it every
        // alert still arrived and every reply to it went nowhere, which is worse than
        // no alert: the text ends "Reply to step in" (bathdone.com, 2026-08-13).
        $body = [
            'site_url'    => $siteUrl,
            'license_key' => (string) Config::get('instinctor_license_key', ''),
            'to'          => $to,
            'message'     => $message,
            'channel'     => $channel,
        ];
        if ($conversationId > 0) {
            $body['conversation_id'] = $conversationId;
        }
        $response = wp_remote_post('https://platform.instinctor.com/api/relay/sms', [
            'body'    => $body,
            'timeout' => 15,
        ]);
        if (is_wp_error($response)) {
            // phpcs:disable WordPress.PHP.DevelopmentFunctions -- server-side error capture / one-time migration logging, not debug output.
            error_log('[Sms] relay error: ' . $response->get_error_message());
            // phpcs:enable WordPress.PHP.DevelopmentFunctions
            return false;
        }
        $code = wp_remote_retrieve_response_code($response);
        if ($code < 200 || $code >= 300) {
            // phpcs:disable WordPress.PHP.DevelopmentFunctions -- server-side error capture / one-time migration logging, not debug output.
            error_log('[Sms] relay HTTP ' . $code . ': ' . substr((string) wp_remote_retrieve_body($response), 0, 300));
            // phpcs:enable WordPress.PHP.DevelopmentFunctions
            return false;
        }
        return true;
    }

    /** Coerce any entered phone format to E.164 (+1...). Returns '' if implausible. */
    public static function normalize(string $raw): string
    {
        $raw     = trim($raw);
        $hasPlus = str_starts_with($raw, '+');
        $digits  = preg_replace('/\D+/', '', $raw);
        if ($hasPlus) {
            return strlen($digits) >= 8 ? '+' . $digits : '';
        }
        if (strlen($digits) === 10) {
            return '+1' . $digits;
        }
        if (strlen($digits) === 11 && $digits[0] === '1') {
            return '+' . $digits;
        }
        return strlen($digits) >= 8 ? '+' . $digits : '';
    }
}
