<?php

namespace App\Livewire\Settings;

use App\Models\ChannelIntegration;
use App\Traits\AuthorizesWorkspaceActions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Livewire\Component;

class ChannelSettings extends Component
{
    use AuthorizesWorkspaceActions;

    public ?string $configuringChannel = null;

    // WhatsApp fields
    public string $whatsappPhoneNumberId = '';
    public string $whatsappAccessToken = '';
    public string $whatsappVerifyToken = '';
    public string $whatsappAppSecret = '';

    // Twilio SMS fields
    public string $twilioSid = '';
    public string $twilioAuthToken = '';
    public string $twilioPhoneNumber = '';

    // Telegram fields
    public string $telegramBotToken = '';

    // Slack fields
    public string $slackClientId = '';
    public string $slackClientSecret = '';
    public string $slackSigningSecret = '';

    // Instagram fields — credentials map onto the Instagram Graph API
    // send path. Page ID + Page Access Token are mandatory for sendDm();
    // ig_user_id / ig_username are optional metadata used by the
    // Instagram addon's webhook router. app_secret enables X-Hub-Signature
    // verification on inbound webhooks (highly recommended for production).
    public string $instagramPageId = '';
    public string $instagramPageName = '';
    public string $instagramIgUserId = '';
    public string $instagramIgUsername = '';
    public string $instagramAccessToken = '';
    public string $instagramAppSecret = '';

    // Live Chat fields
    public string $chatWidgetColor = '#4F46E5';
    public string $chatWelcomeMessage = 'Hi there! How can we help you today?';
    public string $chatCompanyName = '';
    public string $chatPosition = 'bottom-right';
    public string $chatOfflineMessage = 'We are currently offline. Leave a message and we will get back to you.';
    public bool $chatAiAutoReply = true;

    // White-label + i18n controls for the widget. Both are persisted on the
    // `chat_widgets` table (see migration: show_branding boolean, language
    // varchar(8)). The widget JS at public/widget/v1.js reads them out of
    // the /api/widget/status payload (no JS file changes needed per
    // workspace; same single asset serves every customer).
    public bool $chatShowBranding = true;
    // Per-widget "Powered by ..." text. Empty → fall back to
    // WhiteLabel::brandName() at /api/widget/status time, which itself
    // strips the literal "MailTrixy" leak. This input lets the admin
    // type whatever brand text they want without having to also rename
    // their global Site Name (which is used everywhere else).
    public string $chatPoweredByText = '';
    public string $chatLanguage = ''; // empty = follow workspace default locale

    public string $saveStatus = '';

    public function configure(string $channel): void
    {
        $this->configuringChannel = $channel;
        $this->saveStatus = '';

        $workspaceId = auth()->user()->active_workspace_id;
        $integration = ChannelIntegration::where('workspace_id', $workspaceId)
            ->where('channel', $channel)
            ->first();

        if ($integration) {
            // The model's getCredentialsAttribute() accessor already decrypts
            $creds = $integration->credentials ?? [];

            match ($channel) {
                'whatsapp' => $this->fillWhatsApp($creds),
                'sms' => $this->fillSms($creds),
                'telegram' => $this->fillTelegram($creds),
                'slack' => $this->fillSlack($creds),
                'instagram' => $this->fillInstagram($creds),
                'chat' => $this->fillChat($creds, $integration->config ?? []),
                default => null,
            };
        }
    }

    public function closeConfig(): void
    {
        $this->configuringChannel = null;
        $this->saveStatus = '';
    }

