<?php

namespace App\Http\Controllers;

use App\Models\Contact;
use App\Models\MessengerChannel;
use App\Models\MessengerConversation;
use App\Models\MessengerMessage;
use App\Models\MessengerSetting;
use App\Models\User;
use App\Support\AccessControl;
use App\Support\CrmModuleManager;
use App\Support\MessengerAccess;
use App\Support\SectionAccessManager;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\View\View;

class MessengerController extends Controller
{
    public function index(Request $request, SectionAccessManager $sectionAccessManager): View
    {
        $this->authorize('viewAny', MessengerConversation::class);

        $user = $request->user();
        $isElevated = AccessControl::isElevated($user);
        $settings = $this->settingsOrDefault();
        $canAcceptIncoming = MessengerAccess::canAcceptIncoming($user, $settings);

        $search = trim((string) $request->input('q', ''));
        $status = trim((string) $request->input('status', ''));
        $provider = trim((string) $request->input('provider', ''));
        if ($provider !== '') {
            $provider = MessengerAccess::normalizeProvider($provider);
        }
        $channelId = $request->integer('channel_id');
        $selectedUserId = $isElevated ? $request->integer('user_id') : (int) $user->id;
        $dateFrom = trim((string) $request->input('date_from', ''));
        $dateTo = trim((string) $request->input('date_to', ''));

        $channels = MessengerChannel::query()
            ->orderBy('name')
            ->get();

        $providerOptions = array_keys(MessengerAccess::providerOptions());

        $conversationsQuery = MessengerConversation::query()
            ->with(['channel', 'user', 'contact', 'lastMessage'])
            ->withCount('messages')
            ->when($search !== '', function ($query) use ($search): void {
                $query->where(function ($sub) use ($search): void {
                    $sub->where('contact_name', 'like', "%{$search}%")
                        ->orWhere('contact_handle', 'like', "%{$search}%")
                        ->orWhere('external_id', 'like', "%{$search}%")
                        ->orWhereHas('channel', fn ($channel) => $channel->where('name', 'like', "%{$search}%"));
                });
            })
            ->when($status !== '' && array_key_exists($status, $this->conversationStatusOptions()), fn ($query) => $query->where('status', $status))
            ->when($provider !== '', fn ($query) => $query->where('provider', $provider))
            ->when($channelId > 0, fn ($query) => $query->where('channel_id', $channelId))
            ->when($dateFrom !== '', fn ($query) => $query->whereDate('last_message_at', '>=', $dateFrom))
            ->when($dateTo !== '', fn ($query) => $query->whereDate('last_message_at', '<=', $dateTo));

        if (! $isElevated) {
            if ($canAcceptIncoming) {
                $conversationsQuery->where(function ($query) use ($user): void {
                    $query->where('user_id', $user->id)
                        ->orWhereNull('user_id');
                });
            } else {
                $conversationsQuery->where('user_id', $user->id);
            }
        } elseif ($selectedUserId > 0) {
            $conversationsQuery->where('user_id', $selectedUserId);
        }

        $conversations = $conversationsQuery
            ->orderByDesc('last_message_at')
            ->orderByDesc('id')
            ->paginate(20)
            ->withQueryString();

        $activeConversationId = $request->integer('conversation_id');
        $activeConversation = null;
        $conversationMessages = collect();
        $canEditConversationContact = false;
        if ($activeConversationId > 0) {
            $activeConversation = MessengerConversation::query()
                ->with(['channel', 'user', 'contact.owner'])
                ->find($activeConversationId);

            if ($activeConversation && $request->user()->cannot('view', $activeConversation)) {
                $activeConversation = null;
            }

            if ($activeConversation) {
                $canEditConversationContact = $this->canEditConversationContact($user, $activeConversation);

                $conversationMessages = MessengerMessage::query()
                    ->where('conversation_id', $activeConversation->id)
                    ->with('user:id,name,email')
                    ->orderBy('sent_at')
                    ->orderBy('id')
                    ->limit(100)
                    ->get();
            }
        }

        $canManageSettings = AccessControl::allows($user, 'messengers', 'update');
        $canCreateChannel = AccessControl::allows($user, 'messengers', 'create');
        $canCreateConversation = AccessControl::allows($user, 'messengers', 'create');
        $canCreateMessage = AccessControl::allows($user, 'messengers', 'create');
        $canAssignConversation = $canAcceptIncoming || $isElevated;

        $canManageSectionAccess = $sectionAccessManager->canManage($user, 'messengers');
        $sectionAccessUsers = $canManageSectionAccess
            ? User::query()->orderBy('name')->get(['id', 'name', 'email', 'role', 'permissions'])
            : collect();

        $userOptions = $isElevated
            ? User::query()->orderBy('name')->get(['id', 'name', 'email', 'job_title'])
            : User::query()->whereKey($user->id)->get(['id', 'name', 'email', 'job_title']);

        return view('messengers.index', [
            'settings' => $settings,
            'channels' => $channels,
            'providerOptions' => $providerOptions,
            'providerDictionary' => MessengerAccess::providerOptions(),
            'conversations' => $conversations,
            'activeConversation' => $activeConversation,
            'conversationMessages' => $conversationMessages,
            'search' => $search,
            'status' => $status,
            'provider' => $provider,
            'channelId' => $channelId,
            'selectedUserId' => $selectedUserId,
            'dateFrom' => $dateFrom,
            'dateTo' => $dateTo,
            'conversationStatusOptions' => $this->conversationStatusOptions(),
            'messageStatusOptions' => $this->messageStatusOptions(),
            'directionOptions' => $this->directionOptions(),
            'userOptions' => $userOptions,
            'canManageSettings' => $canManageSettings,
            'canCreateChannel' => $canCreateChannel,
            'canCreateConversation' => $canCreateConversation,
            'canCreateMessage' => $canCreateMessage,
            'canAcceptIncoming' => $canAcceptIncoming,
            'canAssignConversation' => $canAssignConversation,
            'canEditConversationContact' => $canEditConversationContact,
            'allowedOperatorIds' => MessengerAccess::allowedOperatorIds($settings),
            'newContactActionOptions' => MessengerAccess::newContactActionOptions(),
            'canManageSectionAccess' => $canManageSectionAccess,
            'sectionAccessUsers' => $sectionAccessUsers,
            'webhookUrl' => route('messengers.webhook'),
        ]);
    }

