<?php

namespace App\Support;

use App\Models\MailServiceSetting;
use App\Models\Mailbox;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class MailboxProvisioner
{
    private const DEFAULT_DOMAIN = 'crm.local';

    private const DEFAULT_STATUS = 'active';

    private const DEFAULT_QUOTA_MB = 2048;

    public function settingsOrDefault(): MailServiceSetting
    {
        $settings = MailServiceSetting::query()->first();

        if ($settings) {
            return $settings;
        }

        return new MailServiceSetting([
            'provider' => 'manual',
            'is_active' => false,
            'domain' => self::DEFAULT_DOMAIN,
            'auto_provision_on_registration' => true,
            'auto_provision_on_user_create' => true,
            'default_status' => self::DEFAULT_STATUS,
            'default_quota_mb' => self::DEFAULT_QUOTA_MB,
        ]);
    }

    public function shouldAutoProvisionOnRegistration(): bool
    {
        return (bool) ($this->settingsOrDefault()->auto_provision_on_registration ?? false);
    }

    public function shouldAutoProvisionOnUserCreate(): bool
    {
        return (bool) ($this->settingsOrDefault()->auto_provision_on_user_create ?? false);
    }

    public function ensurePrimaryMailbox(User $user, ?User $actor = null): Mailbox
    {
        $existing = Mailbox::query()
            ->where('user_id', $user->id)
            ->orderByDesc('is_primary')
            ->orderBy('id')
            ->first();

        if ($existing) {
            if (! $existing->is_primary) {
                $existing->update(['is_primary' => true]);
            }

            return $existing->refresh();
        }

        return $this->createMailbox($user, $actor);
    }

    /**
     * @param  array<string, mixed>  $attributes
     */
    public function createMailbox(User $user, ?User $actor = null, array $attributes = []): Mailbox
    {
        $settings = $this->settingsOrDefault();

        $domain = $this->normalizeDomain((string) ($attributes['domain'] ?? $settings->domain));
        $baseLocalPart = $this->normalizeLocalPart((string) ($attributes['local_part'] ?? $this->defaultLocalPart($user)));
        $localPart = $this->findAvailableLocalPart($baseLocalPart, $domain);
        $status = $this->normalizeStatus((string) ($attributes['status'] ?? $settings->default_status ?? self::DEFAULT_STATUS));
        $quotaMb = max(1, (int) ($attributes['quota_mb'] ?? $settings->default_quota_mb ?? self::DEFAULT_QUOTA_MB));
        $usedMb = max(0, min((int) ($attributes['used_mb'] ?? 0), $quotaMb));
        $isPrimary = array_key_exists('is_primary', $attributes)
            ? (bool) $attributes['is_primary']
            : ! Mailbox::query()->where('user_id', $user->id)->exists();

        $address = $localPart.'@'.$domain;

        /** @var Mailbox $mailbox */
        $mailbox = DB::transaction(function () use (
            $actor,
            $address,
            $attributes,
            $domain,
            $isPrimary,
            $localPart,
            $quotaMb,
            $status,
            $usedMb,
            $user
        ): Mailbox {
            if ($isPrimary) {
                Mailbox::query()
                    ->where('user_id', $user->id)
                    ->update(['is_primary' => false]);
            }

            $mailbox = Mailbox::query()->create([
                'user_id' => $user->id,
                'address' => $address,
                'local_part' => $localPart,
                'domain' => $domain,
                'status' => $status,
                'is_primary' => $isPrimary,
                'password' => $this->nullableText($attributes['password'] ?? null),
                'quota_mb' => $quotaMb,
                'used_mb' => $usedMb,
                'forward_to' => $this->nullableText($attributes['forward_to'] ?? null),
                'meta' => is_array($attributes['meta'] ?? null) ? $attributes['meta'] : null,
                'provisioned_at' => now(),
                'created_by' => $actor?->id,
                'updated_by' => $actor?->id,
            ]);

            if (! $mailbox->is_primary) {
                $hasPrimary = Mailbox::query()
                    ->where('user_id', $user->id)
                    ->where('is_primary', true)
                    ->whereKeyNot($mailbox->id)
                    ->exists();

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

            return $mailbox;
        });

        return $mailbox->refresh();
    }

    private function defaultLocalPart(User $user): string
    {
        $email = strtolower(trim((string) $user->email));
        $emailLocalPart = explode('@', $email)[0] ?? '';
        $candidate = $this->normalizeLocalPart($emailLocalPart);

        if ($candidate !== '') {
            return $candidate;
        }

        $nameCandidate = $this->normalizeLocalPart(Str::slug((string) $user->name, '.'));
        if ($nameCandidate !== '') {
            return $nameCandidate;
        }

        return 'user'.$user->id;
    }

    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 !== '') {
            return $value;
        }

        $appHost = (string) parse_url((string) config('app.url'), PHP_URL_HOST);
        $appHost = trim(strtolower($appHost));

        return $appHost !== '' ? $appHost : self::DEFAULT_DOMAIN;
    }

    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, '._-');

        if ($value !== '') {
            return mb_substr($value, 0, 64);
        }

        return 'user';
    }

    private function normalizeStatus(string $value): string
    {
        $value = strtolower(trim($value));

        return in_array($value, ['active', 'suspended', 'disabled'], true)
            ? $value
            : self::DEFAULT_STATUS;
    }

    private function findAvailableLocalPart(string $baseLocalPart, string $domain): string
    {
        $candidate = $baseLocalPart;
        $suffix = 0;

        while ($suffix < 5000) {
            $exists = Mailbox::query()
                ->where('local_part', $candidate)
                ->where('domain', $domain)
                ->exists();

            if (! $exists) {
                return $candidate;
            }

            $suffix++;
            $candidate = mb_substr($baseLocalPart, 0, 58).$suffix;
        }

        return mb_substr($baseLocalPart, 0, 40).Str::lower(Str::random(6));
    }

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

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

