<?php

namespace App\Controllers;

use App\Models\Control\TenantRecordModel;
use Config\Lookups;

/**
 * Manager-only. Every credential configured here is written to the
 * current tenant's tenant_settings table via the Settings service —
 * never to .env, never hardcoded — since each hotel client has their own
 * Hubtel and WhatsApp accounts. Secret-shaped keys (see App\Libraries\
 * Settings) are encrypted at rest and never redisplayed in plaintext;
 * leaving a secret field blank on save keeps the existing value.
 *
 * Hotel Information tab writes to a different database entirely — the
 * control DB's `tenants` row (hotel_name/logo_path/colors), not this
 * tenant's own DB — via TenantRecordModel, the one model in the app
 * that's allowed to touch the control DB from any context (see its own
 * doc comment). Previously only editable by Super Admin; this is real
 * tenant self-service, the same row, just a second write path into it.
 */
class SettingsController extends BaseController
{
    private const FIELDS = [
        'billing.tax_rate_percent',
        'billing.display_currency_code',
        'billing.display_exchange_rate',
        'billing.widget_deposit_percent',
        'loyalty.points_per_ghs_rate',
        'loyalty.tier_silver_threshold',
        'loyalty.tier_gold_threshold',
        'loyalty.redemption_value_per_point',
        'dining.silver_benefits',
        'dining.gold_benefits',
        'dining.platinum_benefits',
        'dining.membership_duration_months',
        'expenses.approval_threshold_amount',
        'hubtel.client_id',
        'hubtel.client_secret',
        'hubtel.merchant_account_number',
        'hubtel.sms.client_id',
        'hubtel.sms.client_secret',
        'hubtel.sms.sender_id',
        'whatsapp.phone_number_id',
        'whatsapp.access_token',
        'whatsapp.webhook_verify_token',
        'email.smtp_host',
        'email.smtp_port',
        'email.smtp_username',
        'email.smtp_password',
        'email.smtp_encryption',
        'email.from_address',
        'email.from_name',
        'messaging.booking_confirmation.channel',
        'messaging.pre_arrival.channel',
        'messaging.post_stay_feedback.channel',
        'messaging.birthday_offer.channel',
        'messaging.anniversary_offer.channel',
        'marketing.birthday_offer_message',
        'marketing.anniversary_offer_message',
        'ai.claude_api_token',
        'ai.claude_model',
        'ai.brand_voice',
        'ai.pre_arrival_upsell_days_ahead',
        'hotel.check_in_time',
        'hotel.check_out_time',
        'hotel.wifi_info',
        'hotel.guest_info_faq',
    ];

    /**
     * On/off flags for the Phase 5 AI feature set — see ClaudeService and
     * the Phase 5 AI brief's guardrails. Every guest-facing category
     * defaults off (both the feature flag and its own autosend flag);
     * staff-facing/internal features default on. Handled as a separate
     * checkbox loop from FIELDS above (which is a plain get/set-a-string
     * loop) since every one of these is a boolean, same convention
     * AiInsightsController::updateSettings() already uses for its own
     * rule-based toggles — this is deliberately a second, unrelated
     * settings surface (see ClaudeService's class doc comment), not a
     * rename of that page.
     */
    private const AI_TOGGLE_FIELDS = [
        'ai.kpi_narration_enabled'        => '1',
        'ai.maintenance_triage_enabled'   => '1',
        'ai.daily_briefing_enabled'       => '1',
        'ai.smart_guest_search_enabled'   => '1',
        'ai.staff_assistant_enabled'      => '1',
        'ai.menu_event_writer_enabled'    => '1',
        'ai.preference_inference_enabled' => '1',
        'ai.anomaly_flagging_enabled'     => '1',
        'ai.corporate_summary_enabled'    => '1',
        'ai.concierge_enabled'            => '0',
        'ai.pre_arrival_upsell_enabled'   => '0',
        'ai.post_checkout_thankyou_enabled' => '0',
        'ai.autosend_concierge_replies'   => '0',
        'ai.autosend_pre_arrival_upsell'  => '0',
        'ai.autosend_post_checkout_thankyou' => '0',
    ];

