<?php

declare(strict_types=1);

namespace Plugins\Larablog\PaidContent;

use App\Domain\Blog\AccessDecision;
use App\Domain\Blog\HtmlTeaser;
use App\Domain\Plugin\Hook;
use App\Models\Article;
use App\Models\User;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Schemas\Components\Section;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\TernaryFilter;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\ValidationException;
use Plugins\Larablog\PaidContent\Models\ArticleProduct;
use Plugins\Larablog\Payment\Domain\OrderService;
use Plugins\Larablog\Payment\Domain\ProductType;
use Plugins\Larablog\Payment\Models\Entitlement;

class PluginServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        //
    }

    public function boot(): void
    {
        $this->loadMigrationsFrom(__DIR__.'/../database/migrations');

        Hook::listen('filament.article.form', function (array $components): array {
            return [
                Section::make(__('admin.settings.paid_content'))
                    ->schema([
                        Toggle::make('paid_content.enabled')
                            ->label(__('admin.fields.paid_enabled'))
                            ->default(false),
                        TextInput::make('paid_content.price')
                            ->label(__('admin.fields.price'))
                            ->numeric()
                            ->default(0),
                        TextInput::make('paid_content.currency')
                            ->label(__('admin.fields.currency'))
                            ->default('CNY'),
                        TextInput::make('paid_content.trial_value')
                            ->label(__('admin.fields.trial_value'))
                            ->numeric()
                            ->default(200),
                    ])
                    ->collapsible(),
            ];
        });

        Hook::listen('filament.article.mutate_before_fill', function (array $data, mixed $record): array {
            if (! $record instanceof Article) {
                return $data;
            }

            $product = ArticleProduct::query()->where('article_id', $record->id)->first();

            $data['paid_content'] = [
                'enabled' => (bool) ($product?->enabled ?? false),
                'price' => $product?->price ?? null,
                'currency' => $product?->currency ?? 'CNY',
                'trial_mode' => $product?->trial_mode ?? 'chars',
                'trial_value' => $product?->trial_value ?? 200,
            ];

            return $data;
        });

        Hook::listen('filament.article.validate_access_restrictions', function (array $data, mixed $record): array {
            $paid = is_array($data['paid_content'] ?? null) ? $data['paid_content'] : [];
            $paidEnabled = (bool) ($paid['enabled'] ?? false);
            $membership = is_array($data['membership'] ?? null) ? $data['membership'] : [];
            $membershipEnabled = (bool) ($membership['enabled'] ?? false);
            $hasPassword = filled($data['read_password'] ?? null);

            $flags = array_filter([
                'password' => $hasPassword,
                'paid' => $paidEnabled,
                'membership' => $membershipEnabled,
            ]);

            if (count($flags) > 1) {
                throw ValidationException::withMessages([
                    'read_password' => __('admin.messages.access_restriction_mutex'),
                    'paid_content.enabled' => __('admin.messages.access_restriction_mutex'),
                    'membership.enabled' => __('admin.messages.access_restriction_mutex'),
                ]);
            }

            return $data;
        });

        Hook::listen('filament.article.after_save', function (Article $record, array $data): void {
            $paid = $data['paid_content'] ?? null;
            if (! is_array($paid)) {
                return;
            }

            $existing = ArticleProduct::query()->where('article_id', $record->id)->first();

            ArticleProduct::query()->updateOrCreate(
                ['article_id' => $record->id],
                [
                    'enabled' => (bool) ($paid['enabled'] ?? false),
                    'price' => $paid['price'] ?? 0,
                    'currency' => (string) ($paid['currency'] ?? 'CNY'),
                    'trial_mode' => (string) ($paid['trial_mode'] ?? $existing?->trial_mode ?? 'chars'),
                    'trial_value' => (int) ($paid['trial_value'] ?? 200),
                ],
            );
        });

        Hook::listen('filament.article.table.columns', function (array $columns): array {
            return [
                IconColumn::make('paid_content_enabled')
                    ->label(__('admin.fields.paid_enabled'))
                    ->boolean()
                    ->getStateUsing(fn (Article $record): bool => filled($record->getAttribute('paid_content_price'))),
                TextColumn::make('paid_content_price')
                    ->label(__('admin.fields.price'))
                    ->sortable()
                    ->formatStateUsing(function (mixed $state, Article $record): string {
                        if ($state === null || $state === '') {
                            return '—';
                        }

                        $currency = (string) ($record->getAttribute('paid_content_currency') ?: 'CNY');

                        return $state.' '.$currency;
                    }),
            ];
        });

        Hook::listen('filament.article.table.filters', function (array $filters): array {
            return [
                TernaryFilter::make('paid_enabled')
                    ->label(__('admin.fields.paid_enabled'))
                    ->queries(
                        true: fn (Builder $query): Builder => $query->whereExists(
                            fn ($sub) => $sub->from('article_products')
                                ->whereColumn('article_products.article_id', 'articles.id')
                                ->where('enabled', true),
                        ),
                        false: fn (Builder $query): Builder => $query->whereNotExists(
                            fn ($sub) => $sub->from('article_products')
                                ->whereColumn('article_products.article_id', 'articles.id')
                                ->where('enabled', true),
                        ),
                    ),
            ];
        });

        Hook::listen('filament.article.table.query', function (mixed $query): mixed {
            if (! $query instanceof Builder) {
                return $query;
            }

            return $query->addSelect([
                'paid_content_price' => ArticleProduct::query()
                    ->select('price')
                    ->whereColumn('article_id', 'articles.id')
                    ->where('enabled', true)
                    ->limit(1),
                'paid_content_currency' => ArticleProduct::query()
                    ->select('currency')
                    ->whereColumn('article_id', 'articles.id')
                    ->where('enabled', true)
                    ->limit(1),
            ]);
        });

        Hook::listen('article.access', function (AccessDecision $decision, array $context): AccessDecision {
            $article = $context['article'] ?? null;
            $user = $context['user'] ?? null;

            if (! $article instanceof Article) {
                return $decision;
            }

            $product = ArticleProduct::query()
                ->where('article_id', $article->id)
                ->where('enabled', true)
                ->first();

            if ($product === null) {
                return $decision;
            }

            if ($user instanceof User && $this->userHasEntitlement($user, (int) $article->id)) {
                return $decision;
            }

            $teaser = $this->buildTeaser($article, $product);
            $checkoutUrl = $this->checkoutUrl($article, $product);

            return $decision->tightenWith(AccessDecision::needPurchase($teaser, $checkoutUrl));
        });
    }

    protected function userHasEntitlement(User $user, int $articleId): bool
    {
        if (class_exists(OrderService::class)) {
            return app(OrderService::class)->hasEntitlement((int) $user->id, ProductType::ARTICLE, $articleId);
        }

        return Entitlement::query()
            ->where('user_id', $user->id)
            ->where('product_type', ProductType::ARTICLE)
            ->where('product_id', $articleId)
            ->whereNull('revoked_at')
            ->exists();
    }

    protected function buildTeaser(Article $article, ArticleProduct $product): string
    {
        if ($product->trial_mode === 'none' || (int) $product->trial_value <= 0) {
            return '';
        }

        $html = $article->renderedHtml();

        return app(HtmlTeaser::class)->truncate($html, (int) $product->trial_value);
    }

    protected function checkoutUrl(Article $article, ArticleProduct $product): string
    {
        $returnUrl = url('/show-'.$article->id.'.shtml');

        $query = http_build_query([
            'product_type' => ProductType::ARTICLE,
            'product_id' => $article->id,
            'return_url' => $returnUrl,
        ]);

        return url('/plugins/payment/checkout').'?'.$query;
    }
}
