<?php

namespace App\Http\Controllers;

use App\Models\MailServiceSetting;
use App\Models\Mailbox;
use App\Models\User;
use App\Support\AccessControl;
use App\Support\CrmModuleManager;
use App\Support\MailboxProvisioner;
use App\Support\SectionAccessManager;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;

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

        $user = $request->user();
        $isElevated = AccessControl::isElevated($user);

        $search = trim((string) $request->input('q', ''));
        $status = trim((string) $request->input('status', ''));
        $domain = trim((string) $request->input('domain', ''));
        $dateFrom = trim((string) $request->input('date_from', ''));
        $dateTo = trim((string) $request->input('date_to', ''));
        $selectedUserId = $isElevated ? $request->integer('user_id') : (int) $user->id;

        $mailboxesQuery = Mailbox::query()
            ->with('user:id,name,email,job_title')
            ->when($search !== '', function ($query) use ($search): void {
                $query->where(function ($sub) use ($search): void {
                    $sub->where('address', 'like', "%{$search}%")
                        ->orWhere('local_part', 'like', "%{$search}%")
                        ->orWhere('forward_to', 'like', "%{$search}%");
                });
            })
            ->when($status !== '' && array_key_exists($status, $this->statusOptions()), fn ($query) => $query->where('status', $status))
            ->when($domain !== '', fn ($query) => $query->where('domain', 'like', "%{$domain}%"))
            ->when($dateFrom !== '', fn ($query) => $query->whereDate('provisioned_at', '>=', $dateFrom))
            ->when($dateTo !== '', fn ($query) => $query->whereDate('provisioned_at', '<=', $dateTo));

        if (! $isElevated) {
            $mailboxesQuery->where('user_id', $user->id);
        } elseif ($selectedUserId > 0) {
            $mailboxesQuery->where('user_id', $selectedUserId);
        }

        $mailboxes = $mailboxesQuery
            ->orderByDesc('is_primary')
            ->orderBy('address')
            ->paginate(25)
            ->withQueryString();

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

        $canManageSettings = AccessControl::allows($user, 'mail', 'update');
        $canCreateMailbox = AccessControl::allows($user, 'mail', 'create');
        $canUpdateMailbox = AccessControl::allows($user, 'mail', 'update');
        $canDeleteMailbox = AccessControl::allows($user, 'mail', 'delete');

        $canManageSectionAccess = $sectionAccessManager->canManage($user, 'mail');
        $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('mail.index', [
            'mailboxes' => $mailboxes,
            'settings' => $settings,
            'search' => $search,
            'status' => $status,
            'domain' => $domain,
            'dateFrom' => $dateFrom,
            'dateTo' => $dateTo,
            'selectedUserId' => $selectedUserId,
            'statusOptions' => $this->statusOptions(),
            'userOptions' => $userOptions,
            'canManageSettings' => $canManageSettings,
            'canCreateMailbox' => $canCreateMailbox,
            'canUpdateMailbox' => $canUpdateMailbox,
            'canDeleteMailbox' => $canDeleteMailbox,
            'canManageSectionAccess' => $canManageSectionAccess,
            'sectionAccessUsers' => $sectionAccessUsers,
        ]);
    }

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

        $validated = $request->validate([
            'provider' => ['required', 'string', 'max:60'],
            'is_active' => ['nullable', 'boolean'],
            'domain' => ['required', 'string', 'max:120'],
            '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'],
            'webhook_secret' => ['nullable', 'string', 'max:255'],
            'auto_provision_on_registration' => ['nullable', 'boolean'],
            'auto_provision_on_user_create' => ['nullable', 'boolean'],
            'default_status' => ['required', Rule::in(array_keys($this->statusOptions()))],
            'default_quota_mb' => ['required', 'integer', 'min:1', 'max:102400'],
        ]);

        $payload = [
            'provider' => trim((string) $validated['provider']),
            'is_active' => $request->boolean('is_active'),
            'domain' => $this->normalizeDomain((string) $validated['domain']),
            '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),
            'webhook_secret' => $this->nullableText($validated['webhook_secret'] ?? null),
            'auto_provision_on_registration' => $request->boolean('auto_provision_on_registration', true),
            'auto_provision_on_user_create' => $request->boolean('auto_provision_on_user_create', true),
            'default_status' => (string) $validated['default_status'],
            'default_quota_mb' => (int) $validated['default_quota_mb'],
        ];

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

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

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

    public function storeMailbox(Request $request, MailboxProvisioner $mailboxProvisioner, CrmModuleManager $moduleManager): RedirectResponse
    {
        $this->authorize('create', Mailbox::class);

        $validated = $request->validate([
            'user_id' => ['required', 'integer', 'exists:users,id'],
            'local_part' => ['nullable', 'string', 'max:64'],
            'domain' => ['nullable', 'string', 'max:120'],
            'status' => ['required', Rule::in(array_keys($this->statusOptions()))],
            'is_primary' => ['nullable', 'boolean'],
            'quota_mb' => ['nullable', 'integer', 'min:1', 'max:102400'],
            'used_mb' => ['nullable', 'integer', 'min:0'],
            'password' => ['nullable', 'string', 'max:255'],
            'forward_to' => ['nullable', 'email', 'max:255'],
            'meta' => ['nullable', 'array'],
        ]);

        $actor = $request->user();
        $target = User::query()->findOrFail((int) $validated['user_id']);
        if (! AccessControl::isElevated($actor) && $target->id !== $actor->id) {
            abort(403);
        }

        $payload = [
            'local_part' => $this->nullableText($validated['local_part'] ?? null),
            'domain' => $this->nullableText($validated['domain'] ?? null),
            'status' => (string) $validated['status'],
            'is_primary' => $request->boolean('is_primary'),
            'quota_mb' => (int) ($validated['quota_mb'] ?? 2048),
            'used_mb' => (int) ($validated['used_mb'] ?? 0),
            'password' => $this->nullableText($validated['password'] ?? null),
            'forward_to' => $this->nullableText($validated['forward_to'] ?? null),
            'meta' => is_array($validated['meta'] ?? null) ? $validated['meta'] : null,
        ];

        if (($payload['local_part'] ?? null) !== null) {
            $payload['local_part'] = $this->normalizeLocalPart((string) $payload['local_part']);
        }
        if (($payload['domain'] ?? null) !== null) {
            $payload['domain'] = $this->normalizeDomain((string) $payload['domain']);
        }
        $payload['used_mb'] = min((int) $payload['used_mb'], (int) $payload['quota_mb']);

        $payload = $moduleManager->applyPayloadHooks('mail.mailboxes.store', $payload, [
            'hook' => 'mail.mailboxes.store',
            'user_id' => $actor->id,
            'target_user_id' => $target->id,
            'source' => 'web',
        ], array_keys($payload));

        $mailboxProvisioner->createMailbox($target, $actor, $payload);

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

    public function updateMailbox(Request $request, Mailbox $mailbox, CrmModuleManager $moduleManager): RedirectResponse
    {
        $this->authorize('update', $mailbox);

        $validated = $request->validate([
            'user_id' => ['required', 'integer', 'exists:users,id'],
            'local_part' => ['required', 'string', 'max:64'],
            'domain' => ['required', 'string', 'max:120'],
            'status' => ['required', Rule::in(array_keys($this->statusOptions()))],
            'is_primary' => ['nullable', 'boolean'],
            'quota_mb' => ['required', 'integer', 'min:1', 'max:102400'],
            'used_mb' => ['required', 'integer', 'min:0'],
            'password' => ['nullable', 'string', 'max:255'],
            'forward_to' => ['nullable', 'email', 'max:255'],
            'meta' => ['nullable', 'array'],
        ]);

        $actor = $request->user();
        $targetUserId = (int) $validated['user_id'];
        if (! AccessControl::isElevated($actor) && $targetUserId !== $actor->id) {
            abort(403);
        }

        $localPart = $this->normalizeLocalPart((string) $validated['local_part']);
        $domain = $this->normalizeDomain((string) $validated['domain']);
        $address = $localPart.'@'.$domain;

        $duplicateExists = Mailbox::query()
            ->where('address', $address)
            ->whereKeyNot($mailbox->id)
            ->exists();
        if ($duplicateExists) {
            throw ValidationException::withMessages([
                'local_part' => __('Mailbox address is already in use.'),
            ]);
        }

        $payload = [
            'user_id' => $targetUserId,
            'address' => $address,
            'local_part' => $localPart,
            'domain' => $domain,
            'status' => (string) $validated['status'],
            'is_primary' => $request->boolean('is_primary'),
            'password' => $this->nullableText($validated['password'] ?? null),
            'quota_mb' => (int) $validated['quota_mb'],
            'used_mb' => min((int) $validated['used_mb'], (int) $validated['quota_mb']),
            'forward_to' => $this->nullableText($validated['forward_to'] ?? null),
            'meta' => is_array($validated['meta'] ?? null) ? $validated['meta'] : null,
            'updated_by' => $actor->id,
        ];

        $payload = $moduleManager->applyPayloadHooks('mail.mailboxes.update', $payload, [
            'hook' => 'mail.mailboxes.update',
            'user_id' => $actor->id,
            'mailbox_id' => $mailbox->id,
            'source' => 'web',
        ], array_keys($payload));

        $this->applyMailboxUpdate($mailbox, $payload);

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

    public function destroyMailbox(Request $request, Mailbox $mailbox): RedirectResponse
    {
        $this->authorize('delete', $mailbox);

        $userId = (int) ($mailbox->user_id ?? 0);
        $wasPrimary = (bool) $mailbox->is_primary;

        $mailbox->delete();

        if ($userId > 0 && $wasPrimary) {
            $replacement = Mailbox::query()
                ->where('user_id', $userId)
                ->orderBy('id')
                ->first();

            if ($replacement) {
                $replacement->forceFill(['is_primary' => true])->save();
            }
        }

        return redirect()
            ->route('mail.index')
            ->with('success', __('Mailbox has been deleted.'));
    }

    /**
     * @param  array<string, mixed>  $payload
     */
    private function applyMailboxUpdate(Mailbox $mailbox, array $payload): void
    {
        $previousUserId = (int) ($mailbox->user_id ?? 0);
        $nextUserId = (int) ($payload['user_id'] ?? $mailbox->user_id ?? 0);
        $nextIsPrimary = (bool) ($payload['is_primary'] ?? false);

        DB::transaction(function () use ($mailbox, $nextIsPrimary, $nextUserId, $payload, $previousUserId): void {
            if ($nextIsPrimary) {
                Mailbox::query()
                    ->where('user_id', $nextUserId)
                    ->whereKeyNot($mailbox->id)
                    ->update(['is_primary' => false]);
            }

            $mailbox->fill($payload)->save();

            $hasPrimaryForTarget = Mailbox::query()
                ->where('user_id', $nextUserId)
                ->where('is_primary', true)
                ->exists();

            if (! $hasPrimaryForTarget) {
                $mailbox->forceFill(['is_primary' => true])->save();
            }

            if ($previousUserId > 0 && $previousUserId !== $nextUserId) {
                $hasPrimaryForPrevious = Mailbox::query()
                    ->where('user_id', $previousUserId)
                    ->where('is_primary', true)
                    ->exists();

                if (! $hasPrimaryForPrevious) {
                    $fallback = Mailbox::query()
                        ->where('user_id', $previousUserId)
                        ->orderBy('id')
                        ->first();

                    if ($fallback) {
                        $fallback->forceFill(['is_primary' => true])->save();
                    }
                }
            }
        });
    }

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

    private function normalizeDomain(string $value): string
    {
        $value = strtolower(trim($value));
        $value = ltrim($value, '@');
        if (str_contains($value, '://')) {
            $host = parse_url($value, PHP_URL_HOST);
            $value = is_string($host) ? $host : '';
        }

        $value = preg_replace('/[^a-z0-9.-]+/', '', $value) ?: '';
        $value = trim($value, '.-');

        if ($value === '') {
            $value = (string) parse_url((string) config('app.url'), PHP_URL_HOST);
            $value = trim(strtolower($value));
        }

        return $value !== '' ? $value : 'crm.local';
    }

    private function normalizeLocalPart(string $value): string
    {
        $value = Str::ascii(strtolower(trim($value)));
        $value = preg_replace('/[^a-z0-9._-]+/', '.', $value) ?: '';
        $value = preg_replace('/[._-]{2,}/', '.', $value) ?: '';
        $value = trim($value, '._-');

        return $value !== '' ? mb_substr($value, 0, 64) : 'user';
    }

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

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

