<?php

namespace App\Filament\Pages;

use App\Models\ImportedJob;
use App\Models\JobSource;
use App\Models\User;
use App\Services\Email\TransactionalEmailSender;
use App\Services\Settings\SettingsService;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Facades\Filament;
use Filament\Forms\Components\ColorPicker;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Components\Actions;
use Filament\Schemas\Components\Component;
use Filament\Schemas\Components\EmbeddedSchema;
use Filament\Schemas\Components\Form;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Support\Enums\Alignment;
use Filament\Support\Enums\Width;
use Filament\Support\Icons\Heroicon;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Schema as DatabaseSchema;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
use Throwable;

class ManageSettings extends Page
{
    protected static string|BackedEnum|null $navigationIcon = Heroicon::Cog6Tooth;

    protected static ?string $navigationLabel = 'Settings';

    protected static string|\UnitEnum|null $navigationGroup = 'Platform';

    protected static ?int $navigationSort = 1;

    protected static ?string $slug = 'settings';

    protected string $view = 'filament.pages.manage-settings';

    protected Width|string|null $maxContentWidth = Width::Full;

    /**
     * @var array<string, mixed> | null
     */
    public ?array $data = [];

    public string $activeGroup = 'general';

    public static function canAccess(): bool
    {
        $user = Filament::auth()->user();

        return $user instanceof User && $user->isAdmin();
    }

    public function mount(SettingsService $settings): void
    {
        $this->form->fill($this->formState($settings));
    }

    public function defaultForm(Schema $schema): Schema
    {
        return $schema->statePath('data');
    }

    public function form(Schema $schema): Schema
    {
        $groupDefinition = $this->groups()[$this->activeGroup] ?? [];

        return $schema->components([
            Section::make((string) ($groupDefinition['label'] ?? Str::headline($this->activeGroup)))
                ->description((string) ($groupDefinition['description'] ?? ''))
                ->schema($this->fieldsForGroup($this->activeGroup, $groupDefinition))
                ->columns([
                    'default' => 1,
                    'md' => 2,
                    'xl' => 3,
                ])
                ->columnSpanFull(),
        ]);
    }

    public function content(Schema $schema): Schema
    {
        return $schema->components([
            Form::make([EmbeddedSchema::make('form')])
                ->id('settings-form')
                ->livewireSubmitHandler('save')
                ->footer([
                    Actions::make($this->formActions())
                        ->alignment(Alignment::Start)
                        ->sticky()
                        ->key('settings-form-actions'),
                ]),
        ]);
    }

    public function save(SettingsService $settings): void
    {
        if ($this->isDemoMode()) {
            Notification::make()
                ->warning()
                ->title('Demo mode is read-only')
                ->body('Settings cannot be changed in the public demo.')
                ->send();

            return;
        }

        $state = $this->form->getState();
        $group = $this->activeGroup;
        $groupDefinition = $this->groups()[$group] ?? [];

        foreach (($groupDefinition['settings'] ?? []) as $key => $definition) {
            if ($definition['read_only'] ?? false) {
                continue;
            }

            $value = data_get($state, "{$group}.{$key}");
            $isEncrypted = (bool) ($definition['encrypted'] ?? false);

            if ($isEncrypted && blank($value)) {
                continue;
            }

            $settings->set(
                group: (string) $group,
                key: (string) $key,
                value: $value,
                type: (string) ($definition['type'] ?? 'string'),
                encrypted: $isEncrypted,
                public: (bool) ($definition['public'] ?? false),
            );
        }

        $this->form->fill($this->formState($settings));

        Notification::make()
            ->success()
            ->title(Str::headline($group).' settings saved')
            ->body('Settings cache was cleared and secret values remain encrypted.')
            ->send();
    }

    public function setActiveGroup(string $group): void
    {
        if (! array_key_exists($group, $this->groups())) {
            return;
        }

        $this->activeGroup = $group;
        $this->form->fill($this->formState(app(SettingsService::class)));
    }

    /**
     * @return array<string, string>
     */
    public function tabLabels(): array
    {
        return collect($this->groups())
            ->mapWithKeys(fn (array $definition, string $group): array => [
                $group => (string) ($definition['label'] ?? Str::headline($group)),
            ])
            ->all();
    }

