<?php

namespace Jeffgreco13\FilamentBreezy;

use BaconQrCode\Renderer\Color\Rgb;
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\RendererStyle\Fill;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Writer;
use Closure;
use Filament\Contracts\Plugin;
use Filament\Facades\Filament;
use Filament\Forms;
use Filament\Navigation\MenuItem;
use Filament\Panel;
use Filament\Support\Concerns\EvaluatesClosures;
use Illuminate\Cache\Repository;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Validation\Rules\Password;
use Jeffgreco13\FilamentBreezy\Livewire\BrowserSessions;
use Jeffgreco13\FilamentBreezy\Livewire\PersonalInfo;
use Jeffgreco13\FilamentBreezy\Livewire\SanctumTokens;
use Jeffgreco13\FilamentBreezy\Livewire\TwoFactorAuthentication;
use Jeffgreco13\FilamentBreezy\Livewire\UpdatePassword;
use Jeffgreco13\FilamentBreezy\Middleware\MustTwoFactor;
use Jeffgreco13\FilamentBreezy\Pages\TwoFactorPage;
use Livewire\Livewire;
use PragmaRX\Google2FA\Google2FA;

class BreezyCore implements Plugin
{
    use EvaluatesClosures;

    protected $engine;

    protected $cache;

    protected $myProfile;

    protected $avatarUploadComponent;

    protected $twoFactorAuthentication;

    protected $twoFactorAuthenticationMiddleware = MustTwoFactor::class;

    protected $forceTwoFactorAuthentication;

    protected $twoFactorRouteAction;

    protected $ignoredMyProfileComponents = [];

    protected $registeredMyProfileComponents = [];

    protected $passwordUpdateRules = ['min:8'];

    protected bool $passwordUpdateRequireCurrent = true;

    protected $sanctumTokens = false;

    protected $sanctumPermissions = ['create', 'view', 'update', 'delete'];

    protected $browserSessions = false;

    protected ?string $customMyProfilePageClass = null;

    public function __construct(Google2FA $engine, ?Repository $cache = null)
    {
        $this->engine = $engine;
        $this->cache = $cache;
    }

    public function getId(): string
    {
        return 'filament-breezy';
    }

    public static function make(): static
    {
        return app(static::class);
    }

    public function register(Panel $panel): void
    {
        $panel
            ->pages($this->preparePages());
        // If TwoFactor is enabled, register the middleware.
        if ($this->twoFactorAuthentication) {
            if ($this->twoFactorAuthenticationMiddleware) {
                $panel->authMiddleware([$this->twoFactorAuthenticationMiddleware]);
            }

            Livewire::component('two-factor-page', Pages\TwoFactorPage::class);
        }
    }

    protected function preparePages(): array
    {
        $collection = collect();
        if ($this->myProfile) {
            $collection->push($this->getMyProfilePageClass());
        }

        return $collection->toArray();
    }

    public function boot(Panel $panel): void
    {
        if ($this->myProfile) {
            if ($this->sanctumTokens) {
                Livewire::component('sanctum_tokens', SanctumTokens::class);
                $this->myProfileComponents([
                    'sanctum_tokens' => SanctumTokens::class,
                ]);
            }
            if ($this->twoFactorAuthentication) {
                Livewire::component('two_factor_authentication', TwoFactorAuthentication::class);
                $this->myProfileComponents([
                    'two_factor_authentication' => TwoFactorAuthentication::class,
                ]);
            }
            if ($this->browserSessions) {
                Livewire::component('browser_sessions', BrowserSessions::class);
                $this->myProfileComponents([
                    'browser_sessions' => BrowserSessions::class,
                ]);
            }

            Livewire::component('personal_info', PersonalInfo::class);
            Livewire::component('update_password', UpdatePassword::class);
            $this->myProfileComponents([
                'personal_info' => PersonalInfo::class,
                'update_password' => UpdatePassword::class,
            ]);

            if ($this->myProfile['shouldRegisterUserMenu']) {
                if ($panel->hasTenancy()) {
                    $tenantId = request()->route()->parameter('tenant');
                    if ($tenantId && $tenant = app($panel->getTenantModel())::where($panel->getTenantSlugAttribute() ?? 'id', $tenantId)->first()) {
                        $panel->userMenuItems([
                            'account' => MenuItem::make()->url($this->getMyProfilePageClass()::getUrl(panel: $panel->getId(), tenant: $tenant))->label($this->myProfile['userMenuLabel']),
                        ]);
                    }
                } else {
                    $panel->userMenuItems([
                        'account' => MenuItem::make()->url($this->getMyProfilePageClass()::getUrl())->label($this->myProfile['userMenuLabel']),
                    ]);
                }
            }
        }
    }

    public function auth()
    {
        return Filament::getCurrentPanel()->auth();
    }

    public function getCurrentPanel()
    {
        return Filament::getCurrentPanel();
    }

    public function myProfile(bool $condition = true, bool $shouldRegisterUserMenu = true, bool $shouldRegisterNavigation = false, bool $hasAvatars = false, string $slug = 'my-profile', ?string $navigationGroup = null, ?string $userMenuLabel = null)
    {
        $this->myProfile = get_defined_vars();

        return $this;
    }

    /** @param class-string<Pages\MyProfilePage> $class */
    public function customMyProfilePage(string $class)
    {
        $this->customMyProfilePageClass = $class;

        return $this;
    }

    public function hasAvatars()
    {
        return $this->myProfile['hasAvatars'];
    }

    public function slug()
    {
        return $this->myProfile['slug'];
    }

    public function avatarUploadComponent(Closure $component)
    {
        $this->avatarUploadComponent = $component;

        return $this;
    }

