<?php

namespace Modules\LandedCost\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\LandedCost\Entities\LcExpense;
use Modules\LandedCost\Entities\Drivers\LandedCostDriverFactory;
use Modules\LandedCost\Entities\Drivers\LandedCostDriverInterface;
use Modules\LandedCost\Entities\Services\LandedCostDistributionService;
use Modules\LandedCost\Entities\Services\LandedCostApplicationService;

class LandedCostController extends Controller
{
    /** @var LandedCostDistributionService */
    protected $service;

    /** @var LandedCostApplicationService */
    protected $appService;

    public function __construct(LandedCostApplicationService $appService)
    {
        $this->service    = new LandedCostDistributionService();
        $this->appService = $appService;
    }

    // ─── Endpoints ────────────────────────────────────────────────────────────

    public function getExpenses($parentType, $parentRef)
    {
        $driver = $this->driver($parentType, $parentRef);

        $expenses = LcExpense::forParent($driver->parentType(), $driver->parentId())
            ->orderBy('created_at')
            ->get();

        $currencies = $driver->currencyOptions();

        return response()->json([
            'expenses'        => $expenses->map(fn($e) => $this->formatExpense($e, $currencies, $driver)),
            'total_base'      => $this->service->getTotalBaseAmount($driver->parentType(), $driver->parentId()),
            'expense_types'   => LcExpense::TYPES,
            'expense_methods' => LcExpense::METHODS,
            'landed_costs'    => $driver->getLandedCostData(),
            'currencies'      => $currencies,
            'base_symbol'     => $driver->baseSymbol(),
            'applied'         => $this->appService->isApplied($driver),
            'can_apply'       => auth()->user()->can('landed_cost.apply'),
            'applied_warning' => $this->hasSoldStock($driver),
        ]);
    }

    public function storeExpense($parentType, $parentRef, Request $request)
    {
        $driver = $this->driver($parentType, $parentRef);
        $this->validateExpense($request);

        if ($error = $this->customRatioError($request)) {
            return $error;
        }

        $amount    = (float) $request->amount;
        $rateToBase = (float) $request->rate_to_base;

        $expense = LcExpense::create([
            'business_id'         => $driver->businessId(),
            'parent_type'         => $driver->parentType(),
            'parent_id'           => $driver->parentId(),
            'name'                => $request->name,
            'expense_type'        => $request->expense_type,
            'amount'              => $amount,
            'currency_id'         => $request->currency_id ?: null,
            'rate_to_base'        => $rateToBase,
            'base_amount'         => round($amount * $rateToBase, 4),
            'distribution_method' => $request->distribution_method,
            'notes'               => $request->notes,
            'created_by'          => auth()->id(),
        ]);

        $this->distributeAndRecalculate($driver, $expense, $request->input('ratios'));

        return response()->json([
            'success'      => true,
            'message'      => __('landedcost::lang.expense_saved'),
            'expense'      => $this->formatExpense($expense->fresh(), $driver->currencyOptions(), $driver),
            'landed_costs' => $driver->getLandedCostData(),
            'total_base'   => $this->service->getTotalBaseAmount($driver->parentType(), $driver->parentId()),
        ]);
    }

    public function updateExpense($parentType, $parentRef, $expenseId, Request $request)
    {
        $driver  = $this->driver($parentType, $parentRef);
        $expense = $this->findExpense($driver, $expenseId);

        $this->validateExpense($request);

        if ($error = $this->customRatioError($request)) {
            return $error;
        }

        $amount    = (float) $request->amount;
        $rateToBase = (float) $request->rate_to_base;

        $expense->update([
            'name'                => $request->name,
            'expense_type'        => $request->expense_type,
            'amount'              => $amount,
            'currency_id'         => $request->currency_id ?: null,
            'rate_to_base'        => $rateToBase,
            'base_amount'         => round($amount * $rateToBase, 4),
            'distribution_method' => $request->distribution_method,
            'notes'               => $request->notes,
        ]);

        $this->distributeAndRecalculate($driver, $expense, $request->input('ratios'));

        return response()->json([
            'success'      => true,
            'message'      => __('landedcost::lang.expense_updated'),
            'expense'      => $this->formatExpense($expense->fresh(), $driver->currencyOptions(), $driver),
            'landed_costs' => $driver->getLandedCostData(),
            'total_base'   => $this->service->getTotalBaseAmount($driver->parentType(), $driver->parentId()),
        ]);
    }

