<?php

namespace Modules\LandedCost\Entities\Drivers;

use App\PurchaseLine;
use Illuminate\Support\Collection;
use Modules\PurchaseRequisition\Entities\PrPurchaseShare;
use Modules\PurchaseRequisition\Entities\PrSupplierProduct;
use Modules\PurchaseRequisition\Entities\PrCurrency;

/**
 * Driver for PurchaseRequisition purchase shares.
 *
 * Preserves the original PR behaviour: lines are the PR-sourced purchase_lines
 * (purchase_requisition_line_id not null), the base currency is EGP, base prices and
 * supplier-currency rates come from the approved pr_supplier_products, and landed cost
 * is written back to pr_supplier_products.landed_unit_cost(_egp) so existing PR views
 * keep working unchanged.
 *
 * PurchaseRequisition classes are referenced lazily (only this driver is constructed
 * for parent_type=pr_share), so the LandedCost module carries no hard dependency on PR.
 */
class PrShareDriver extends AbstractLandedCostDriver
{
    /** @var PrPurchaseShare */
    protected $share;

    /** @var \Illuminate\Support\Collection|null memoized supplier products keyed by pr_product_id */
    protected $supplierProducts = null;

    public function __construct($token)
    {
        $this->share = PrPurchaseShare::where('share_token', $token)
            ->with('transaction')
            ->firstOrFail();
    }

    public function authorize(): void
    {
        if (!auth()->check() ||
            (!auth()->user()->can('purchase_requisition.make_purchase') &&
             !auth()->user()->can('purchase_requisition.view_all'))) {
            abort(403);
        }
    }

    public function parentType(): string
    {
        return 'pr_share';
    }

    public function parentId(): int
    {
        return (int) $this->share->id;
    }

    public function businessId(): ?int
    {
        return $this->share->business_id ? (int) $this->share->business_id : null;
    }

    public function subjectModel(): ?\Illuminate\Database\Eloquent\Model
    {
        return $this->share->transaction;
    }

    public function getLines(): Collection
    {
        return PurchaseLine::where('transaction_id', $this->share->transaction_id)
            ->whereNotNull('purchase_requisition_line_id')
            ->with('product', 'variations')
            ->get();
    }

    public function baseSymbol(): string
    {
        return 'EGP';
    }

    public function currencyOptions(): array
    {
        return PrCurrency::forBusiness($this->businessId())->active()->get()->map(function ($c) {
            return [
                'id'     => $c->id,
                'name'   => $c->name . ' (' . $c->symbol . ')',
                'symbol' => $c->symbol,
                'rate'   => (float) $c->conversion_rate_to_egp,
            ];
        })->toArray();
    }

    /** Approved supplier products for this share's supplier, keyed by pr_product_id. */
    protected function supplierProducts()
    {
        if ($this->supplierProducts !== null) {
            return $this->supplierProducts;
        }

        $supplierId   = $this->share->transaction->contact_id;
        $prProductIds = $this->getLines()->pluck('purchase_requisition_line_id')->filter()->unique();

        $this->supplierProducts = PrSupplierProduct::whereIn('pr_product_id', $prProductIds)
            ->where('approved', true)
            ->whereHas('supplierResponse', function ($q) use ($supplierId) {
                $q->where('supplier_id', $supplierId)->where('status', 'approved');
            })
            ->with('supplierResponse.currency', 'prProduct.product', 'prProduct.variation')
            ->get()
            ->keyBy('pr_product_id');

        return $this->supplierProducts;
    }

    public function buildPreviewRow(array $row, $line): array
    {
        $sp       = $this->supplierProducts()[$line->purchase_requisition_line_id] ?? null;
        $currency = $sp ? optional($sp->supplierResponse)->currency : null;
        $symbol   = $currency ? $currency->symbol : 'EGP';
        $rate     = $currency ? (float) $currency->conversion_rate_to_egp : 1;
        $rate     = $rate ?: 1;

        $qty             = (float) $line->quantity;
        $unitExpenseBase = $qty > 0 ? ((float) $row['allocated_base_amount'] / $qty) : 0;
        $basePrice       = $sp ? (float) $sp->supplier_price : (float) $line->purchase_price;
        $basePriceBase   = $sp ? (float) $sp->converted_price_egp : (float) $line->purchase_price;
        $landedCost      = $basePrice + ($unitExpenseBase / $rate);
        $landedCostBase  = $basePriceBase + $unitExpenseBase;

        $prProduct = $sp ? $sp->prProduct : null;
        $product   = $prProduct ? $prProduct->product   : $line->product;
        $variation = $prProduct ? $prProduct->variation : $line->variations;

        return [
            'purchase_line_id'   => $row['purchase_line_id'],
            'product_name'       => $this->productLabel($product, $variation),
            'order_qty'          => $qty,
            'distribution_ratio' => round($row['distribution_ratio'] * 100, 2),
            'allocated_amount'   => $row['allocated_amount'],
            'allocated_base'     => $row['allocated_base_amount'],
            'base_price'         => round($basePrice, 4),
            'landed_cost'        => round($landedCost, 4),
            'landed_cost_base'   => round($landedCostBase, 4),
            'currency_symbol'    => $symbol,
        ];
    }

    public function recalculateLandedCosts(): void
    {
        $allocations = $this->allocationsByLine();
        $supplierId  = $this->share->transaction->contact_id;
        $lines       = $this->getLines();

        foreach ($lines as $line) {
            $prProductId = $line->purchase_requisition_line_id;
            if (!$prProductId) {
                continue;
            }

            $totalBase       = (float) ($allocations[$line->id] ?? 0);
            $qty             = (float) $line->quantity;
            $unitExpenseBase = $qty > 0 ? ($totalBase / $qty) : 0;

            $sp = PrSupplierProduct::where('pr_product_id', $prProductId)
                ->where('approved', true)
                ->whereHas('supplierResponse', function ($q) use ($supplierId) {
                    $q->where('supplier_id', $supplierId)->where('status', 'approved');
                })
                ->with('supplierResponse.currency')
                ->first();

            if ($sp) {
                $rate = optional(optional($sp->supplierResponse)->currency)->conversion_rate_to_egp ?? 1;
                $rate = (float) $rate ?: 1;
                $landedUnitCost     = (float) $sp->supplier_price + ($unitExpenseBase / $rate);
                $landedUnitCostBase = (float) $sp->converted_price_egp + $unitExpenseBase;

                $sp->update([
                    'landed_unit_cost'     => round($landedUnitCost, 4),
                    'landed_unit_cost_egp' => round($landedUnitCostBase, 4),
                ]);
            }
        }
    }

    public function getLandedCostData(): array
    {
        $supplierId = $this->share->transaction->contact_id;
        $lines      = $this->getLines();

        $prProductIds = $lines->pluck('purchase_requisition_line_id')->filter()->unique();
        $supplierProducts = PrSupplierProduct::whereIn('pr_product_id', $prProductIds)
            ->where('approved', true)
            ->whereHas('supplierResponse', function ($q) use ($supplierId) {
                $q->where('supplier_id', $supplierId)->where('status', 'approved');
            })
            ->get()
            ->keyBy('pr_product_id');

        $result = [];
        foreach ($lines as $line) {
            $sp = $supplierProducts[$line->purchase_requisition_line_id] ?? null;
            if ($sp) {
                $result[$line->id] = [
                    'landed_unit_cost'      => (float) $sp->landed_unit_cost,
                    'landed_unit_cost_base' => (float) $sp->landed_unit_cost_egp,
                ];
            }
        }
        return $result;
    }
}
