<?php

namespace Modules\ActivityLog\Observers;

use Illuminate\Database\Eloquent\Model;
use Modules\ActivityLog\Entities\Services\ActivityLoggerService;

class EloquentActivityObserver
{
    private static array $stash = [];

    /**
     * Stash for product-location before-state.
     * Keyed by spl_object_id($product).
     */
    private static array $locationStash = [];

    protected ActivityLoggerService $logger;

    public function __construct(ActivityLoggerService $logger)
    {
        $this->logger = $logger;
    }

    public function created(Model $model): void
    {
        $this->logger->logCreated($model);
    }

    public function saving(Model $model): void
    {
        // Only track location changes on existing Products (update scenario)
        if ($model instanceof \App\Product && $model->exists) {
            try {
                self::$locationStash[spl_object_id($model)] = $model
                    ->product_locations()
                    ->pluck('location_id')
                    ->sort()
                    ->values()
                    ->toArray();
            } catch (\Throwable $e) {
                // Non-critical — skip silently
            }
        }
    }

    public function saved(Model $model): void
    {
        // After the model is saved, the controller may call sync() on product_locations.
        // app()->terminating() fires after the full response is sent, by which time
        // sync() is complete — so we compare before vs after locations there.
        if ($model instanceof \App\Product) {
            $key = spl_object_id($model);

            if (!array_key_exists($key, self::$locationStash)) {
                return;
            }

            $locationsBefore = self::$locationStash[$key];
            unset(self::$locationStash[$key]);

            $logger  = $this->logger;
            $product = $model;

            app()->terminating(function () use ($product, $locationsBefore, $logger) {
                try {
                    $locationsAfter = $product
                        ->product_locations()
                        ->pluck('location_id')
                        ->sort()
                        ->values()
                        ->toArray();

                    if ($locationsBefore === $locationsAfter) {
                        return;
                    }

                    // Resolve location IDs to names for a readable log
                    $allIds    = array_unique(array_merge($locationsBefore, $locationsAfter));
                    $nameMap   = \App\BusinessLocation::whereIn('id', $allIds)->pluck('name', 'id');

                    $toNames = fn (array $ids) => collect($ids)
                        ->map(fn ($id) => ($nameMap[$id] ?? '#' . $id))
                        ->join(', ');

                    $logger->logUpdated(
                        $product,
                        ['business_locations' => $toNames($locationsBefore) ?: '(none)'],
                        ['business_locations' => $toNames($locationsAfter)  ?: '(none)']
                    );
                } catch (\Throwable $e) {
                    \Log::error('ActivityLog: product location tracking failed — ' . $e->getMessage());
                }
            });
        }
    }

    public function updating(Model $model): void
    {
        self::$stash[spl_object_id($model)] = [
            'original' => $model->getOriginal(),
            'dirty'    => $model->getDirty(),
        ];
    }

    public function updated(Model $model): void
    {
        $key  = spl_object_id($model);
        $data = self::$stash[$key] ?? [];
        unset(self::$stash[$key]);

        if (!empty($data)) {
            $this->logger->logUpdated($model, $data['original'], $data['dirty']);
        }
    }

    public function deleting(Model $model): void
    {
        $stashData = ['snapshot' => $model->getAttributes()];

        // For purchase and sell transactions, snapshot line items so we can
        // log each product line as a secondary entry after the main delete.
        if ($model instanceof \App\Transaction) {
            $type = $model->getAttributes()['type'] ?? null;

            if (in_array($type, ['purchase', 'purchase_return'], true)) {
                try {
                    $stashData['purchase_lines'] = $model
                        ->purchase_lines()
                        ->with(['product:id,name,sku', 'variation:id,name,sub_sku'])
                        ->get()
                        ->toArray();
                } catch (\Throwable $e) {
                    // Non-critical — skip silently
                }
            } elseif (in_array($type, ['sell', 'sell_return'], true)) {
                try {
                    $stashData['sell_lines'] = $model
                        ->sell_lines()
                        ->with(['product:id,name,sku', 'variation:id,name,sub_sku'])
                        ->get()
                        ->toArray();
                } catch (\Throwable $e) {
                    // Non-critical — skip silently
                }
            }
        }

        self::$stash[spl_object_id($model)] = $stashData;
    }

    public function deleted(Model $model): void
    {
        $key  = spl_object_id($model);
        $data = self::$stash[$key] ?? [];
        unset(self::$stash[$key]);

        $snapshot = $data['snapshot'] ?? $model->getAttributes();
        $this->logger->logDeleted($model, $snapshot);

        // Log individual line items as secondary entries
        if (!empty($data['purchase_lines'])) {
            $this->logger->logDeletedLines($model, $data['purchase_lines'], \App\PurchaseLine::class);
        } elseif (!empty($data['sell_lines'])) {
            $this->logger->logDeletedLines($model, $data['sell_lines'], \App\TransactionSellLine::class);
        }
    }
}