    public function getAvatarUploadComponent()
    {
        $fileUpload = Forms\Components\FileUpload::make('avatar_url')
            ->label(__('filament-breezy::default.fields.avatar'))->avatar();

        return is_null($this->avatarUploadComponent) ? $fileUpload : $this->evaluate($this->avatarUploadComponent, namedInjections: [
            'fileUpload' => $fileUpload,
        ]);
    }

    public function withoutMyProfileComponents(array|Closure $components)
    {
        $this->ignoredMyProfileComponents = is_array($components) ? $components : $this->evaluate($components);

        return $this;
    }

    public function myProfileComponents(array $components)
    {

        $merged = [
            ...$components,
            ...$this->registeredMyProfileComponents,
        ];

        // Ensure we have string keys
        $merged = array_combine(
            array_map('strval', array_keys($merged)),
            array_values($merged)
        );

        $this->registeredMyProfileComponents = $merged;

        return $this;
    }

    public function getRegisteredMyProfileComponents(): array
    {
        $ignoredComponents = is_array($this->ignoredMyProfileComponents)
            ? $this->ignoredMyProfileComponents
            : $this->evaluate($this->ignoredMyProfileComponents);

        $components = collect($this->registeredMyProfileComponents)
            ->filter(
                fn (string $component) => $component::canView()
            )
            ->except($ignoredComponents)
            ->sortBy(
                fn (string $component) => $component::getSort()
            );

        if ($this->shouldForceTwoFactor()) {
            $components = $components->only(['two_factor_authentication']);
        }

        return $components->all();
    }

    public function passwordUpdateRules(array|Password $rules, bool $requiresCurrentPassword = true)
    {
        $this->passwordUpdateRequireCurrent = $requiresCurrentPassword;
        $this->passwordUpdateRules = $rules;

        return $this;
    }

    public function getPasswordUpdateRequiresCurrent()
    {
        return $this->passwordUpdateRequireCurrent;
    }

    public function getPasswordUpdateRules()
    {
        return $this->passwordUpdateRules;
    }

    public function shouldRegisterNavigation(string $key)
    {
        return $this->{$key}['shouldRegisterNavigation'];
    }

    public function getNavigationGroup(string $key)
    {
        return $this->{$key}['navigationGroup'] ?? null;
    }

    public function enableTwoFactorAuthentication(bool $condition = true, bool|Closure $force = false, string|Closure|array|null $action = TwoFactorPage::class, string|false $authMiddleware = MustTwoFactor::class)
    {
        $this->twoFactorAuthentication = $condition;
        $this->forceTwoFactorAuthentication = $force;
        $this->twoFactorRouteAction = $action;
        $this->twoFactorAuthenticationMiddleware = $authMiddleware;

        return $this;
    }

    public function getForceTwoFactorAuthentication(): ?bool
    {
        return $this->evaluate($this->forceTwoFactorAuthentication);
    }

    public function getTwoFactorRouteAction(): string|Closure|array|null
    {
        return $this->twoFactorRouteAction;
    }

    public function getEngine()
    {
        return $this->engine;
    }

    public function generateSecretKey()
    {
        return $this->engine->generateSecretKey();
    }

    public function getTwoFactorQrCodeSvg(string $url)
    {
        $svg = (new Writer(
            new ImageRenderer(
                new RendererStyle(150, 1, null, null, Fill::uniformColor(new Rgb(255, 255, 255), new Rgb(45, 55, 72))),
                new SvgImageBackEnd
            )
        ))->writeString($url);

        return trim(substr($svg, strpos($svg, "\n") + 1));
    }

    public function getQrCodeUrl($companyName, $companyEmail, $secret)
    {
        return $this->engine->getQRCodeUrl($companyName, $companyEmail, $secret);
    }

    public function verify(string $code, ?Authenticatable $user = null)
    {
        if (is_null($user)) {
            $user = Filament::auth()->user();
        }
        $secret = decrypt($user->two_factor_secret);

        $timestamp = $this->engine->verifyKeyNewer(
            $secret,
            $code,
            optional($this->cache)->get($key = 'filament.2fa_codes.'.md5($code))
        );

        if ($timestamp !== false) {
            optional($this->cache)->put($key, $timestamp, ($this->engine->getWindow() ?: 1) * 60);

            return true;
        }

        return false;
    }

    public function shouldForceTwoFactor(): bool
    {
        $forceTwoFactor = $this->getForceTwoFactorAuthentication();

        if ($this->getCurrentPanel()->isEmailVerificationRequired()) {
            return $forceTwoFactor && ! $this->auth()->user()?->hasConfirmedTwoFactor() && $this->auth()->user()?->hasVerifiedEmail();
        }

        return $forceTwoFactor && ! $this->auth()->user()?->hasConfirmedTwoFactor();
    }

    public function enableSanctumTokens(bool $condition = true, null|array|Closure $permissions = null)
    {
        $this->sanctumTokens = $condition;
        if (! is_null($permissions)) {
            $this->sanctumPermissions = $permissions;
        }

        return $this;
    }

    public function getSanctumPermissions(): array
    {
        return collect($this->evaluate($this->sanctumPermissions))->mapWithKeys(function ($item, $key) {
            $key = is_string($key) ? $key : strtolower($item);

            return [$key => $item];
        })->toArray();
    }

    protected function getMyProfilePageClass(): string
    {
        return $this->customMyProfilePageClass ?? Pages\MyProfilePage::class;
    }

    public function enableBrowserSessions(bool $condition = true)
    {
        $this->browserSessions = $condition;

        return $this;
    }
}