    /**
     * @return array<Action>
     */
    protected function getHeaderActions(): array
    {
        return [
            Action::make('clearSettingsCache')
                ->label('Clear Settings Cache')
                ->icon(Heroicon::ArrowPath)
                ->color('gray')
                ->disabled(fn (): bool => $this->isDemoMode())
                ->action(function (): void {
                    app(SettingsService::class)->clearCache();

                    Notification::make()
                        ->success()
                        ->title('Settings cache cleared')
                        ->send();
                }),

            Action::make('clearAppCache')
                ->label('Clear App Cache')
                ->icon(Heroicon::Bolt)
                ->color('warning')
                ->disabled(fn (): bool => $this->isDemoMode())
                ->requiresConfirmation()
                ->action(function (): void {
                    Artisan::call('cache:clear');
                    app(SettingsService::class)->clearCache();

                    Notification::make()
                        ->success()
                        ->title('Application cache cleared')
                        ->send();
                }),

            Action::make('sendTestEmail')
                ->label('Send Test Email')
                ->icon(Heroicon::Envelope)
                ->disabled(fn (): bool => $this->isDemoMode())
                ->schema([
                    TextInput::make('email')
                        ->label('Recipient email')
                        ->default(fn (): ?string => app(SettingsService::class)->get('email.admin_notification_email'))
                        ->email()
                        ->required(),
                ])
                ->action(function (array $data): void {
                    $settings = app(SettingsService::class);

                    try {
                        $transport = 'SMTP';

                        if ($settings->isEnabled('email.brevo_enabled') && filled($settings->get('email.brevo_api_key'))) {
                            $transport = 'Brevo API';
                        }

                        app(TransactionalEmailSender::class)->send(
                            toEmail: (string) $data['email'],
                            toName: null,
                            subject: 'RemoteMatch AI test email',
                            html: '<p>This is a test email from your RemoteMatch AI admin settings module.</p>',
                            text: 'This is a test email from your RemoteMatch AI admin settings module.',
                            userId: Filament::auth()->id(),
                            type: 'admin_test',
                            metadata: [
                                'triggered_from' => 'admin_settings',
                            ],
                        );

                        Notification::make()
                            ->success()
                            ->title('Test email queued/sent')
                            ->body("Transport used: {$transport}. Check the recipient inbox and spam folder if delivery is delayed.")
                            ->send();
                    } catch (Throwable $exception) {
                        report($exception);

                        Notification::make()
                            ->danger()
                            ->title('Test email failed')
                            ->body(Str::limit($exception->getMessage(), 260))
                            ->send();
                    }
                }),

            Action::make('queueJobSync')
                ->label('Queue Job Sync')
                ->icon(Heroicon::ArrowPathRoundedSquare)
                ->color('gray')
                ->disabled(fn (): bool => $this->isDemoMode())
                ->requiresConfirmation()
                ->action(function (): void {
                    Artisan::call('jobs:sync-sources');

                    Notification::make()
                        ->success()
                        ->title('Job sync queued')
                        ->body(trim(Artisan::output()) ?: 'The existing jobs:sync-sources command was dispatched.')
                        ->send();
                }),
        ];
    }

    /**
     * @return array<Action>
     */
    private function formActions(): array
    {
        return [
            Action::make('save')
                ->label('Save settings')
                ->icon(Heroicon::Check)
                ->submit('save')
                ->disabled(fn (): bool => $this->isDemoMode())
                ->keyBindings(['mod+s']),
        ];
    }

    /**
     * @param  array<string, mixed>  $groupDefinition
     * @return array<int, Component>
     */
    private function fieldsForGroup(string $group, array $groupDefinition): array
    {
        return collect($groupDefinition['settings'] ?? [])
            ->map(fn (array $definition, string $key): Component => $this->field($group, $key, $definition))
            ->values()
            ->all();
    }

