<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Resources\MailboxResource;
use App\Models\Mailbox;
use App\Models\User;
use App\Support\AccessControl;
use App\Support\CrmModuleManager;
use App\Support\MailboxProvisioner;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;

class MailboxController extends Controller
{
    public function __construct()
    {
        $this->authorizeResource(Mailbox::class, 'mailbox');
    }

    public function index(Request $request)
    {
        $search = trim((string) $request->input('q', ''));
        $status = trim((string) $request->input('status', ''));
        $domain = trim((string) $request->input('domain', ''));
        $userId = $request->integer('user_id');

        $query = Mailbox::query()
            ->with('user:id,name,email')
            ->when($search !== '', function ($builder) use ($search): void {
                $builder->where(function ($sub) use ($search): void {
                    $sub->where('address', 'like', "%{$search}%")
                        ->orWhere('local_part', 'like', "%{$search}%")
                        ->orWhere('forward_to', 'like', "%{$search}%");
                });
            })
            ->when(in_array($status, $this->statusValues(), true), fn ($builder) => $builder->where('status', $status))
            ->when($domain !== '', fn ($builder) => $builder->where('domain', 'like', "%{$domain}%"));

        if (! AccessControl::isElevated($request->user())) {
            $query->where('user_id', $request->user()->id);
        } elseif ($userId > 0) {
            $query->where('user_id', $userId);
        }

        $mailboxes = $query
            ->orderByDesc('is_primary')
            ->orderBy('address')
            ->paginate(30)
            ->withQueryString();

        return MailboxResource::collection($mailboxes);
    }

    public function store(Request $request, MailboxProvisioner $mailboxProvisioner, CrmModuleManager $moduleManager): MailboxResource
    {
        $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($this->statusValues())],
            '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();
        $targetUser = User::query()->findOrFail((int) $validated['user_id']);

        if (! AccessControl::isElevated($actor) && $targetUser->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' => (bool) ($validated['is_primary'] ?? false),
            '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' => $targetUser->id,
            'source' => 'api',
        ], array_keys($payload));

        $mailbox = $mailboxProvisioner->createMailbox($targetUser, $actor, $payload);

        return MailboxResource::make($mailbox->load('user'));
    }

    public function show(Mailbox $mailbox): MailboxResource
    {
        return MailboxResource::make($mailbox->load('user'));
    }

    public function update(Request $request, Mailbox $mailbox, CrmModuleManager $moduleManager): MailboxResource
    {
        $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($this->statusValues())],
            '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' => (bool) ($validated['is_primary'] ?? false),
            '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' => 'api',
        ], array_keys($payload));

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

        return MailboxResource::make($mailbox->fresh()->load('user'));
    }

    public function destroy(Mailbox $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 response()->noContent();
    }

    /**
     * @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 list<string>
     */
    private function statusValues(): array
    {
        return ['active', 'suspended', '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;
    }
}