    public function saveChannel(): void
    {
        if (! $this->authorizeWorkspaceAction('manage')) {
            return;
        }

        $workspaceId = auth()->user()->active_workspace_id;
        $channel = $this->configuringChannel;

        // Plan feature check — block if channel not available on current plan
        $featureMap = [
            'whatsapp' => 'whatsapp',
            'sms' => 'sms',
            'telegram' => 'telegram',
            'slack' => 'slack',
            'chat' => 'live_chat',
            // Instagram inherits the Meta business permission set, so we
            // gate it on the same plan feature as WhatsApp/Messenger. Plans
            // without `instagram` in plan_features fall through here, which
            // means the user sees a friendly upgrade prompt instead of a
            // half-saved integration.
            'instagram' => 'instagram',
        ];
        $featureKey = $featureMap[$channel] ?? null;
        if ($featureKey) {
            $workspace = auth()->user()->activeWorkspace;
            if (!app(\App\Services\PlanLimitService::class)->hasFeature($workspace, $featureKey)) {
                session()->flash('error', ucfirst($channel) . ' is not available on your current plan. Please upgrade.');
                return;
            }
        }

        // Validate required credential fields per channel
        $this->validateChannelCredentials($channel);

        $credentials = match ($channel) {
            'whatsapp' => [
                'phone_number_id' => $this->whatsappPhoneNumberId,
                'access_token' => $this->whatsappAccessToken,
                'verify_token' => $this->whatsappVerifyToken,
                'app_secret' => $this->whatsappAppSecret,
            ],
            'sms' => [
                'sid' => $this->twilioSid,
                'auth_token' => $this->twilioAuthToken,
                'phone_number' => $this->twilioPhoneNumber,
            ],
            'telegram' => [
                'bot_token' => $this->telegramBotToken,
            ],
            'slack' => [
                'client_id' => $this->slackClientId,
                'client_secret' => $this->slackClientSecret,
                'signing_secret' => $this->slackSigningSecret,
            ],
            // Instagram credential shape mirrors what the Instagram addon's
            // InstagramService::sendDm() and webhook handler read off the
            // ChannelIntegration row. page_id + access_token are mandatory
            // for outbound sends; ig_user_id is used to route inbound webhooks
            // to the right workspace.
            'instagram' => [
                'page_id' => $this->instagramPageId,
                'page_name' => $this->instagramPageName,
                'ig_user_id' => $this->instagramIgUserId,
                'ig_username' => $this->instagramIgUsername,
                'access_token' => $this->instagramAccessToken,
                // Meta App Secret — used to verify the HMAC-SHA256 signature
                // (X-Hub-Signature-256) on every inbound webhook. Without it
                // we accept unsigned events for back-compat, but a populated
                // value flips webhook verification into strict mode.
                'app_secret' => $this->instagramAppSecret,
                // Per-workspace verify token for Meta's webhook subscription
                // handshake. Generated once on first save and kept stable so
                // re-saving credentials doesn't break the Meta subscription.
                'verify_token' => $this->loadOrCreateInstagramVerifyToken($workspaceId),
            ],
            'chat' => [
                'widget_color' => $this->chatWidgetColor,
                'welcome_message' => $this->chatWelcomeMessage,
                'company_name' => $this->chatCompanyName,
                'position' => $this->chatPosition,
                'offline_message' => $this->chatOfflineMessage,
            ],
            default => [],
        };

        $config = $channel === 'chat' ? ['ai_auto_reply' => $this->chatAiAutoReply] : [];

        $hasCredentials = collect($credentials)->filter(fn ($v) => ! empty($v))->isNotEmpty();

        // Top-level columns that the rest of the app queries directly
        // (campaign editor's "From number" dropdown reads phone_number off
        // the channel_integrations row, not the encrypted credentials JSON).
        // Mirror SMS phone + WhatsApp phone-number-id into the dedicated columns.
        $topLevel = [
            'credentials' => $credentials,
            'config' => $config,
            'status' => $hasCredentials ? 'active' : 'inactive',
            'ai_auto_reply' => $channel === 'chat' ? $this->chatAiAutoReply : false,
        ];
        if ($channel === 'sms') {
            $topLevel['phone_number'] = $this->twilioPhoneNumber ?: null;
            $topLevel['account_name'] = $this->twilioSid ? 'Twilio' : null;
        } elseif ($channel === 'whatsapp') {
            $topLevel['phone_number'] = $this->whatsappPhoneNumberId ?: null;
        }

        // Pass plain credentials — the model's setCredentialsAttribute() mutator encrypts them
        ChannelIntegration::updateOrCreate(
            ['workspace_id' => $workspaceId, 'channel' => $channel],
            $topLevel
        );

        // Ensure a chat_widgets row exists so the embed script has a public_id token
        if ($channel === 'chat') {
            $widgetData = [
                'primary_color' => $this->chatWidgetColor,
                'welcome_message' => $this->chatWelcomeMessage,
                'company_name' => $this->chatCompanyName ?: null,
                'position' => $this->chatPosition,
                'offline_message' => $this->chatOfflineMessage,
                'ai_auto_reply' => $this->chatAiAutoReply,
                // New v1.4 widget controls. Stored on chat_widgets so they
                // ride the same row that /api/widget/status reads on every
                // visitor's page load — no settings cache to invalidate.
                'show_branding' => $this->chatShowBranding,
                // Trim + null-out empty strings so the resolution
                // chain at /api/widget/status sees a real value to
                // use, or cleanly falls through to BrandingHelper.
                'powered_by_text' => trim($this->chatPoweredByText) !== ''
                    ? trim($this->chatPoweredByText)
                    : null,
                'language' => $this->chatLanguage ?: null,
                'updated_at' => now(),
            ];

            $exists = DB::table('chat_widgets')->where('workspace_id', $workspaceId)->exists();
            if (! $exists) {
                DB::table('chat_widgets')->insert(array_merge($widgetData, [
                    'workspace_id' => $workspaceId,
                    'public_id' => Str::random(32),
                    'created_at' => now(),
                ]));
            } else {
                DB::table('chat_widgets')->where('workspace_id', $workspaceId)->update($widgetData);
            }
        }

        // Auto-register webhooks if on HTTPS
        if ($hasCredentials && str_starts_with(config('app.url'), 'https')) {
            try {
                match ($channel) {
                    'telegram' => (function () use ($credentials) {
                        $service = new \App\Services\Channels\TelegramService($credentials['bot_token']);
                        $service->setWebhook(url('/api/webhooks/telegram'));
                        \Log::info("ChannelSettings: Telegram webhook auto-registered");
                    })(),
                    'whatsapp' => \Log::info("ChannelSettings: WhatsApp webhook must be set manually in Meta Dashboard: " . url('/api/webhooks/whatsapp')),
                    'sms' => \Log::info("ChannelSettings: Twilio webhook must be set manually in Twilio Console: " . url('/api/webhooks/twilio/incoming')),
                    'slack' => \Log::info("ChannelSettings: Slack webhook must be set manually in Slack App settings: " . url('/api/webhooks/slack/events')),
                    default => null,
                };
            } catch (\Throwable $e) {
                \Log::warning("ChannelSettings: Auto-webhook failed for {$channel}: {$e->getMessage()}");
            }
        }

        $this->saveStatus = 'saved';
        session()->flash('success', ucfirst($channel) . ' channel settings saved.');
    }