    public function updateSettings(Request $request): RedirectResponse
    {
        $user = $request->user();
        abort_unless(AccessControl::allows($user, 'messengers', 'update'), 403);

        $providerValues = array_keys(MessengerAccess::providerOptions());
        $newContactActionValues = array_keys(MessengerAccess::newContactActionOptions());
        $validated = $request->validate([
            'provider' => ['required', Rule::in($providerValues)],
            'is_active' => ['nullable', 'boolean'],
            'api_base_url' => ['nullable', 'string', 'max:255'],
            'api_key' => ['nullable', 'string', 'max:255'],
            'api_secret' => ['nullable', 'string', 'max:255'],
            'account_id' => ['nullable', 'string', 'max:255'],
            'allowed_operator_ids' => ['nullable', 'array'],
            'allowed_operator_ids.*' => ['integer', 'exists:users,id'],
            'auto_create_contact' => ['nullable', 'boolean'],
            'new_contact_action' => ['required', Rule::in($newContactActionValues)],
            'new_contact_action_user_id' => ['nullable', 'integer', 'exists:users,id'],
            'webhook_secret' => ['nullable', 'string', 'max:255'],
        ]);

        $payload = [
            'provider' => MessengerAccess::normalizeProvider($validated['provider']),
            'is_active' => $request->boolean('is_active'),
            'api_base_url' => $this->nullableText($validated['api_base_url'] ?? null),
            'api_key' => $this->nullableText($validated['api_key'] ?? null),
            'api_secret' => $this->nullableText($validated['api_secret'] ?? null),
            'account_id' => $this->nullableText($validated['account_id'] ?? null),
            'allowed_operator_ids' => MessengerAccess::sanitizeOperatorIds($validated['allowed_operator_ids'] ?? null),
            'auto_create_contact' => $request->boolean('auto_create_contact', true),
            'new_contact_action' => (string) $validated['new_contact_action'],
            'new_contact_action_user_id' => $validated['new_contact_action_user_id'] ?? null,
            'webhook_secret' => $this->nullableText($validated['webhook_secret'] ?? null),
        ];

        if (($payload['webhook_secret'] ?? '') === '') {
            $payload['webhook_secret'] = MessengerSetting::query()->value('webhook_secret') ?: Str::random(40);
        }

        $settings = MessengerSetting::query()->first() ?? new MessengerSetting();
        $settings->fill($payload)->save();

        return back()->with('success', __('Messenger settings have been updated.'));
    }