    public function index()
    {
        $settings = service('settings');
        $values   = [];

        foreach (self::FIELDS as $key) {
            $values[$key] = $settings->getGroupMasked(explode('.', $key)[0])[$key] ?? null;
        }

        $aiToggles = [];

        foreach (self::AI_TOGGLE_FIELDS as $key => $default) {
            $aiToggles[$key] = $settings->get($key, $default) === '1';
        }

        // Re-fetched fresh from the control DB rather than trusting the
        // request-cached tenantContext object, since this page is exactly
        // where that row gets edited — session()/tenantContext() would
        // show stale values right after a save until the next request.
        $tenantId = service('tenantContext')->getTenant()->id;
        $tenant   = (new TenantRecordModel())->find($tenantId);

        // Built from the CURRENT request's actual host, not app.baseURL (a
        // single static value, not tenant-aware) — so this reflects
        // whatever subdomain the manager is really browsing on, the same
        // host their own visitors would need to reach.
        $currentUri = current_url(true);

        return view('settings/index', [
            'pageTitle'      => 'Settings',
            'values'         => $values,
            'aiToggles'      => $aiToggles,
            'tenant'         => $tenant,
            'lookups'        => new Lookups(),
            'widgetEmbedUrl' => $currentUri->getScheme() . '://' . $currentUri->getAuthority() . '/widget/booking',
            'conciergeWebhookUrl' => $currentUri->getScheme() . '://' . $currentUri->getAuthority() . '/webhooks/whatsapp/concierge/' . $tenant->slug,
        ]);
    }

    /**
     * Hotel name, logo, and brand colors — self-service, writes straight
     * to the control DB's tenants row (see class doc comment). Logo
     * upload is optional per save (leaving the file input empty keeps
     * the current logo, same "blank keeps existing value" convention as
     * the secret fields below).
     */
    public function updateHotelInfo()
    {
        $tenantId = service('tenantContext')->getTenant()->id;
        $tenants  = new TenantRecordModel();

        $data = [
            'hotel_name'      => $this->request->getPost('hotel_name'),
            'primary_color'   => $this->request->getPost('primary_color') ?: null,
            'secondary_color' => $this->request->getPost('secondary_color') ?: null,
        ];

        $upload = handle_image_upload($this->request->getFile('logo'), 'logos');

        if ($upload['error'] !== null) {
            return redirect()->to('settings')->with('error', $upload['error']);
        }

        if ($upload['path'] !== null) {
            $data['logo_path'] = $upload['path'];
        }

        if (! $tenants->update($tenantId, $data)) {
            return $this->redirectWithModelErrors($tenants);
        }

        $this->logActivity('settings', 'Updated hotel information' . ($upload['path'] ? ' (new logo uploaded)' : '') . '.');

        return redirect()->to('settings')->with('success', 'Hotel information saved.');
    }

    public function update()
    {
        $settings = service('settings');

        foreach (self::FIELDS as $key) {
            $posted = $this->request->getPost(str_replace('.', '__', $key));

            // Blank leaves a sensitive value untouched (masked display means
            // the form never round-trips the real secret) but DOES clear a
            // non-sensitive value, since there's no masking reason to keep
            // a plain field's old value once the box is emptied.
            $isSecretField = str_contains($key, 'secret') || str_contains($key, 'token') || str_contains($key, 'password');

            if ($isSecretField && ($posted === null || $posted === '')) {
                continue;
            }

            $settings->set($key, $posted);
        }

        // Only touched when the AI Features tab's own form was the one
        // submitted — a hidden marker field on that form (see settings/
        // index.php) distinguishes it from Billing/Integrations posting
        // to this same action, since none of those forms carry every
        // other tab's fields (see the loop above: an absent field is
        // treated as "cleared" for anything non-secret). Without this
        // guard, saving Billing would silently blank every AI toggle.
        if ($this->request->getPost('ai_settings_submitted') !== null) {
            foreach (self::AI_TOGGLE_FIELDS as $key => $default) {
                $settings->set($key, $this->request->getPost(str_replace('.', '__', $key)) ? '1' : '0');
            }
        }

        $this->logActivity('settings', 'Updated integration settings.');

        return redirect()->to('settings')->with('success', 'Settings saved.');
    }