    public function disconnect(string $channel): void
    {
        if (! $this->authorizeWorkspaceAction('manage')) {
            return;
        }

        $workspaceId = auth()->user()->active_workspace_id;
        ChannelIntegration::where('workspace_id', $workspaceId)
            ->where('channel', $channel)
            ->update(['status' => 'inactive']);

        session()->flash('success', ucfirst($channel) . ' channel disconnected.');
    }

    /**
     * Validate that required credentials are present for the given channel.
     */
    protected function validateChannelCredentials(string $channel): void
    {
        $rules = match ($channel) {
            'whatsapp' => [
                'whatsappPhoneNumberId' => 'required|string|max:255',
                'whatsappAccessToken' => 'required|string|max:1000',
                'whatsappVerifyToken' => 'required|string|max:255',
                'whatsappAppSecret' => 'required|string|max:255',
            ],
            'sms' => [
                'twilioSid' => 'required|string|max:255',
                'twilioAuthToken' => 'required|string|max:255',
                'twilioPhoneNumber' => 'required|string|max:30',
            ],
            'telegram' => [
                'telegramBotToken' => 'required|string|max:255',
            ],
            'slack' => [
                'slackClientId' => 'required|string|max:255',
                'slackClientSecret' => 'required|string|max:255',
                'slackSigningSecret' => 'required|string|max:255',
            ],
            'instagram' => [
                'instagramPageId' => 'required|string|max:64',
                'instagramIgUserId' => 'required|string|max:64',
                'instagramAccessToken' => 'required|string|max:1000',
                'instagramPageName' => 'nullable|string|max:255',
                'instagramIgUsername' => 'nullable|string|max:64',
                'instagramAppSecret' => 'nullable|string|max:255',
            ],
            'chat' => [
                'chatWidgetColor' => 'required|string|max:20',
                'chatWelcomeMessage' => 'required|string|max:500',
                'chatCompanyName' => 'nullable|string|max:100',
                'chatPosition' => 'required|string|in:bottom-right,bottom-left',
                'chatOfflineMessage' => 'nullable|string|max:500',
                'chatShowBranding' => 'boolean',
                // Locale code or empty string. We use a permissive regex
                // here (matches en, en-US, en_us, etc.) instead of an
                // Rule::in() against the languages table — the table can
                // change and we don't want a stale validation failure.
                'chatLanguage' => 'nullable|string|max:8|regex:/^[a-zA-Z][a-zA-Z0-9_-]{0,7}$/',
            ],
            default => [],
        };

        if ($rules) {
            $this->validate($rules);
        }
    }

    protected function fillWhatsApp(array $creds): void
    {
        $this->whatsappPhoneNumberId = $creds['phone_number_id'] ?? '';
        $this->whatsappAccessToken = $creds['access_token'] ?? '';
        $this->whatsappVerifyToken = $creds['verify_token'] ?? '';
        $this->whatsappAppSecret = $creds['app_secret'] ?? '';
    }

    protected function fillSms(array $creds): void
    {
        $this->twilioSid = $creds['sid'] ?? '';
        $this->twilioAuthToken = $creds['auth_token'] ?? '';
        $this->twilioPhoneNumber = $creds['phone_number'] ?? '';
    }

    protected function fillTelegram(array $creds): void
    {
        $this->telegramBotToken = $creds['bot_token'] ?? '';
    }

