<?php

namespace App\Services\Tenancy;

use App\Models\BranchStock;
use App\Models\Catalog\MainCategory;
use App\Models\Catalog\Product;
use App\Models\Catalog\ProductBundle;
use App\Models\Central\Tenant;
use App\Models\Inventory\ProductSerial;
use App\Models\Inventory\StockBatch;
use App\Models\Purchasing\PurchaseOrderItem;
use App\Services\CurrentContext;
use Illuminate\Support\Facades\DB;

/**
 * Master Control's one-click "reset product catalog" — for a tenant that
 * needs a clean catalog to start over (e.g. leftover demo/test data before
 * real onboarding). Deliberately narrow: only catalog + stock data goes.
 * Roles, permissions, users, branches and tills are never touched, and
 * neither is any transactional/financial record (invoices, sales, held
 * sales, customers, loyalty, returns, purchase order headers, GRNs) — see
 * EditTenant::getHeaderActions()'s "resetCatalog" action for the exact
 * boundary this was agreed on with the client.
 *
 * The "Packaging & Add-ons" system category is kept, but emptied: its own
 * products are wiped along with everything else, only the category row
 * itself (MainCategory::packaging()) survives, since the POS depends on it
 * existing (see MainCategory::SYSTEM_PACKAGING).
 *
 * Deletion order follows the FK graph on `products.id`, verified against
 * every migration that references it:
 *  - bundle_items, stock_batches, product_serials and purchase_order_items
 *    are `restrictOnDelete` — MySQL refuses to delete a product while any of
 *    these still point at it, so they must be cleared first.
 *  - product_units and product_attributes are `cascadeOnDelete` — the
 *    database removes them automatically when the product goes.
 *  - invoice_items, held_sale_items, grn_items, return_items, damages,
 *    stock_adjustments, stock_transfers and sales_order_items are all
 *    `nullOnDelete` — they already store their own product name/sku/barcode
 *    snapshot for historical display, so nulling the FK doesn't break them.
 *    Nothing to do there.
 *  - branch_stock and stock_movements carry no FK on product_id at all
 *    (never added, per the drop_product_variants migration). branch_stock
 *    is current-state, not history, so stale rows for a deleted product are
 *    cleaned up here; stock_movements is the immutable Bin Card audit trail
 *    and is deliberately left alone.
 */
class TenantCatalogResetService
{
    /**
     * @return array<string, int> counts of what was removed, for the audit log/notification.
     */
    public function reset(Tenant $tenant): array
    {
        return app(CurrentContext::class)->runForTenant(
            $tenant,
            fn () => DB::transaction(fn () => $this->wipe($tenant))
        );
    }

    /**
     * @return array<string, int>
     */
    private function wipe(Tenant $tenant): array
    {
        // Product and MainCategory carry no branch scope (a product/category
        // is tenant-wide, stock is what's per-branch), so the normal
        // tenant-scoped query — via runForTenant() — is already correct here.
        $packagingId = MainCategory::packaging()?->id;

        $productIds = Product::withTrashed()
            ->when(
                $packagingId,
                fn ($q) => $q->where(fn ($q) => $q->whereNull('main_category_id')->orWhere('main_category_id', '!=', $packagingId)),
            )
            ->pluck('id');

        // ProductBundle, StockBatch and ProductSerial additionally carry a
        // branch scope (NullableBranchScoped / BranchScoped), which this
        // landlord-panel operation never binds a "current branch" for. Left
        // alone, that scope would silently narrow each query to nothing (or
        // to whatever branch happened to be bound) instead of covering every
        // branch — so every global scope is stripped and the tenant filter
        // is re-applied by hand instead of relying on TenantScope's own
        // resolution.
        $tenantId = $tenant->id;

        // Bundles are entirely catalog data — every one goes, regardless of
        // which products they contain. Deleting the parent row cascades
        // bundle_items at the database level, which also clears the
        // restrictOnDelete blocker those rows hold against products below.
        $bundleCount = ProductBundle::withoutGlobalScopes()->where('tenant_id', $tenantId)->count();
        ProductBundle::withoutGlobalScopes()->where('tenant_id', $tenantId)->forceDelete();

        PurchaseOrderItem::whereIn('product_id', $productIds)->delete();

        $serialQuery = fn () => ProductSerial::withoutGlobalScopes()
            ->where('tenant_id', $tenantId)
            ->whereIn('product_id', $productIds);
        $serialCount = $serialQuery()->count();
        $serialQuery()->forceDelete();

        $batchQuery = fn () => StockBatch::withoutGlobalScopes()
            ->where('tenant_id', $tenantId)
            ->whereIn('product_id', $productIds);
        $batchCount = $batchQuery()->count();
        $batchQuery()->forceDelete();

        BranchStock::withoutGlobalScopes()
            ->where('tenant_id', $tenantId)
            ->whereIn('product_id', $productIds)
            ->delete();

        $productCount = $productIds->count();
        Product::withTrashed()->whereIn('id', $productIds)->forceDelete();

        // Cascades remaining sub_categories at the database level.
        $categoryCount = MainCategory::withTrashed()->where('is_system', false)->count();
        MainCategory::withTrashed()->where('is_system', false)->forceDelete();

        return [
            'products' => $productCount,
            'categories' => $categoryCount,
            'bundles' => $bundleCount,
            'stock_batches' => $batchCount,
            'product_serials' => $serialCount,
        ];
    }
}