    /**
     * The three "Test Connection" actions below all operate on whatever
     * is currently SAVED (service('settings')->get(...)), not on
     * unsaved form input — save first, then test, same as every other
     * settings-then-verify flow. Each returns JSON for
     * window.maxyieldsPost() (see layouts/main.php) so the result shows
     * inline next to the button without a full page reload.
     */
    public function testSms()
    {
        $phone = trim((string) $this->request->getPost('test_phone'));

        if ($phone === '') {
            return $this->response->setJSON(['success' => false, 'error' => 'Enter a phone number to send the test SMS to.', 'csrf_hash' => csrf_hash()]);
        }

        try {
            $tenant = service('tenantContext')->getTenant();
            $result = service('hubtelService')->sendSms(
                $phone,
                "This is a test message from {$tenant->hotel_name} via MaxYields PMS. If you received this, your Hubtel SMS setup is working.",
            );
        } catch (\Throwable $e) {
            return $this->response->setJSON(['success' => false, 'error' => $e->getMessage(), 'csrf_hash' => csrf_hash()]);
        }

        $this->logActivity('settings', 'Sent a test SMS to ' . $phone . ' (' . ($result['success'] ? 'succeeded' : 'failed') . ').');

        return $this->response->setJSON([
            'success'    => $result['success'],
            'message'    => $result['success'] ? "Test SMS sent to {$phone}." : null,
            'error'      => $result['success'] ? null : 'Hubtel reported a failure: ' . $result['raw'],
            'csrf_hash'  => csrf_hash(),
        ]);
    }

    public function testEmail()
    {
        $email = trim((string) $this->request->getPost('test_email'));

        if (! filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return $this->response->setJSON(['success' => false, 'error' => 'Enter a valid email address to send the test to.', 'csrf_hash' => csrf_hash()]);
        }

        $tenant = service('tenantContext')->getTenant();
        $result = service('emailService')->send(
            $email,
            "MaxYields PMS — Test Email ({$tenant->hotel_name})",
            '<p>This is a test email from <strong>' . esc($tenant->hotel_name) . '</strong> via MaxYields PMS.</p><p>If you received this, your SMTP setup is working correctly.</p>',
        );

        $this->logActivity('settings', 'Sent a test email to ' . $email . ' (' . ($result['success'] ? 'succeeded' : 'failed') . ').');

        return $this->response->setJSON([
            'success'   => $result['success'],
            'message'   => $result['success'] ? "Test email sent to {$email}." : null,
            'error'     => $result['success'] ? null : $result['error'],
            'csrf_hash' => csrf_hash(),
        ]);
    }

    /** No real message send — see WhatsAppService::verifyCredentials()'s own doc comment for why. */
    public function testWhatsapp()
    {
        $result = service('whatsAppService')->verifyCredentials();

        $this->logActivity('settings', 'Tested the WhatsApp connection (' . ($result['success'] ? 'succeeded' : 'failed') . ').');

        return $this->response->setJSON([
            'success'   => $result['success'],
            'message'   => $result['success']
                ? 'Connected — ' . ($result['verifiedName'] ?: 'this number') . ' (' . ($result['displayPhoneNumber'] ?: 'no display number on file') . ').'
                : null,
            'error'     => $result['error'],
            'csrf_hash' => csrf_hash(),
        ]);
    }
}