    public function deleteExpense($parentType, $parentRef, $expenseId)
    {
        $driver  = $this->driver($parentType, $parentRef);
        $expense = $this->findExpense($driver, $expenseId);

        $expense->delete(); // cascades to lc_expense_distributions
        $this->syncLanded($driver);

        return response()->json([
            'success'      => true,
            'message'      => __('landedcost::lang.expense_deleted'),
            'landed_costs' => $driver->getLandedCostData(),
            'total_base'   => $this->service->getTotalBaseAmount($driver->parentType(), $driver->parentId()),
            'applied'      => $this->appService->isApplied($driver),
        ]);
    }

    public function applyLandedCosts($parentType, $parentRef)
    {
        $driver = $this->driver($parentType, $parentRef);
        if (!auth()->user()->can('landed_cost.apply')) {
            abort(403);
        }

        // Make sure landed costs are current before applying (lines still hold original base).
        if (!$this->appService->isApplied($driver)) {
            $driver->recalculateLandedCosts();
        }

        $this->appService->apply($driver);

        return response()->json([
            'success'      => true,
            'applied'      => true,
            'message'      => __('landedcost::lang.landed_cost_applied_msg'),
            'landed_costs' => $driver->getLandedCostData(),
            'total_base'   => $this->service->getTotalBaseAmount($driver->parentType(), $driver->parentId()),
        ]);
    }

    public function rollbackLandedCosts($parentType, $parentRef)
    {
        $driver = $this->driver($parentType, $parentRef);
        if (!auth()->user()->can('landed_cost.apply')) {
            abort(403);
        }

        $this->appService->rollback($driver);
        // Recompute the (display-only) landed cost from the now-restored base.
        $driver->recalculateLandedCosts();

        return response()->json([
            'success'      => true,
            'applied'      => false,
            'message'      => __('landedcost::lang.landed_cost_rolled_back_msg'),
            'landed_costs' => $driver->getLandedCostData(),
            'total_base'   => $this->service->getTotalBaseAmount($driver->parentType(), $driver->parentId()),
        ]);
    }

    public function previewDistribution($parentType, $parentRef, Request $request)
    {
        $driver = $this->driver($parentType, $parentRef);

        $request->validate([
            'amount'              => 'required|numeric|min:0',
            'rate_to_base'        => 'required|numeric|min:0.000001',
            'distribution_method' => 'required|in:equal_qty,equal_value,equal_lines,custom',
            'ratios'              => 'nullable|array',
            'ratios.*'            => 'numeric|min:0|max:100',
        ]);

        $lines = $driver->getLines();
        if ($lines->isEmpty()) {
            return response()->json(['rows' => []]);
        }

        $requestedMethod = $request->distribution_method;
        $incomingRatios  = $request->input('ratios', []);

        // First-load custom with no ratios: seed the preview table with equal_value so the
        // user gets sensible starting percentages to adjust.
        $isCustomSeed = false;
        if ($requestedMethod === 'custom') {
            $hasRatios = !empty($incomingRatios) && array_sum(array_values($incomingRatios)) > 0;
            if (!$hasRatios) {
                $isCustomSeed    = true;
                $requestedMethod = 'equal_value';
            }
        }

        $amount     = (float) $request->amount;
        $rateToBase = (float) $request->rate_to_base;

        $tempExpense = new LcExpense([
            'amount'              => $amount,
            'rate_to_base'        => $rateToBase,
            'base_amount'         => round($amount * $rateToBase, 4),
            'distribution_method' => $requestedMethod,
        ]);

        $rows = $this->service->buildDistributions($tempExpense, $lines, $isCustomSeed ? null : $incomingRatios);

        $lineMap = $lines->keyBy('id');
        $preview = [];
        foreach ($rows as $row) {
            $line = $lineMap[$row['purchase_line_id']] ?? null;
            if (!$line) {
                continue;
            }
            $preview[] = $driver->buildPreviewRow($row, $line);
        }

        return response()->json([
            'rows'           => $preview,
            'is_custom_seed' => $isCustomSeed,
        ]);
    }