    public function storeChannel(Request $request, CrmModuleManager $moduleManager): RedirectResponse
    {
        $this->authorize('create', MessengerChannel::class);

        $providerValues = array_keys(MessengerAccess::providerOptions());
        $providerInput = MessengerAccess::normalizeProvider($request->input('provider', ''));

        $validated = $request->validate([
            'provider' => ['required', Rule::in($providerValues)],
            'external_id' => [
                'nullable',
                'string',
                'max:255',
                Rule::unique('messenger_channels', 'external_id')->where(fn ($query) => $query->where('provider', $providerInput)),
            ],
            'name' => ['required', 'string', 'max:255'],
            'handle' => ['nullable', 'string', 'max:120'],
            'status' => ['required', Rule::in(array_keys($this->channelStatusOptions()))],
            'is_default' => ['nullable', 'boolean'],
        ]);

        $payload = [
            'provider' => MessengerAccess::normalizeProvider($validated['provider']),
            'external_id' => $this->nullableText($validated['external_id'] ?? null),
            'name' => trim((string) $validated['name']),
            'handle' => $this->nullableText($validated['handle'] ?? null),
            'status' => (string) $validated['status'],
            'is_default' => $request->boolean('is_default'),
        ];

        $payload = $moduleManager->applyPayloadHooks('messengers.channels.store', $payload, [
            'hook' => 'messengers.channels.store',
            'user_id' => $request->user()->id,
        ], array_keys($payload));

        if ($payload['is_default'] ?? false) {
            MessengerChannel::query()->update(['is_default' => false]);
        }

        MessengerChannel::query()->create($payload);

        return redirect()
            ->route('messengers.index')
            ->with('success', __('Channel has been created.'));
    }

    public function updateChannel(Request $request, MessengerChannel $channel, CrmModuleManager $moduleManager): RedirectResponse
    {
        $this->authorize('update', $channel);

        $providerValues = array_keys(MessengerAccess::providerOptions());
        $providerInput = MessengerAccess::normalizeProvider($request->input('provider', $channel->provider));
        $validated = $request->validate([
            'provider' => ['required', Rule::in($providerValues)],
            'external_id' => [
                'nullable',
                'string',
                'max:255',
                Rule::unique('messenger_channels', 'external_id')
                    ->where(fn ($query) => $query->where('provider', $providerInput))
                    ->ignore($channel->id),
            ],
            'name' => ['required', 'string', 'max:255'],
            'handle' => ['nullable', 'string', 'max:120'],
            'status' => ['required', Rule::in(array_keys($this->channelStatusOptions()))],
            'is_default' => ['nullable', 'boolean'],
        ]);

        $payload = [
            'provider' => MessengerAccess::normalizeProvider($validated['provider']),
            'external_id' => $this->nullableText($validated['external_id'] ?? null),
            'name' => trim((string) $validated['name']),
            'handle' => $this->nullableText($validated['handle'] ?? null),
            'status' => (string) $validated['status'],
            'is_default' => $request->boolean('is_default'),
        ];

        $payload = $moduleManager->applyPayloadHooks('messengers.channels.update', $payload, [
            'hook' => 'messengers.channels.update',
            'user_id' => $request->user()->id,
            'channel_id' => $channel->id,
        ], array_keys($payload));

        if ($payload['is_default'] ?? false) {
            MessengerChannel::query()->whereKeyNot($channel->id)->update(['is_default' => false]);
        }

        $channel->update($payload);

        return redirect()
            ->route('messengers.index')
            ->with('success', __('Channel has been updated.'));
    }