    /**
     * @param  array<string, mixed>  $definition
     */
    private function field(string $group, string $key, array $definition): Component
    {
        $statePath = "{$group}.{$key}";
        $label = (string) ($definition['label'] ?? Str::headline($key));
        $type = (string) ($definition['type'] ?? 'string');

        $field = match ($type) {
            'boolean' => Toggle::make($statePath),
            'textarea' => Textarea::make($statePath)->rows(4),
            'password' => TextInput::make($statePath)
                ->password()
                ->revealable()
                ->autocomplete('new-password')
                ->placeholder($this->secretPlaceholder($group, $key)),
            'email' => TextInput::make($statePath)->email(),
            'url' => TextInput::make($statePath)->url(),
            'integer' => TextInput::make($statePath)->integer(),
            'decimal' => TextInput::make($statePath)->numeric(),
            'select' => Select::make($statePath)
                ->options((array) ($definition['options'] ?? []))
                ->native(false),
            'color' => ColorPicker::make($statePath),
            'file' => FileUpload::make($statePath)
                ->disk('public')
                ->directory('settings')
                ->visibility('public')
                ->acceptedFileTypes(['image/png', 'image/jpeg', 'image/webp', 'image/svg+xml', 'image/x-icon'])
                ->maxSize(2048),
            'tags', 'array' => TagsInput::make($statePath)
                ->placeholder('Add a value and press Enter'),
            default => TextInput::make($statePath),
        };

        $field->label($label);

        if (! ($definition['read_only'] ?? false)) {
            in_array($type, ['boolean', 'select', 'tags', 'array', 'color'], true)
                ? $field->live()
                : $field->live(onBlur: true);
        }

        if ($rules = ($definition['rules'] ?? null)) {
            $field->rules($rules);
        }

        if ($definition['required'] ?? false) {
            $field->required();
        }

        if ($type === 'password') {
            $field->helperText($this->secretHelperText($group, $key));
        } elseif (filled($definition['helper'] ?? null)) {
            $field->helperText((string) $definition['helper']);
        }

        if (($definition['read_only'] ?? false) || $this->isDemoMode()) {
            $field
                ->disabled()
                ->dehydrated(false);

            if ($definition['read_only'] ?? false) {
                $field->helperText('Read-only operational status. It is not saved as a setting.');
            } elseif ($this->isDemoMode()) {
                $field->helperText('Demo mode is read-only. Values cannot be changed from the public demo.');
            }
        }

        if (($definition['full_width'] ?? false) || in_array($type, ['textarea', 'file', 'tags', 'array'], true)) {
            $field->columnSpanFull();
        }

        return $field;
    }

    private function secretPlaceholder(string $group, string $key): string
    {
        $value = app(SettingsService::class)->get("{$group}.{$key}");

        if (! filled($value)) {
            return 'No secret saved';
        }

        return 'Saved: '.app(SettingsService::class)->maskSecret((string) $value);
    }

    private function secretHelperText(string $group, string $key): Htmlable
    {
        $value = app(SettingsService::class)->get("{$group}.{$key}");
        $masked = filled($value)
            ? app(SettingsService::class)->maskSecret((string) $value)
            : 'none saved';

        return new HtmlString('Leave blank to keep the current secret. Stored value: <code>'.$masked.'</code>');
    }

    /**
     * @return array<string, mixed>
     */
    private function formState(SettingsService $settings): array
    {
        $state = [];

        $group = $this->activeGroup;
        $groupDefinition = $this->groups()[$group] ?? [];

        foreach (($groupDefinition['settings'] ?? []) as $key => $definition) {
            if ($definition['read_only'] ?? false) {
                Arr::set($state, "{$group}.{$key}", $this->readOnlyValue($group, $key));

                continue;
            }

            if ($definition['encrypted'] ?? false) {
                Arr::set($state, "{$group}.{$key}", null);

                continue;
            }

            Arr::set(
                $state,
                "{$group}.{$key}",
                $settings->get("{$group}.{$key}", $definition['default'] ?? null)
            );
        }

        return $state;
    }

    public function isDemoMode(): bool
    {
        return (bool) config('admin.demo_mode');
    }

    /**
     * @return array<string, array<string, mixed>>
     */
    private function groups(): array
    {
        return collect((array) config('settings.groups', []))
            ->filter(fn (array $definition): bool => (bool) ($definition['visible_in_settings'] ?? true))
            ->all();
    }