    protected function fillSlack(array $creds): void
    {
        $this->slackClientId = $creds['client_id'] ?? '';
        $this->slackClientSecret = $creds['client_secret'] ?? '';
        $this->slackSigningSecret = $creds['signing_secret'] ?? '';
    }

    protected function fillInstagram(array $creds): void
    {
        $this->instagramPageId = $creds['page_id'] ?? '';
        $this->instagramPageName = $creds['page_name'] ?? '';
        $this->instagramIgUserId = $creds['ig_user_id'] ?? '';
        $this->instagramIgUsername = $creds['ig_username'] ?? '';
        $this->instagramAccessToken = $creds['access_token'] ?? '';
        $this->instagramAppSecret = $creds['app_secret'] ?? '';
    }

    /**
     * Return the existing verify_token for this workspace's Instagram row,
     * or generate a new one if the row doesn't exist yet. Stays stable
     * across re-saves so the Meta webhook subscription doesn't break each
     * time the admin tweaks credentials.
     */
    protected function loadOrCreateInstagramVerifyToken(int $workspaceId): string
    {
        $existing = ChannelIntegration::where('workspace_id', $workspaceId)
            ->where('channel', 'instagram')
            ->first();
        if ($existing) {
            $creds = $existing->credentials ?? [];
            if (!empty($creds['verify_token'])) {
                return $creds['verify_token'];
            }
        }
        return Str::random(40);
    }

    protected function fillChat(array $creds, array $config): void
    {
        $this->chatWidgetColor = $creds['widget_color'] ?? '#4F46E5';
        $this->chatWelcomeMessage = $creds['welcome_message'] ?? 'Hi there! How can we help you today?';
        $this->chatCompanyName = $creds['company_name'] ?? '';
        $this->chatPosition = $creds['position'] ?? 'bottom-right';
        $this->chatOfflineMessage = $creds['offline_message'] ?? 'We are currently offline. Leave a message and we will get back to you.';
        $this->chatAiAutoReply = $config['ai_auto_reply'] ?? true;

        // Pull the per-widget controls from the chat_widgets row. The
        // top-level `creds` map (from channel_integrations) doesn't include
        // these — they live on chat_widgets because that row is what
        // /api/widget/status reads and they need to be queryable by the
        // public widget endpoint without a join.
        //
        // The row CAN be null on first-ever configure: the customer just
        // installed the addon and is opening this modal before ever
        // saving, so no chat_widgets row exists yet. We default explicitly
        // instead of relying on `null->property ?? default` semantics —
        // even though PHP 8 happens to coerce that to null, the pattern
        // raises a deprecation notice in PHP 8.2+ and a hard error in 9.
        // Defensive column-existence check on powered_by_text so the
        // modal still opens cleanly on a customer install where the
        // 2026_06_01 migration hasn't been run yet — the field just
        // stays empty and the existing brand-name fallback kicks in
        // at widget render time.
        $hasPoweredByCol = Schema::hasColumn('chat_widgets', 'powered_by_text');
        $cols = $hasPoweredByCol
            ? ['show_branding', 'language', 'powered_by_text']
            : ['show_branding', 'language'];

        $row = DB::table('chat_widgets')
            ->where('workspace_id', auth()->user()->active_workspace_id)
            ->first($cols);
        if ($row) {
            $this->chatShowBranding = (bool) $row->show_branding;
            $this->chatLanguage = (string) ($row->language ?? '');
            $this->chatPoweredByText = $hasPoweredByCol
                ? (string) ($row->powered_by_text ?? '')
                : '';
        } else {
            $this->chatShowBranding = true;
            $this->chatLanguage = '';
            $this->chatPoweredByText = '';
        }
    }

    public function render()
    {
        $workspaceId = auth()->user()->active_workspace_id;
        $integrations = ChannelIntegration::where('workspace_id', $workspaceId)
            ->get()
            ->keyBy('channel');

        // Get the chat widget public_id for the embed code
        $chatWidgetPublicId = DB::table('chat_widgets')
            ->where('workspace_id', $workspaceId)
            ->value('public_id');

        // Languages registered in admin → Languages. Used to build the
        // chat-widget language dropdown. Falls back to a single English row
        // on fresh installs so the dropdown never renders blank.
        $availableLanguages = \App\Models\Language::where('is_active', true)
            ->orderBy('sort_order')->orderBy('name')
            ->get(['code', 'name', 'native_name']);
        if ($availableLanguages->isEmpty()) {
            $availableLanguages = collect([(object) ['code' => 'en', 'name' => 'English', 'native_name' => null]]);
        }

        return view('livewire.settings.channel-settings', [
            'integrations' => $integrations,
            'chatWidgetPublicId' => $chatWidgetPublicId,
            'availableLanguages' => $availableLanguages,
        ]);
    }
}
