<?php

namespace App\Services;

use App\Models\Product;
use App\Models\StockAudit;
use App\Models\StockAuditItem;
use App\Models\StockAdjustment;
use App\Models\StockMovement;
use App\Models\VariantStockMovement;
use Illuminate\Support\Facades\DB;

class StockAuditService
{
    /**
     * Create a new draft audit.
     */
    public function createAudit(array $data): StockAudit
    {
        return StockAudit::create([
            'audit_name' => $data['audit_name'],
            'audit_date' => $data['audit_date'],
            'status' => StockAudit::STATUS_DRAFT,
            'created_by' => auth()->id(),
        ]);
    }

    /**
     * Load products into audit (snapshot system qty). Scope: 'all' | 'category' | 'skus'.
     */
    public function loadProductsIntoAudit(StockAudit $audit, string $scope, ?array $categoryIds = null, ?array $productIds = null): void
    {
        if (!$audit->isDraft()) {
            throw new \InvalidArgumentException('Only draft audits can load products.');
        }

        DB::transaction(function () use ($audit, $scope, $categoryIds, $productIds) {
            $locked = StockAudit::where('id', $audit->id)->lockForUpdate()->first();
            if (!$locked) {
                throw new \InvalidArgumentException('Audit not found.');
            }
            $audit->items()->delete();

            if ($scope === 'skus' && !empty($productIds)) {
                $this->loadProductsByIds($audit, $productIds);
                return;
            }

            $singleQuery = Product::query()
                ->whereNull('parent_id')
                ->where('product_type', 'single');

            if ($scope === 'category' && !empty($categoryIds)) {
                $singleQuery->whereIn('category_id', $categoryIds);
            }

            foreach ($singleQuery->get() as $product) {
                StockAuditItem::create([
                    'audit_id' => $audit->id,
                    'product_id' => $product->id,
                    'variant_id' => null,
                    'system_qty' => (float) $product->current_stock,
                    'physical_qty' => null,
                    'difference_qty' => null,
                ]);
            }

            $variantParentQuery = Product::query()
                ->whereNull('parent_id')
                ->where('product_type', 'variant')
                ->with('variants');

            if ($scope === 'category' && !empty($categoryIds)) {
                $variantParentQuery->whereIn('category_id', $categoryIds);
            }

            foreach ($variantParentQuery->get() as $parent) {
                foreach ($parent->variants as $variant) {
                    StockAuditItem::create([
                        'audit_id' => $audit->id,
                        'product_id' => $parent->id,
                        'variant_id' => $variant->id,
                        'system_qty' => (float) $variant->current_stock,
                        'physical_qty' => null,
                        'difference_qty' => null,
                    ]);
                }
            }
        });
    }

    /**
     * Load specific products by id (each id = single product or variant child).
     */
    private function loadProductsByIds(StockAudit $audit, array $ids): void
    {
        $products = Product::whereIn('id', $ids)->get();
        foreach ($products as $product) {
            if ($product->isVariant()) {
                StockAuditItem::create([
                    'audit_id' => $audit->id,
                    'product_id' => $product->parent_id,
                    'variant_id' => $product->id,
                    'system_qty' => (float) $product->current_stock,
                    'physical_qty' => null,
                    'difference_qty' => null,
                ]);
            } else {
                StockAuditItem::create([
                    'audit_id' => $audit->id,
                    'product_id' => $product->id,
                    'variant_id' => null,
                    'system_qty' => (float) $product->current_stock,
                    'physical_qty' => null,
                    'difference_qty' => null,
                ]);
            }
        }
    }

    /**
     * Update physical quantities and recalc difference. No stock update.
     */
    public function updatePhysicalQuantities(StockAudit $audit, array $items, ?string $remarks = null): void
    {
        if (!$audit->isDraft()) {
            throw new \InvalidArgumentException('Only draft audits can be updated.');
        }

        DB::transaction(function () use ($audit, $items) {
            $locked = StockAudit::where('id', $audit->id)->lockForUpdate()->first();
            if (!$locked) {
                throw new \InvalidArgumentException('Audit not found.');
            }
            foreach ($items as $itemId => $physicalQty) {
                $item = StockAuditItem::where('audit_id', $audit->id)->find($itemId);
                if (!$item) {
                    continue;
                }
                $physical = $physicalQty === null || $physicalQty === '' ? null : (float) $physicalQty;
                if ($physical !== null && $physical < 0) {
                    throw new \InvalidArgumentException("Physical quantity cannot be negative for item #{$itemId}.");
                }
                $item->physical_qty = $physical;
                $item->difference_qty = $physical !== null ? $physical - (float) $item->system_qty : null;
                $item->save();
            }
        });
    }

    /**
     * Generate adjustments, create movement records, update stock, mark audit completed.
     */
    public function generateAndApplyAdjustments(StockAudit $audit, string $remarks): void
    {
        if (!$audit->isDraft()) {
            throw new \InvalidArgumentException('Only draft audits can generate adjustments.');
        }

        if (StockAdjustment::where('reference_type', StockAdjustment::REFERENCE_AUDIT)->where('reference_id', $audit->id)->exists()) {
            throw new \InvalidArgumentException('Adjustments have already been generated for this audit.');
        }

        DB::transaction(function () use ($audit, $remarks) {
            $locked = StockAudit::where('id', $audit->id)->lockForUpdate()->first();
            if (!$locked || $locked->status !== StockAudit::STATUS_DRAFT) {
                throw new \InvalidArgumentException('Audit is no longer in draft status.');
            }

            $items = $audit->items()->whereNotNull('physical_qty')->where('difference_qty', '!=', 0)->get();
            $userId = auth()->id();

            foreach ($items as $item) {
                $diff = (float) $item->difference_qty;
                if ($diff == 0) {
                    continue;
                }

                $effectiveProductId = $item->effective_product_id;
                $product = Product::lockForUpdate()->find($effectiveProductId);
                if (!$product) {
                    continue;
                }

                $beforeQty = (float) $product->current_stock;
                $afterQty = (float) $item->physical_qty;
                $adjustmentType = $diff > 0 ? StockAdjustment::TYPE_ADDITION : StockAdjustment::TYPE_SUBTRACTION;
                $adjustedQty = abs($diff);

                StockAdjustment::create([
                    'product_id' => $item->product_id,
                    'variant_id' => $item->variant_id,
                    'adjustment_type' => $adjustmentType,
                    'before_qty' => $beforeQty,
                    'adjusted_qty' => $adjustedQty,
                    'after_qty' => $afterQty,
                    'reason' => 'Stock audit',
                    'remarks' => $remarks,
                    'reference_type' => StockAdjustment::REFERENCE_AUDIT,
                    'reference_id' => $audit->id,
                    'created_by' => $userId,
                ]);

                $delta = (int) round($afterQty - $beforeQty);
                if ($delta !== 0) {
                    if ($item->variant_id) {
                        VariantStockMovement::create([
                            'product_id' => $item->variant_id,
                            'type' => VariantStockMovement::TYPE_ADJUSTMENT,
                            'quantity' => $delta,
                            'notes' => $remarks,
                            'created_by' => $userId,
                        ]);
                    } else {
                        StockMovement::create([
                            'product_id' => $item->product_id,
                            'type' => StockMovement::TYPE_ADJUSTMENT,
                            'quantity' => $delta,
                            'notes' => $remarks,
                            'created_by' => $userId,
                        ]);
                    }
                    $product->recalculateStock();
                }
            }

            $audit->update(['status' => StockAudit::STATUS_COMPLETED]);
        });
    }
}