    private function readOnlyValue(string $group, string $key): string|int|null
    {
        if ($group === 'job_sources') {
            return match ($key) {
                'last_sync_time' => $this->latestJobSyncTime(),
                'last_sync_status' => $this->jobSourceStatus(),
                'total_jobs_imported' => ImportedJob::query()->count(),
                'total_active_jobs' => ImportedJob::query()->active()->count(),
                default => null,
            };
        }

        if ($group !== 'maintenance') {
            return null;
        }

        return match ($key) {
            'queue_status' => 'Driver: '.config('queue.default'),
            'last_queue_run' => 'Not tracked yet',
            'last_scheduler_run' => 'Not tracked yet',
            'last_job_sync' => $this->latestJobSyncTime(),
            'failed_jobs' => DatabaseSchema::hasTable('failed_jobs') ? DB::table('failed_jobs')->count() : 0,
            'storage_usage' => 'Storage path: '.storage_path('app'),
            'database_status' => $this->databaseStatus(),
            'openai_status' => filled(app(SettingsService::class)->get('openai.api_key')) ? 'Configured' : 'Missing API key',
            'mail_status' => 'Mailer: '.config('mail.default'),
            'razorpay_webhook_status' => filled(app(SettingsService::class)->get('payment.razorpay_webhook_secret')) ? 'Configured' : 'Missing webhook secret',
            'cron_status' => 'Scheduler command exists; configure cron on hosting.',
            default => null,
        };
    }

    private function latestJobSyncTime(): string
    {
        $lastSyncedAt = JobSource::query()->max('last_synced_at');

        return $lastSyncedAt ? (string) $lastSyncedAt : 'Never';
    }

    private function jobSourceStatus(): string
    {
        $lastSyncedAt = JobSource::query()->max('last_synced_at');
        $lastFailedAt = JobSource::query()->max('last_failed_at');

        if (! $lastSyncedAt && ! $lastFailedAt) {
            return 'Not synced';
        }

        if ($lastFailedAt && (! $lastSyncedAt || $lastFailedAt > $lastSyncedAt)) {
            return 'Last sync failed';
        }

        return 'Last sync completed';
    }

    private function databaseStatus(): string
    {
        try {
            DB::select('select 1');

            return 'Connected';
        } catch (Throwable) {
            return 'Connection failed';
        }
    }

    private function applyMailSettings(SettingsService $settings): void
    {
        config([
            'mail.default' => $settings->get('email.mail_driver', config('mail.default')),
            'mail.mailers.smtp.host' => $settings->get('email.smtp_host', config('mail.mailers.smtp.host')),
            'mail.mailers.smtp.port' => $settings->get('email.smtp_port', config('mail.mailers.smtp.port')),
            'mail.mailers.smtp.username' => $settings->get('email.smtp_username', config('mail.mailers.smtp.username')),
            'mail.mailers.smtp.password' => $settings->get('email.smtp_password', config('mail.mailers.smtp.password')),
            'mail.mailers.smtp.encryption' => $settings->get('email.smtp_encryption', config('mail.mailers.smtp.encryption')),
            'mail.from.address' => $settings->get('email.from_email', config('mail.from.address')),
            'mail.from.name' => $settings->get('email.from_name', config('mail.from.name')),
        ]);
    }

    private function sendBrevoTestEmail(string $recipientEmail, SettingsService $settings): void
    {
        Http::timeout(12)
            ->withHeaders([
                'accept' => 'application/json',
                'api-key' => (string) $settings->get('email.brevo_api_key'),
                'content-type' => 'application/json',
            ])
            ->post((string) $settings->get('email.brevo_api_url', 'https://api.brevo.com/v3/smtp/email'), array_filter([
                'sender' => [
                    'name' => (string) $settings->get('email.from_name', 'RemoteMatch'),
                    'email' => (string) $settings->get('email.from_email', 'no-reply@remotematch.local'),
                ],
                'to' => [
                    [
                        'email' => $recipientEmail,
                    ],
                ],
                'replyTo' => filled($settings->get('email.reply_to_email'))
                    ? ['email' => (string) $settings->get('email.reply_to_email')]
                    : null,
                'subject' => 'RemoteMatch AI test email',
                'htmlContent' => '<p>This is a test email from your RemoteMatch AI admin settings module.</p>',
                'textContent' => 'This is a test email from your RemoteMatch AI admin settings module.',
            ], fn (mixed $value): bool => $value !== null))
            ->throw();
    }
}