    public function storeConversation(Request $request, CrmModuleManager $moduleManager): RedirectResponse
    {
        $this->authorize('create', MessengerConversation::class);

        $validated = $request->validate([
            'channel_id' => ['required', 'exists:messenger_channels,id'],
            'external_id' => ['nullable', 'string', 'max:255'],
            'contact_name' => ['nullable', 'string', 'max:255'],
            'contact_handle' => ['nullable', 'string', 'max:120'],
            'status' => ['required', Rule::in(array_keys($this->conversationStatusOptions()))],
            'user_id' => ['nullable', 'exists:users,id'],
        ]);

        $channel = MessengerChannel::query()->findOrFail($validated['channel_id']);
        $provider = $channel->provider;
        $settings = MessengerSetting::query()->first();

        $payload = [
            'channel_id' => $channel->id,
            'provider' => $provider,
            'external_id' => $this->nullableText($validated['external_id'] ?? null),
            'contact_name' => $this->nullableText($validated['contact_name'] ?? null),
            'contact_handle' => $this->nullableText($validated['contact_handle'] ?? null),
            'status' => (string) $validated['status'],
            'user_id' => $validated['user_id'] ?? null,
        ];

        if (! AccessControl::isElevated($request->user())) {
            $payload['user_id'] = MessengerAccess::canAcceptIncoming($request->user(), $settings)
                ? $request->user()->id
                : null;
        }

        if ($payload['external_id']) {
            $exists = MessengerConversation::query()
                ->where('provider', $provider)
                ->where('external_id', $payload['external_id'])
                ->exists();

            if ($exists) {
                return back()
                    ->withErrors(['external_id' => __('Conversation external ID must be unique for this provider.')])
                    ->withInput();
            }
        }

        $payload = $moduleManager->applyPayloadHooks('messengers.conversations.store', $payload, [
            'hook' => 'messengers.conversations.store',
            'user_id' => $request->user()->id,
        ], array_keys($payload));

        $conversation = MessengerConversation::query()->create($payload);

        return redirect()
            ->route('messengers.index', ['conversation_id' => $conversation->id])
            ->with('success', __('Conversation has been created.'));
    }

    public function acceptConversation(Request $request, MessengerConversation $conversation): RedirectResponse
    {
        $this->authorize('update', $conversation);

        $actor = $request->user();
        $settings = MessengerSetting::query()->first();
        $isElevated = AccessControl::isElevated($actor);

        $validated = $request->validate([
            'user_id' => ['nullable', 'integer', 'exists:users,id'],
        ]);

        if (! $isElevated) {
            abort_unless(MessengerAccess::canAcceptIncoming($actor, $settings), 403);
            if ($conversation->user_id !== null && (int) $conversation->user_id !== (int) $actor->id) {
                abort(403);
            }
        }

        $targetUserId = $isElevated
            ? (int) ($validated['user_id'] ?? $actor->id)
            : (int) $actor->id;

        $conversation->update([
            'user_id' => $targetUserId,
            'status' => $conversation->status === 'closed' ? 'open' : $conversation->status,
        ]);

        return redirect()
            ->route('messengers.index', ['conversation_id' => $conversation->id])
            ->with('success', __('Conversation has been assigned.'));
    }

    public function updateConversationContact(Request $request, MessengerConversation $conversation, CrmModuleManager $moduleManager): RedirectResponse
    {
        $this->authorize('view', $conversation);

        $actor = $request->user();
        abort_unless($this->canEditConversationContact($actor, $conversation), 403);

        $validated = $request->validate([
            'first_name' => ['required', 'string', 'max:255'],
            'last_name' => ['nullable', 'string', 'max:255'],
            'title' => ['nullable', 'string', 'max:255'],
            'email' => ['nullable', 'email', 'max:255'],
            'phone' => ['nullable', 'string', 'max:50'],
            'owner_id' => ['nullable', 'integer', 'exists:users,id'],
            'source' => ['nullable', 'string', 'max:255'],
            'notes' => ['nullable', 'string'],
        ]);

        $conversation->loadMissing('contact');

        $payload = [
            'first_name' => trim((string) $validated['first_name']),
            'last_name' => $this->nullableText($validated['last_name'] ?? null),
            'title' => $this->nullableText($validated['title'] ?? null),
            'email' => $this->nullableText($validated['email'] ?? null),
            'phone' => $this->nullableText($validated['phone'] ?? null),
            'owner_id' => $validated['owner_id'] ?? ($conversation->user_id ?: null),
            'source' => $this->nullableText($validated['source'] ?? null) ?: ('messenger:'.$conversation->provider),
            'notes' => $this->nullableText($validated['notes'] ?? null),
        ];

        $contact = $conversation->contact;
        if ($contact) {
            $payload = $moduleManager->applyPayloadHooks('contacts.update', $payload, [
                'hook' => 'contacts.update',
                'user_id' => $actor->id,
                'contact_id' => $contact->id,
                'source' => 'messenger-chat',
            ], array_keys($payload));

            $contact->update($payload);
        } else {
            $payload = $moduleManager->applyPayloadHooks('contacts.store', $payload, [
                'hook' => 'contacts.store',
                'user_id' => $actor->id,
                'source' => 'messenger-chat',
            ], array_keys($payload));

            $contact = Contact::query()->create($payload);
        }

        $conversation->update([
            'contact_id' => $contact->id,
            'contact_name' => trim($contact->full_name) !== '' ? trim($contact->full_name) : $contact->first_name,
            'contact_handle' => $conversation->contact_handle ?: ($contact->email ?: $contact->phone),
        ]);

        return redirect()
            ->route('messengers.index', ['conversation_id' => $conversation->id])
            ->with('success', __('Contact has been updated.'));
    }

