<?php

namespace App\Providers;

use App\Core\Hook\HookManager;
use App\Core\Hook\HookBridge;
use App\Models\ClientDomain;
use App\Models\ClientProduct;
use App\Services\Tracking\HashedUser;
use App\Services\Tracking\TrackingEvent;
use App\Services\Tracking\TrackingService;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\ServiceProvider;

class HookServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(HookManager::class);
        $this->app->singleton(\App\Core\Hook\HookBridge::class);
    }

    public function boot(): void
    {
        Blade::directive('hook', function (string $expression): string {
            return "<?php echo app(\\App\\Core\\Hook\\HookManager::class)->render(
                {$expression},
                [],
                app()->bound(\\App\\Services\\Consent\\ConsentService::class)
                    ? app(\\App\\Services\\Consent\\ConsentService::class)->allowedCategories()
                    : ['necessary', 'marketing']
            ); ?>";
        });

        /** @var HookManager $hooks */
        $hooks = $this->app->make(HookManager::class);

        foreach ($this->discoverHookClasses() as $class) {
            try {
                $instance = $this->app->make($class);
                if (method_exists($instance, 'isEnabled') && !$instance->isEnabled()) {
                    continue;
                }
                $instance->boot($hooks);
            } catch (\Throwable $e) {
                Log::channel('tracking')->error('Failed to boot hook class', [
                    'class' => $class,
                    'exception' => $e->getMessage(),
                ]);
            }
        }

        // Bridge existing Laravel events to action hooks — so tracking fires
        // automatically without duplicating event firing code in controllers.
        $this->bridgeInvoicePaidEvent($hooks);

        // Bridge Laravel events to WHMCS-named lifecycle hooks.
        $this->bridgeLifecycleEvents($hooks);

        // Wire tracking dispatches onto action hooks.
        $this->registerTrackingActionListeners($hooks);

        // PRC-622: config/communication_events.php 'hooks' haritası →
        // action hook listener'ları (mapper → CommunicationEvent dispatch).
        $this->app->make(\App\Services\Communication\HookCommunicationBridge::class)->register();
    }

    /**
     * Bridge existing Laravel domain/product/invoice events to WHMCS-named action hooks.
     *
     * Each event is translated into one or more WHMCS hook fires so that
     * third-party integrations can respond to lifecycle events using the
     * familiar WHMCS hook naming convention.
     *
     * Property mapping notes (events use provisioningId + entityId pattern):
     *   - ProductProvisioningCompleted/Failed: entityId = ClientProduct.id, action = 'create'|'suspend'|…
     *   - DomainProvisioningCompleted/Failed:  entityId = ClientDomain.id,  action = 'register'|'transfer'|…
     *   - InvoicePaid: carries the Invoice model directly via $event->invoice
     */
    private function bridgeLifecycleEvents(HookManager $hooks): void
    {
        /** @var HookBridge $bridge */
        $bridge = app(HookBridge::class);

        // InvoicePaid → InvoicePaidPreEmail + InvoicePaid + AddInvoicePayment
        Event::listen(\App\Events\InvoicePaid::class, function ($event) use ($bridge) {
            $ctx = $bridge->buildInvoiceContext($event->invoice);
            $bridge->fireAction('InvoicePaidPreEmail', $ctx);
            $bridge->fireAction('InvoicePaid', $ctx);
            $bridge->fireAction('AddInvoicePayment', $ctx);
        });

        // ProductProvisioningCompleted → AfterModuleCreate (only for 'create' action)
        Event::listen(\App\Events\ProductProvisioningCompleted::class, function ($event) use ($bridge) {
            if (($event->action ?? null) !== 'create') {
                return; // other actions handled separately (Task 16)
            }
            if (! $event->entityId) {
                return;
            }
            $service = ClientProduct::find($event->entityId);
            if (! $service) {
                return;
            }
            $bridge->fireAction('AfterModuleCreate', array_merge(
                $bridge->buildServiceContext($service),
                ['params' => $event->result ?? []]
            ));
        });

        // ProductProvisioningFailed → AfterModuleCreateFailed (only for 'create' action)
        Event::listen(\App\Events\ProductProvisioningFailed::class, function ($event) use ($bridge) {
            if (($event->action ?? null) !== 'create') {
                return; // other actions handled separately (Task 16)
            }
            if (! $event->entityId) {
                return;
            }
            $service = ClientProduct::find($event->entityId);
            if (! $service) {
                return;
            }
            $bridge->fireAction('AfterModuleCreateFailed', array_merge(
                $bridge->buildServiceContext($service),
                [
                    'params' => [],
                    'failureResponseMessage' => $event->message ?? '',
                ]
            ));
        });

        // ProductProvisioningCompleted → AfterModuleSuspend / AfterModuleTerminate / AfterModuleUnsuspend
        Event::listen(\App\Events\ProductProvisioningCompleted::class, function ($event) use ($bridge) {
            $actionMap = [
                'suspend'   => 'AfterModuleSuspend',
                'terminate' => 'AfterModuleTerminate',
                'unsuspend' => 'AfterModuleUnsuspend',
            ];

            $hookName = $actionMap[$event->action ?? ''] ?? null;
            if (! $hookName || ! $event->entityId) {
                return;
            }

            $service = ClientProduct::find($event->entityId);
            if (! $service) {
                return;
            }

            $bridge->fireAction($hookName, array_merge(
                $bridge->buildServiceContext($service),
                ['params' => $event->result ?? []]
            ));
        });

        // ProductProvisioningFailed → AfterModuleSuspendFailed / AfterModuleTerminateFailed / AfterModuleUnsuspendFailed
        Event::listen(\App\Events\ProductProvisioningFailed::class, function ($event) use ($bridge) {
            $actionMap = [
                'suspend'   => 'AfterModuleSuspendFailed',
                'terminate' => 'AfterModuleTerminateFailed',
                'unsuspend' => 'AfterModuleUnsuspendFailed',
            ];

            $hookName = $actionMap[$event->action ?? ''] ?? null;
            if (! $hookName || ! $event->entityId) {
                return;
            }

            $service = ClientProduct::find($event->entityId);
            if (! $service) {
                return;
            }

            $bridge->fireAction($hookName, array_merge(
                $bridge->buildServiceContext($service),
                [
                    'params' => [],
                    'failureResponseMessage' => $event->message ?? '',
                ]
            ));
        });

        // DomainProvisioningCompleted → DomainTransferCompleted (only when action=transfer)
        Event::listen(\App\Events\DomainProvisioningCompleted::class, function ($event) use ($bridge) {
            if (($event->action ?? null) !== 'transfer') {
                return;
            }
            if (! $event->entityId) {
                return;
            }
            $domain = ClientDomain::find($event->entityId);
            if (! $domain) {
                return;
            }
            $bridge->fireAction('DomainTransferCompleted', $bridge->buildDomainContext($domain, [
                'domainId'           => $domain->id,
                'registrationPeriod' => $domain->period ?? null,
                'expiryDate'         => optional($domain->expire_date)->toDateTimeString(),
                'registrar'          => $domain->tld->registrar ?? null,
            ]));
        });

        // DomainProvisioningFailed → DomainTransferFailed (only when action=transfer)
        Event::listen(\App\Events\DomainProvisioningFailed::class, function ($event) use ($bridge) {
            if (($event->action ?? null) !== 'transfer') {
                return;
            }
            if (! $event->entityId) {
                return;
            }
            $domain = ClientDomain::find($event->entityId);
            if (! $domain) {
                return;
            }
            $bridge->fireAction('DomainTransferFailed', $bridge->buildDomainContext($domain, [
                'domainId'            => $domain->id,
                'failureResponseMessage' => $event->message ?? '',
            ]));
        });
    }

    private function bridgeInvoicePaidEvent(HookManager $hooks): void
    {
        Event::listen(\App\Events\InvoicePaid::class, function ($event) use ($hooks) {
            $invoice = $event->invoice;
            $client = $invoice->client ?? null;

            $hooks->action('AfterShoppingCartCheckout', [
                'invoice' => $invoice,
                'client' => $client,
                'payment_method' => $invoice->payment_method ?? null,
            ]);
        });
    }

    private function registerTrackingActionListeners(HookManager $hooks): void
    {
        // Purchase — fires on invoice paid
        $hooks->listenAction('AfterShoppingCartCheckout', function (array $ctx) {
            /** @var \App\Models\Invoice $invoice */
            $invoice = $ctx['invoice'];
            $client = $ctx['client'] ?? null;

            $user = $this->hashedUserFromRequest($client);

            $event = new TrackingEvent(
                name: 'Purchase',
                eventId: $this->eventIdFromRequest(),
                params: [
                    'value' => (float) ($invoice->total_amount ?? $invoice->total ?? 0),
                    'currency' => $invoice->currency->code ?? 'TRY',
                    'transaction_id' => 'INV-' . $invoice->id,
                    'contents' => \App\Services\Tracking\InvoiceItemMapper::toTrackingContents($invoice),
                ],
                user: $user,
                sourceUrl: request()?->header('referer'),
                timestamp: now()->timestamp,
            );

            app(TrackingService::class)->dispatch($event);
        });

        // AddToCart
        $hooks->listenAction('store.cart.add', function (array $ctx) {
            $params = [];
            if (!empty($ctx['product'])) {
                $product = $ctx['product'];
                $quantity = (int) ($ctx['quantity'] ?? 1);
                // Prefer price supplied by the caller (computed per-currency in controller);
                // fall back to product->price if it exists on the model.
                $unitPrice = isset($ctx['price']) ? (float) $ctx['price'] : (float) ($product->price ?? 0);
                $params['contents'] = [[
                    'id' => (string) ($product->id ?? ''),
                    'quantity' => $quantity,
                    'item_price' => $unitPrice,
                ]];
                $params['value'] = $unitPrice * $quantity;
                $params['currency'] = $ctx['currency'] ?? 'TRY';
            }
            app(TrackingService::class)->dispatch($this->makeEvent('AddToCart', $params, $ctx['client'] ?? null));
        });

        // InitiateCheckout
        $hooks->listenAction('PreShoppingCartCheckout', function (array $ctx) {
            $basket = $ctx['basket'] ?? null;
            $params = [
                'value' => (float) ($basket->total ?? 0),
                'currency' => $basket->currency ?? 'TRY',
            ];
            app(TrackingService::class)->dispatch($this->makeEvent('InitiateCheckout', $params, $ctx['client'] ?? null));
        });

        // CompleteRegistration
        $hooks->listenAction('ClientAdd', function (array $ctx) {
            app(TrackingService::class)->dispatch(
                $this->makeEvent('CompleteRegistration', ['method' => 'email'], $ctx['client'] ?? null)
            );
        });

        // Lead
        $hooks->listenAction('store.contact.submit', function (array $ctx) {
            app(TrackingService::class)->dispatch(
                $this->makeEvent('Lead', [], $ctx['client'] ?? null)
            );
        });

        // ViewContent — fires on store product-browse page (unauthenticated catalog view)
        $hooks->listenAction('StoreProductDetails', function (array $ctx) {
            $product = $ctx['product'] ?? null;
            if (!$product) return;
            $params = [
                'contents' => [[
                    'id' => (string) $product->id,
                    'quantity' => 1,
                    'item_price' => (float) ($product->price ?? 0),
                ]],
                'value' => (float) ($product->price ?? 0),
                'currency' => 'TRY',
            ];
            app(TrackingService::class)->dispatch($this->makeEvent('ViewContent', $params, $ctx['client'] ?? null));
        });
    }

    private function hashedUserFromRequest(?object $client): ?HashedUser
    {
        $request = request();
        if (!$request) return null;
        return HashedUser::fromRequest($request, $client);
    }

    private function eventIdFromRequest(): ?string
    {
        $request = request();
        if (!$request) return null;
        return $request->header('X-Event-Id') ?: $request->input('event_id');
    }

    private function makeEvent(string $name, array $params, ?object $client): TrackingEvent
    {
        return new TrackingEvent(
            name: $name,
            eventId: $this->eventIdFromRequest(),
            params: $params,
            user: $this->hashedUserFromRequest($client),
            sourceUrl: request()?->header('referer'),
            timestamp: now()->timestamp,
        );
    }

    /**
     * Discover hook classes by scanning filesystem.
     * - app/Hooks/*Hook.php (flat)
     * - app/Hooks/*\/*Hook.php (1 subdir)
     * - app/Modules/*\/*\/Hooks/*Hook.php (module convention, 2-level module depth)
     *
     * @return array<int, class-string>
     */
    private function discoverHookClasses(): array
    {
        $patterns = [
            app_path('Hooks/*Hook.php'),
            app_path('Hooks/*/*Hook.php'),
            app_path('Modules/*/*/Hooks/*Hook.php'),
        ];

        $classes = [];
        foreach ($patterns as $glob) {
            foreach (glob($glob) ?: [] as $file) {
                $class = $this->pathToClass($file);
                if ($class === null) continue;
                if (!class_exists($class)) continue;
                $implements = class_implements($class) ?: [];
                if (in_array(\App\Core\Hook\Contracts\Hook::class, $implements, true)) {
                    $classes[] = $class;
                }
            }
        }

        $classes = array_unique($classes);
        sort($classes); // deterministic boot order

        // Filter out hooks belonging to inactive addons
        $classes = array_filter($classes, fn ($cls) => ! $this->isFromInactiveAddon($cls));
        $classes = array_values($classes);  // re-index after filter

        return $classes;
    }

    /**
     * Determine whether a hook class belongs to an INACTIVE addon module.
     *
     * Returns false (keep) for non-addon hooks. Returns true (reject) when
     * the class lives under App\Modules\Addon\{Folder}\... and the matching
     * addon is either not currently active or has no manifest at all
     * (orphan Hooks/ class).
     */
    protected function isFromInactiveAddon(string $class): bool
    {
        if (! preg_match('#^App\\\\Modules\\\\Addon\\\\([^\\\\]+)\\\\#', $class, $m)) {
            return false; // not an addon hook → keep
        }
        if (! $this->app->bound(\App\Core\Addon\AddonManager::class)) {
            return false; // safe default if addon system not booted
        }
        /** @var \App\Core\Addon\AddonManager $manager */
        $manager = $this->app->make(\App\Core\Addon\AddonManager::class);
        $folder = $m[1];

        foreach ($manager->all() as $addon) {
            if (\Illuminate\Support\Str::studly($addon->name()) === $folder) {
                return ! $manager->isActive($addon->name());
            }
        }
        return true; // unknown addon (orphan hook) → reject
    }

    private function pathToClass(string $path): ?string
    {
        $relative = substr($path, strlen(app_path()) + 1);  // strip "app/"
        $relative = substr($relative, 0, -4);                // strip ".php"
        $relative = str_replace('/', '\\', $relative);
        return 'App\\' . $relative;
    }
}