    public function saveCustomRatios($parentType, $parentRef, $expenseId, Request $request)
    {
        $driver  = $this->driver($parentType, $parentRef);
        $expense = $this->findExpense($driver, $expenseId);

        $request->validate([
            'ratios'   => 'required|array',
            'ratios.*' => 'numeric|min:0|max:100',
        ]);

        $sum = array_sum($request->input('ratios', []));
        if (abs($sum - 100) > 0.01) {
            return response()->json(['message' => __('landedcost::lang.ratios_must_sum_100')], 422);
        }

        $expense->update(['distribution_method' => 'custom']);
        $this->distributeAndRecalculate($driver, $expense, $request->input('ratios'));

        return response()->json([
            'success'      => true,
            'message'      => __('landedcost::lang.expense_updated'),
            'landed_costs' => $driver->getLandedCostData(),
        ]);
    }

    // ─── Helpers ──────────────────────────────────────────────────────────────

    protected function driver($parentType, $parentRef): LandedCostDriverInterface
    {
        $driver = LandedCostDriverFactory::make($parentType, $parentRef);
        $driver->authorize();
        return $driver;
    }

    protected function findExpense(LandedCostDriverInterface $driver, $expenseId): LcExpense
    {
        return LcExpense::where('id', $expenseId)
            ->where('parent_type', $driver->parentType())
            ->where('parent_id', $driver->parentId())
            ->firstOrFail();
    }

    protected function validateExpense(Request $request): void
    {
        $request->validate([
            'name'                => 'required|string|max:100',
            'expense_type'        => 'required|in:shipping,customs,insurance,handling,freight,other',
            'amount'              => 'required|numeric|min:0.0001',
            'currency_id'         => 'nullable|integer',
            'rate_to_base'        => 'required|numeric|min:0.000001',
            'distribution_method' => 'required|in:equal_qty,equal_value,equal_lines,custom',
            'notes'               => 'nullable|string|max:2000',
            'ratios'              => 'required_if:distribution_method,custom|array',
            'ratios.*'            => 'numeric|min:0|max:100',
        ]);
    }

    protected function customRatioError(Request $request)
    {
        if ($request->distribution_method === 'custom') {
            $sum = array_sum($request->input('ratios', []));
            if (abs($sum - 100) > 0.01) {
                return response()->json(['message' => __('landedcost::lang.ratios_must_sum_100')], 422);
            }
        }
        return null;
    }

    protected function distributeAndRecalculate(LandedCostDriverInterface $driver, LcExpense $expense, ?array $ratios): void
    {
        $lines = $driver->getLines();
        $rows  = $this->service->buildDistributions($expense, $lines, $ratios);
        $this->service->saveDistributions($expense, $rows);
        $this->syncLanded($driver);
    }

    /**
     * Recompute landed cost; if the parent is currently applied, do it as a
     * restore → recompute (from original base) → re-overwrite cycle so the real
     * purchase cost stays in sync without compounding.
     */
    protected function syncLanded(LandedCostDriverInterface $driver): void
    {
        if ($this->appService->isApplied($driver)) {
            $this->appService->reapply($driver, fn() => $driver->recalculateLandedCosts());
        } else {
            $driver->recalculateLandedCosts();
        }
    }

    /** True if any involved purchase line already has stock sold (apply will shift COGS). */
    protected function hasSoldStock(LandedCostDriverInterface $driver): bool
    {
        foreach ($driver->getLines() as $line) {
            if ((float) ($line->quantity_sold ?? 0) > 0) {
                return true;
            }
        }
        return false;
    }

    protected function formatExpense(LcExpense $expense, array $currencies, LandedCostDriverInterface $driver): array
    {
        $symbol = $driver->baseSymbol();
        if ($expense->currency_id) {
            foreach ($currencies as $c) {
                if ((int) $c['id'] === (int) $expense->currency_id) {
                    $symbol = $c['symbol'];
                    break;
                }
            }
        }

        return [
            'id'                  => $expense->id,
            'name'                => $expense->name,
            'expense_type'        => $expense->expense_type,
            'expense_type_label'  => LcExpense::TYPES[$expense->expense_type] ?? $expense->expense_type,
            'amount'              => (float) $expense->amount,
            'currency_id'         => $expense->currency_id,
            'currency_symbol'     => $symbol,
            'rate_to_base'        => (float) $expense->rate_to_base,
            'base_amount'         => (float) $expense->base_amount,
            'distribution_method' => $expense->distribution_method,
            'distribution_label'  => LcExpense::METHODS[$expense->distribution_method] ?? $expense->distribution_method,
            'notes'               => $expense->notes,
            'created_at'          => $expense->created_at ? $expense->created_at->format('Y-m-d H:i') : null,
        ];
    }
}