    public function storeMessage(Request $request, MessengerConversation $conversation, CrmModuleManager $moduleManager): RedirectResponse
    {
        $this->authorize('view', $conversation);
        $this->authorize('create', MessengerMessage::class);

        $validated = $request->validate([
            'body' => ['required', 'string', 'max:5000'],
        ]);

        $payload = [
            'conversation_id' => $conversation->id,
            'direction' => 'outbound',
            'body' => trim((string) $validated['body']),
            'status' => 'sent',
            'sent_at' => now(),
            'user_id' => $request->user()->id,
        ];

        $payload = $moduleManager->applyPayloadHooks('messengers.messages.store', $payload, [
            'hook' => 'messengers.messages.store',
            'user_id' => $request->user()->id,
            'conversation_id' => $conversation->id,
        ], array_keys($payload));

        MessengerMessage::query()->create($payload);
        $conversation->update(['last_message_at' => $payload['sent_at'] ?? now()]);

        return redirect()
            ->route('messengers.index', ['conversation_id' => $conversation->id])
            ->with('success', __('Message has been sent.'));
    }

    /**
     * @return array<string, string>
     */
    private function conversationStatusOptions(): array
    {
        return [
            'open' => __('Open'),
            'pending' => __('Pending'),
            'closed' => __('Closed'),
        ];
    }

    /**
     * @return array<string, string>
     */
    private function channelStatusOptions(): array
    {
        return [
            'active' => __('Active'),
            'disabled' => __('Disabled'),
        ];
    }

    /**
     * @return array<string, string>
     */
    private function messageStatusOptions(): array
    {
        return [
            'new' => __('New'),
            'sent' => __('Sent'),
            'delivered' => __('Delivered'),
            'read' => __('Read'),
            'failed' => __('Failed'),
        ];
    }

    /**
     * @return array<string, string>
     */
    private function directionOptions(): array
    {
        return [
            'inbound' => __('Inbound'),
            'outbound' => __('Outbound'),
        ];
    }

    private function nullableText(mixed $value): ?string
    {
        $value = trim((string) $value);

        return $value === '' ? null : $value;
    }

    private function canEditConversationContact(User $user, MessengerConversation $conversation): bool
    {
        if (! AccessControl::allows($user, 'messengers', 'update')) {
            return false;
        }

        if (AccessControl::isElevated($user)) {
            return true;
        }

        return (int) ($conversation->user_id ?? 0) === (int) $user->id;
    }

    private function settingsOrDefault(): MessengerSetting
    {
        $settings = MessengerSetting::query()->first();
        if (! $settings) {
            return new MessengerSetting([
                'provider' => 'telegram',
                'is_active' => false,
                'allowed_operator_ids' => [],
                'auto_create_contact' => true,
                'new_contact_action' => 'none',
                'new_contact_action_user_id' => null,
                'webhook_secret' => Str::random(40),
            ]);
        }

        if (! $settings->webhook_secret) {
            $settings->webhook_secret = Str::random(40);
        }

        return $settings;
    }
}

