<?php

namespace App\Http\Controllers\Crm;

use App\Http\Controllers\Controller;
use App\Models\LiftProduct;
use App\Models\LiftProductVariant;
use App\Models\Proposal;
use App\Models\ProposalClient;
use App\Models\ProposalHistory;
use App\Models\RoleDiscountLimit;
use App\Models\User;
use App\Services\ProposalApprovalService;
use App\Services\ProposalUpgradationService;
use App\Services\RevisionOtpGate;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;

/**
 * Port of the standalone portal's ProposalController@create/store/show/edit/update/destroy
 * plus the approval-workflow action wrappers, adapted to this CRM (Spatie roles, real
 * Customer/Proposal separation, in-app notifications). E-signature capture and OTP-gated
 * download remain out of scope for this pass; a plain PDF export is provided via download().
 */
class ProposalController extends Controller
{
    /**
     * Delivery schedules per delivery type, with their (GST-inclusive) fast-track charge.
     * The charge is always taken from here, never from the submitted delivery_amount.
     */
    protected const DELIVERY_TERMS = [
        'standard' => ['5_months' => 0],
        'fast_track' => ['3_months_4l' => 400000, '2_months_5l' => 500000],
    ];

    /**
     * The pre-catalog Luxe / à-la-carte upgrade columns. The create form no longer submits
     * them (selected_upgradations is the source of truth), so every save clears them
     * instead of leaving stale values behind — e.g. on a revision replicated from a
     * legacy proposal.
     */
    protected const CLEARED_LEGACY_UPGRADATION_COLUMNS = [
        'lux_package' => null, 'luxe_package_price' => 0,
        'door_id' => null, 'door_count' => 0, 'door_price' => 0,
        'color_id' => null, 'color_price' => 0,
        'swing_id' => null, 'swing_price' => 0,
        'cop_id' => null, 'cop_price' => 0,
        'extra_travel_height_id' => null, 'extra_travel_height_price' => 0,
        'sos_id' => null, 'sos_price' => 0,
    ];

    public function __construct(
        protected ProposalApprovalService $approvals,
        protected ProposalUpgradationService $upgradations,
    ) {
    }

    public function index(Request $request): RedirectResponse
    {
        return redirect()->route('crm.quotations.index');
    }

    public function create(Request $request): View
    {
        abort_unless($this->approvals->canCreateProposal($request->user()), 403);

        $catalogProducts = $this->catalogProducts();
        $discountMax = $this->creationDiscountLimit($request->user());
        $country = DB::table('countries')->where('name', 'India')->first() ?? DB::table('countries')->first();
        $warrantyYearOptions = $this->warrantyYearOptions($request->user());

        return view('crm.proposals.create', compact('catalogProducts', 'discountMax', 'country', 'warrantyYearOptions'));
    }

    /**
     * The active catalog embedded in the create/edit form, which cascades it locally
     * (category → product type → technology → model). Each product carries its
     * server-derived upgrade panel so the browser never re-implements eligibility.
     */
    protected function catalogProducts(): array
    {
        return LiftProduct::catalog()->get()->map(fn (LiftProduct $product) => [
            'id' => $product->id,
            'product_code' => $product->product_code,
            'name' => $product->name,
            'elevator_category' => $product->elevator_category,
            'type' => $product->type,
            'technology' => $product->technology,
            'power_phase' => $product->power_phase,
            'motor_required' => ! $product->isStairlift(),
            'upgrades' => $this->upgradations->forForm($product),
        ])->values()->all();
    }

    public function store(Request $request): RedirectResponse
    {
        $user = $request->user();
        abort_unless($this->approvals->canCreateProposal($user), 403);

        $discountMax = $this->creationDiscountLimit($user);
        $validated = $this->validateProposal($request, $discountMax);
        $validated = $this->applySameAsBillingAddress($validated);

        [
            'variant' => $variant, 'billingAddress' => $billingAddress, 'siteAddress' => $siteAddress, 'upg' => $upg,
            'installationCost' => $installationCost, 'transportCost' => $transportCost, 'unloadingCost' => $unloadingCost,
            'gstPercentage' => $gstPercentage, 'requestedPrice' => $requestedPrice, 'discountAmount' => $discountAmount,
            'deliveryAmount' => $deliveryAmount, 'gstAmount' => $gstAmount, 'finalAmount' => $finalAmount,
            'discountPercentage' => $discountPercentage, 'motorType' => $motorType,
        ] = $this->buildPricingContext($validated);

        $proposal = DB::transaction(function () use (
            $request, $user, $validated, $variant, $billingAddress, $siteAddress,
            $upg, $installationCost, $transportCost,
            $unloadingCost, $gstPercentage, $gstAmount, $finalAmount, $requestedPrice,
            $discountAmount, $discountPercentage, $deliveryAmount, $motorType
        ) {
            $client = ProposalClient::create([
                'client_code' => 'CLI-'.time(),
                'company_name' => $validated['contact_person'],
                'salutation' => $validated['salutation'] ?? null,
                'contact_person' => $validated['contact_person'],
                'phone' => trim($validated['phone_country_code'].' '.$validated['phone']),
                'alternate_phone' => $validated['alternate_phone'] ?? null,
                'email' => $validated['email'] ?? null,
                'country_id' => $validated['billing_country_id'],
                'billing_address' => $billingAddress,
                'site_address' => $siteAddress,
                'pincode' => $validated['billing_pincode'],
                'gst_number' => $validated['gst_number'] ?? null,
                'assigned_bde_user_id' => $user->id,
                'created_by_user_id' => $user->id,
                'status' => 'lead',
            ]);

            $approvalLevel = match (true) {
                $discountAmount > 300000 => 'vice_president',
                $discountAmount > 250000 => 'state_director',
                $discountAmount > 100000 => 'regional_manager',
                default => 'business_development_manager',
            };

            $proposal = Proposal::create([
                'proposal_number' => $this->generateProposalNumber($validated['billing_state_id'] ?? null),
                'reference_number' => 'REF-'.random_int(1000, 9999),
                'client_id' => $client->id,
                'product_id' => $validated['product_id'],
                'created_by_user_id' => $user->id,
                'assigned_bde_user_id' => $user->id,
                'billing_address' => $billingAddress,
                'site_address' => $siteAddress,
                'same_as_billing_address' => (bool) ($validated['same_as_billing_address'] ?? false),
                'billing_country_id' => $validated['billing_country_id'],
                'billing_state_id' => $validated['billing_state_id'],
                'billing_district_id' => $validated['billing_district_id'],
                'billing_city' => $validated['billing_city_id'],
                'billing_area' => $validated['billing_area'] ?? null,
                'billing_address1' => $validated['billing_address1'],
                'billing_landmark' => $validated['billing_landmark'] ?? null,
                'site_country_id' => $validated['site_country_id'] ?? $validated['billing_country_id'],
                'site_state_id' => $validated['site_state_id'] ?? $validated['billing_state_id'],
                'site_district_id' => $validated['site_district_id'] ?? $validated['billing_district_id'],
                'site_city' => $validated['site_city_id'] ?? $validated['billing_city_id'],
                'site_area' => $validated['site_area'] ?? null,
                'site_address1' => $validated['site_address1'] ?? $validated['billing_address1'],
                'site_landmark' => $validated['site_landmark'] ?? null,
                'original_price' => $requestedPrice,
                'requested_price' => $requestedPrice,
                'approved_price' => null,
                'discount_percentage' => $discountPercentage,
                'discount_amount' => $discountAmount,
                'gst_percentage' => $gstPercentage,
                'gst_amount' => $gstAmount,
                'final_amount' => $finalAmount,
                'installation_cost' => $installationCost,
                'transport_cost' => $transportCost,
                'unloading_cost' => $unloadingCost,
                'approval_level' => $approvalLevel,
                'status' => 'pending',
                'no_of_floor' => $variant->id,
                'motor_type' => $motorType,
                'payment_terms_preset' => $validated['payment_terms_preset'],
                'warranty_type' => $validated['warranty_type'] ?? null,
                'warranty_years' => $validated['warranty_years'] ?? null,
                'amc_type' => $validated['amc_type'] ?? null,
                'amc_percentage' => $validated['amc_percentage'] ?? null,
                'delivery_type' => $validated['delivery_type'],
                'delivery_term' => $validated['delivery_term'],
                'delivery_amount' => $deliveryAmount,
                'selected_upgradations' => $upg['selected'],
                'upgradation_total' => $upg['total'],
                'valid_until' => $validated['valid_until'] ?? null,
                'payment_terms' => $validated['payment_terms'] ?? null,
                'delivery_terms' => $validated['delivery_terms'] ?? null,
                'installation_notes' => $validated['installation_notes'] ?? null,
                'client_signature' => $validated['client_signature'] ?? null,
                'signed_at' => ! empty($validated['client_signature']) ? now() : null,
            ] + self::CLEARED_LEGACY_UPGRADATION_COLUMNS);

            ProposalHistory::create([
                'proposal_id' => $proposal->id,
                'action_by_user_id' => $user->id,
                'actor_role_name' => $this->approvals->salesHierarchyRole($user),
                'action_type' => 'created',
                'new_price' => $finalAmount,
                'new_discount' => $discountAmount,
                'remarks' => 'Proposal created.',
            ]);

            return $proposal;
        });

        $this->approvals->submitProposal($proposal->fresh());

        return redirect()->route('crm.proposals.show', $proposal)
            ->with('success', "Proposal {$proposal->proposal_number} submitted for approval.");
    }

    public function show(Request $request, Proposal $proposal): View
    {
        abort_unless($this->approvals->canViewProposal($proposal, $request->user()), 403);

        $this->approvals->ensureApprovalWorkflow($proposal);
        $proposal->refresh();

        $detail = $proposal->toDetailArray();
        $canApprove = $this->approvals->canApproveProposal($proposal, $request->user());
        $canEscalate = $canApprove && ProposalApprovalService::APPROVAL_HIERARCHY[$proposal->approval_level] !== null;
        $histories = $proposal->histories()->latest()->get();

        return view('crm.proposals.show', compact('proposal', 'detail', 'canApprove', 'canEscalate', 'histories'));
    }

    /**
     * Direct port of the portal's ProposalPdfService::download() — same multi-page
     * quotation layout (resources/views/crm/proposals/pdf.blade.php, ported verbatim from
     * the portal's resources/views/proposals/pdf.blade.php), same signature-temp-file
     * workaround (DomPDF can't render base64 data URIs directly), same staff_type-driven
     * bank details block. OTP-gating is the only piece intentionally left out.
     */
    public function download(Request $request, Proposal $proposal): \Symfony\Component\HttpFoundation\Response
    {
        abort_unless($this->approvals->canViewProposal($proposal, $request->user()), 403);
        abort_unless($this->approvals->canDownloadProposal($proposal, $request->user()), 403, 'Downloading this proposal has been disabled by an administrator.');

        $proposal->loadMissing(['client', 'product', 'createdByUser', 'createdBy']);
        $proposal->staff_type = $proposal->createdByUser?->staff_type ?? $proposal->createdBy?->staff_type ?? 'N/A';

        $signatureTempPath = null;
        if (! empty($proposal->client_signature)) {
            $base64 = $proposal->client_signature;
            if (str_contains($base64, ',')) {
                $base64 = explode(',', $base64, 2)[1];
            }

            $tempFile = storage_path('app/esign_'.$proposal->id.'_'.time().'.png');
            file_put_contents($tempFile, base64_decode($base64));

            if (file_exists($tempFile)) {
                $signatureTempPath = $tempFile;
            }
        }

        $pdf = app('dompdf.wrapper')->loadView('crm.proposals.pdf', [
            'proposal' => $proposal,
            'signatureTempPath' => $signatureTempPath,
        ])->setPaper('a4');

        $fileName = \Illuminate\Support\Str::slug($proposal->proposal_number ?: 'proposal').'.pdf';
        $response = $pdf->download($fileName);

        if ($signatureTempPath && file_exists($signatureTempPath)) {
            @unlink($signatureTempPath);
        }

        return $response;
    }

    /**
     * Roles the portal calls sp/vp/sd — allowed to edit ANY proposal (not just their own),
     * including already-approved ones, which spins off a new numbered revision instead of
     * editing in place (see update()).
     */
    protected const REVISION_MANAGER_ROLES = ProposalApprovalService::REVISION_MANAGER_ROLES;

    public function edit(Request $request, Proposal $proposal, RevisionOtpGate $otpGate): View|RedirectResponse
    {
        $user = $request->user();
        $canManageProposal = $user->hasAnyRole(self::REVISION_MANAGER_ROLES);
        $isOwner = $proposal->created_by_user_id === $user->id;

        abort_unless($isOwner || $canManageProposal, 403);
        abort_unless($this->approvals->canViewProposal($proposal, $user), 403);
        abort_if($proposal->status === 'revised', 422, 'Revised proposals are closed. Please use the latest revision record.');
        abort_unless($canManageProposal || in_array($proposal->status, ['pending', 'revision_requested'], true), 422, 'Only proposals awaiting revision can be edited.');

        if (! $otpGate->isUnlocked($proposal, $user)) {
            return redirect()->route('crm.proposals.revision-otp', $proposal);
        }

        $catalogProducts = $this->catalogProducts();
        $discountMax = $this->creationDiscountLimit($request->user());
        $country = DB::table('countries')->where('name', 'India')->first() ?? DB::table('countries')->first();
        $warrantyYearOptions = $this->warrantyYearOptions($request->user());
        $isEdit = true;

        // The form is shared with create() and reads every value through old(), so seed
        // the saved proposal in as this request's old input (unless a failed submit has
        // already flashed the user's own input back).
        if (! $request->session()->hasOldInput()) {
            $request->session()->now('_old_input', $this->formValuesFor($proposal));
        }

        return view('crm.proposals.create', compact('proposal', 'catalogProducts', 'discountMax', 'country', 'warrantyYearOptions', 'isEdit'));
    }

    /**
     * Maps a saved proposal back onto the create/edit form's field names. A proposal on a
     * legacy (pre-catalog) product has no elevator_category, so its product cascade starts
     * empty and the user must pick a current catalog model before resubmitting.
     */
    protected function formValuesFor(Proposal $proposal): array
    {
        $client = $proposal->client;
        $product = $proposal->product;

        [$phoneCode, $phone] = preg_match('/^(\+\d{1,4})\s*(.*)$/', (string) $client?->phone, $m)
            ? [$m[1], $m[2]]
            : ['+91', (string) $client?->phone];

        $pincodeFrom = fn (?string $address) => preg_match('/Pincode:\s*(\d{6})/', (string) $address, $pm) ? $pm[1] : null;

        return $this->upgradeFormValues($proposal->selected_upgradations ?? []) + [
            'salutation' => $client?->salutation,
            'contact_person' => $client?->contact_person,
            'email' => $client?->email,
            'phone_country_code' => $phoneCode,
            'phone' => $phone,
            'alternate_phone' => $client?->alternate_phone,
            'gst_number' => $client?->gst_number,
            'billing_address1' => $proposal->billing_address1,
            'billing_area' => $proposal->billing_area,
            'billing_country_id' => $proposal->billing_country_id,
            'billing_state_id' => $proposal->billing_state_id,
            'billing_district_id' => $proposal->billing_district_id,
            'billing_city_id' => $proposal->billing_city,
            'billing_pincode' => $pincodeFrom($proposal->billing_address) ?? $client?->pincode,
            'billing_landmark' => $proposal->billing_landmark,
            'same_as_billing_address' => $proposal->same_as_billing_address ? '1' : null,
            'site_address1' => $proposal->site_address1,
            'site_area' => $proposal->site_area,
            'site_country_id' => $proposal->site_country_id,
            'site_state_id' => $proposal->site_state_id,
            'site_district_id' => $proposal->site_district_id,
            'site_city_id' => $proposal->site_city,
            'site_pincode' => $pincodeFrom($proposal->site_address),
            'site_landmark' => $proposal->site_landmark,
            'elevator_category' => $product?->elevator_category,
            'product_type' => $product?->elevator_category ? $product->type : null,
            'technology' => $product?->elevator_category ? $product->technology : null,
            'product_id' => $proposal->product_id,
            'no_of_floor' => $proposal->no_of_floor,
            'motor_type' => $proposal->motor_type,
            'warranty_type' => $proposal->warranty_type,
            'warranty_years' => $proposal->warranty_years,
            'amc_type' => $proposal->amc_type,
            'amc_percentage' => $proposal->amc_percentage,
            'delivery_type' => $proposal->delivery_type,
            'delivery_term' => $proposal->delivery_term,
            'delivery_amount' => $proposal->delivery_amount,
            'discount_amount' => (float) $proposal->discount_amount,
            'payment_terms_preset' => $proposal->payment_terms_preset,
            'payment_terms' => $proposal->payment_terms,
            'delivery_terms' => $proposal->delivery_terms,
            'installation_notes' => $proposal->installation_notes,
        ];
    }

    /**
     * Turns saved upgrade snapshots back into the form's upgrade inputs. Legacy snapshots
     * ({id, name, amount} from the Luxe flow) carry no key and are not restorable.
     */
    protected function upgradeFormValues(array $snapshots): array
    {
        $values = ['upgrade_selections' => [], 'upgrade_colour_finish' => null, 'upgrade_access_quantity' => 0];

        foreach ($snapshots as $snapshot) {
            $key = $snapshot['key'] ?? null;

            if ($key === 'colour_finish') {
                $values['upgrade_colour_finish'] = array_search($snapshot['name'] ?? null, ProposalUpgradationService::FREE_COLOURS, true) ?: null;
            } elseif (in_array($key, ProposalUpgradationService::PAID_COLOUR_KEYS, true)) {
                $values['upgrade_colour_finish'] = $key;
            } elseif ($key === ProposalUpgradationService::ACCESS_KEY) {
                $values['upgrade_access_quantity'] = (int) ($snapshot['quantity'] ?? 0);
            } elseif ($key) {
                $values['upgrade_selections'][$key] = ['selected' => '1', 'quantity' => (int) ($snapshot['quantity'] ?? 1)];
            }
        }

        return $values;
    }

    /**
     * Direct port of the portal's update()/buildProposalUpdatePayload() revision branch:
     * editing a `revision_requested` proposal, or (for super_admin/vice_president/
     * state_director only) an already-`approved` one, never edits in place — it spins off
     * a new `-R{n}` numbered revision with freshly recalculated pricing, closes the
     * original to `revised` (a terminal status update() itself refuses to touch again),
     * and records a paired audit trail entry on both records. A privileged edit of an
     * approved proposal auto-approves the revision; anything else lands back in `pending`
     * for the creator to review and separately resubmit, exactly like a new proposal.
     */
    public function update(Request $request, Proposal $proposal, RevisionOtpGate $otpGate): RedirectResponse
    {
        $user = $request->user();
        $canManageProposal = $user->hasAnyRole(self::REVISION_MANAGER_ROLES);
        $isOwner = $proposal->created_by_user_id === $user->id;

        abort_unless($isOwner || $canManageProposal, 403);
        abort_unless($this->approvals->canViewProposal($proposal, $user), 403);
        abort_if($proposal->status === 'revised', 422, 'Revised proposals are closed. Please use the latest revision record.');
        abort_unless($canManageProposal || in_array($proposal->status, ['pending', 'revision_requested'], true), 422, 'Only proposals awaiting revision can be edited.');

        if (! $otpGate->isUnlocked($proposal, $user)) {
            return redirect()->route('crm.proposals.revision-otp', $proposal)->with('error', 'Please verify the revision OTP first.');
        }

        $discountMax = $this->creationDiscountLimit($user);
        $validated = $this->validateProposal($request, $discountMax);
        $validated = $this->applySameAsBillingAddress($validated);

        [
            'variant' => $variant, 'billingAddress' => $billingAddress, 'siteAddress' => $siteAddress, 'upg' => $upg,
            'installationCost' => $installationCost, 'transportCost' => $transportCost, 'unloadingCost' => $unloadingCost,
            'gstPercentage' => $gstPercentage, 'requestedPrice' => $requestedPrice, 'discountAmount' => $discountAmount,
            'deliveryAmount' => $deliveryAmount, 'gstAmount' => $gstAmount, 'finalAmount' => $finalAmount,
            'discountPercentage' => $discountPercentage, 'motorType' => $motorType,
        ] = $this->buildPricingContext($validated);

        $payload = [
            'product_id' => $validated['product_id'],
            'billing_address' => $billingAddress,
            'site_address' => $siteAddress,
            'same_as_billing_address' => (bool) ($validated['same_as_billing_address'] ?? false),
            'billing_country_id' => $validated['billing_country_id'],
            'billing_state_id' => $validated['billing_state_id'],
            'billing_district_id' => $validated['billing_district_id'],
            'billing_city' => $validated['billing_city_id'],
            'billing_area' => $validated['billing_area'] ?? null,
            'billing_address1' => $validated['billing_address1'],
            'billing_landmark' => $validated['billing_landmark'] ?? null,
            'site_country_id' => $validated['site_country_id'] ?? $validated['billing_country_id'],
            'site_state_id' => $validated['site_state_id'] ?? $validated['billing_state_id'],
            'site_district_id' => $validated['site_district_id'] ?? $validated['billing_district_id'],
            'site_city' => $validated['site_city_id'] ?? $validated['billing_city_id'],
            'site_area' => $validated['site_area'] ?? null,
            'site_address1' => $validated['site_address1'] ?? $validated['billing_address1'],
            'site_landmark' => $validated['site_landmark'] ?? null,
            'requested_price' => $requestedPrice,
            'original_price' => $requestedPrice,
            'discount_amount' => $discountAmount,
            'discount_percentage' => $discountPercentage,
            'gst_amount' => $gstAmount,
            'final_amount' => $finalAmount,
            'installation_cost' => $installationCost,
            'transport_cost' => $transportCost,
            'unloading_cost' => $unloadingCost,
            'gst_percentage' => $gstPercentage,
            'delivery_amount' => $deliveryAmount,
            'no_of_floor' => $variant->id,
            'motor_type' => $motorType,
            'selected_upgradations' => $upg['selected'],
            'upgradation_total' => $upg['total'],
            'payment_terms_preset' => $validated['payment_terms_preset'],
            'delivery_type' => $validated['delivery_type'],
            'delivery_term' => $validated['delivery_term'],
            'warranty_type' => $validated['warranty_type'] ?? null,
            'warranty_years' => $validated['warranty_years'] ?? null,
            'amc_type' => $validated['amc_type'] ?? null,
            'amc_percentage' => $validated['amc_percentage'] ?? null,
            'valid_until' => $validated['valid_until'] ?? null,
            'payment_terms' => $validated['payment_terms'] ?? null,
            'delivery_terms' => $validated['delivery_terms'] ?? null,
            'installation_notes' => $validated['installation_notes'] ?? null,
        ] + self::CLEARED_LEGACY_UPGRADATION_COLUMNS;

        $proposal->client?->update([
            'company_name' => $validated['contact_person'],
            'salutation' => $validated['salutation'] ?? null,
            'contact_person' => $validated['contact_person'],
            'phone' => trim($validated['phone_country_code'].' '.$validated['phone']),
            'alternate_phone' => $validated['alternate_phone'] ?? null,
            'email' => $validated['email'] ?? null,
            'country_id' => $validated['billing_country_id'],
            'billing_address' => $billingAddress,
            'site_address' => $siteAddress,
            'pincode' => $validated['billing_pincode'],
            'gst_number' => $validated['gst_number'] ?? null,
        ]);

        $shouldCreateRevision = $proposal->status === 'revision_requested'
            || ($canManageProposal && $proposal->status === 'approved');
        $shouldApproveRevision = $canManageProposal && $proposal->status === 'approved';

        if ($shouldCreateRevision) {
            $revision = DB::transaction(function () use ($proposal, $payload, $shouldApproveRevision, $user) {
                $revisionNumber = $this->nextRevisionNumber($proposal);
                $revisionProposalNumber = $this->makeRevisionProposalNumber($proposal, $revisionNumber);

                $revision = $proposal->replicate();
                $revision->fill($payload);
                $revision->proposal_number = $revisionProposalNumber;
                $revision->revised_from_id = $proposal->revised_from_id ?: $proposal->id;
                $revision->revision_number = $revisionNumber;
                $revision->status = $shouldApproveRevision ? 'approved' : 'pending';
                $revision->current_handler_user_id = null;
                $revision->submitted_at = $shouldApproveRevision ? now() : null;
                $revision->approved_at = $shouldApproveRevision ? now() : null;
                $revision->approved_price = $shouldApproveRevision ? $revision->requested_price : null;
                $revision->confirmed_at = null;
                $revision->client_confirmation_status = null;
                $revision->save();

                ProposalHistory::create([
                    'proposal_id' => $revision->id,
                    'action_by_user_id' => $user->id,
                    'actor_role_name' => $this->approvals->salesHierarchyRole($user),
                    'action_type' => 'revision_created',
                    'old_price' => $proposal->requested_price,
                    'new_price' => $revision->requested_price,
                    'old_discount' => $proposal->discount_amount,
                    'new_discount' => $revision->discount_amount,
                    'remarks' => $shouldApproveRevision
                        ? "Created and approved revision {$revision->proposal_number} from {$proposal->proposal_number}."
                        : "Created revision {$revision->proposal_number} from {$proposal->proposal_number}.",
                ]);

                $proposal->update(['status' => 'revised', 'current_handler_user_id' => null]);

                ProposalHistory::create([
                    'proposal_id' => $proposal->id,
                    'action_by_user_id' => $user->id,
                    'actor_role_name' => $this->approvals->salesHierarchyRole($user),
                    'action_type' => 'revised',
                    'old_price' => $proposal->requested_price,
                    'new_price' => $revision->requested_price,
                    'old_discount' => $proposal->discount_amount,
                    'new_discount' => $revision->discount_amount,
                    'remarks' => "Closed after creating revision {$revision->proposal_number}.",
                ]);

                return $revision;
            });

            $otpGate->consume($proposal, $user);

            return redirect()->route('crm.proposals.show', $revision)->with('success', "Revision {$revision->proposal_number} created successfully.");
        }

        $proposal->update($payload);

        $this->approvals->submitProposal($proposal->fresh());
        $otpGate->consume($proposal, $user);

        return redirect()->route('crm.proposals.show', $proposal)->with('success', 'Proposal updated and resubmitted.');
    }

    /**
     * Next sequential revision number across every revision descended from the same root
     * proposal (not just this one's direct children) — matches the portal's numbering,
     * which never reuses a number even if earlier revisions were later replaced again.
     */
    protected function nextRevisionNumber(Proposal $proposal): int
    {
        $rootProposalId = $proposal->revised_from_id ?: $proposal->id;

        $currentMax = (int) Proposal::withTrashed()
            ->where('revised_from_id', $rootProposalId)
            ->max('revision_number');

        return max($currentMax, (int) $proposal->revision_number) + 1;
    }

    protected function makeRevisionProposalNumber(Proposal $proposal, int $revisionNumber): string
    {
        $baseNumber = preg_replace('/-R\d+$/', '', $proposal->proposal_number);
        $revisionNumber = max(1, $revisionNumber);

        do {
            $number = "{$baseNumber}-R{$revisionNumber}";
            $exists = Proposal::withTrashed()->where('proposal_number', $number)->exists();

            if ($exists) {
                $revisionNumber++;
            }
        } while ($exists);

        return $number;
    }

    public function destroy(Request $request, Proposal $proposal): RedirectResponse
    {
        $user = $request->user();
        $isOwner = $proposal->created_by_user_id === $user->id;
        $isPrivileged = $user->hasAnyRole(['super_admin', 'vice_president']);

        abort_unless($isOwner || $isPrivileged, 403);
        abort_unless(in_array($proposal->status, ['pending', 'revision_requested'], true), 422, 'Only proposals awaiting revision can be deleted.');

        $proposal->delete();

        return redirect()->route('crm.proposals.index')->with('success', 'Proposal deleted.');
    }

    public function submit(Request $request, Proposal $proposal): RedirectResponse
    {
        abort_unless($proposal->created_by_user_id === $request->user()->id, 403);

        $this->approvals->submitProposal($proposal);

        return back()->with('success', 'Proposal submitted for approval.');
    }

    public function approve(Request $request, Proposal $proposal): RedirectResponse
    {
        $validated = $request->validate(['comments' => ['nullable', 'string']]);

        try {
            $this->approvals->approveProposal($proposal, $request->user(), $validated);
        } catch (\RuntimeException $e) {
            return back()->with('error', $e->getMessage());
        }

        return back()->with('success', 'Proposal approved.');
    }

    public function reject(Request $request, Proposal $proposal): RedirectResponse
    {
        $validated = $request->validate(['reason' => ['required', 'string']]);

        try {
            $this->approvals->rejectProposal($proposal, $request->user(), $validated['reason']);
        } catch (\RuntimeException $e) {
            return back()->with('error', $e->getMessage());
        }

        return back()->with('success', 'Proposal rejected.');
    }

    public function revise(Request $request, Proposal $proposal): RedirectResponse
    {
        $validated = $request->validate(['remarks' => ['required', 'string']]);

        try {
            $this->approvals->requestRevision($proposal, $request->user(), $validated['remarks']);
        } catch (\RuntimeException $e) {
            return back()->with('error', $e->getMessage());
        }

        return back()->with('success', 'Revision requested.');
    }

    public function toggleDownload(Request $request, Proposal $proposal): RedirectResponse
    {
        $user = $request->user();
        abort_unless($this->approvals->canViewProposal($proposal, $user) && $this->approvals->canManageDownload($user), 403);

        $enabled = $request->boolean('enabled');
        $this->approvals->setDownloadEnabled($proposal, $user, $enabled);

        return back()->with('success', 'Proposal download '.($enabled ? 'enabled' : 'disabled').'.');
    }

    public function escalate(Request $request, Proposal $proposal): RedirectResponse
    {
        $validated = $request->validate(['remarks' => ['nullable', 'string']]);

        try {
            $this->approvals->moveToParent($proposal, $request->user(), $validated['remarks'] ?? null);
        } catch (\RuntimeException $e) {
            return back()->with('error', $e->getMessage());
        }

        return back()->with('success', 'Proposal escalated.');
    }

    /**
     * Active variants of an active product, in catalog order. Prices returned here are
     * display-only; buildPricingContext() re-reads the variant on submit.
     */
    public function variants(LiftProduct $liftProduct): JsonResponse
    {
        abort_unless($liftProduct->status, 404);

        return response()->json(
            $liftProduct->variants()
                ->where('status', true)
                ->orderBy('sort_order')
                ->orderBy('number_of_stops')
                ->orderBy('id')
                ->get([
                    'id', 'variant_name', 'configuration', 'number_of_stops', 'capacity_persons', 'load_kg',
                    'total_travel_height', 'specifications', 'mrp_price', 'gst_percentage',
                    'installation_cost', 'transport_cost', 'unloading_cost',
                ])
        );
    }

    public function addressStates(): JsonResponse
    {
        return response()->json(
            DB::table('proposal_address_states')->where('status', true)->orderBy('name')->get(['id', 'name'])
        );
    }

    public function addressDistricts(Request $request): JsonResponse
    {
        $stateId = $request->query('state_id');

        return response()->json(
            DB::table('proposal_address_districts')
                ->where('proposal_address_state_id', $stateId)
                ->where('status', true)
                ->orderBy('name')
                ->get(['id', 'name'])
        );
    }

    /**
     * The most this user's role may give as a Special Offer — set per role by Super Admin
     * under Offer Limit Settings (see RoleDiscountLimit::limitForRole()).
     */
    protected function creationDiscountLimit(User $user): float
    {
        return RoleDiscountLimit::limitForRole($this->approvals->salesHierarchyRole($user));
    }

    /**
     * Portal role-gates the warranty term a proposal can offer: senior roles can go up to
     * 10 years, Regional Manager up to 5, everyone else is limited to 1.
     *
     * @return int[]
     */
    protected function warrantyYearOptions(User $user): array
    {
        $role = $this->approvals->salesHierarchyRole($user);

        $max = match ($role) {
            'super_admin', 'vice_president', 'state_director' => 10,
            'regional_manager' => 5,
            default => 1,
        };

        return range(1, $max);
    }

    /**
     * Mirrors the portal's pre-validation normalization: strips a redundant leading "91"
     * from an already-+91-prefixed phone number, strips non-digits from the alternate
     * phone, and uppercases/strips the GST number — done before validation so the regex
     * rules below see clean input the same way the portal's do.
     */
    protected function normalizePhoneAndGst(Request $request): void
    {
        $countryCode = $request->input('phone_country_code', '+91');
        $phone = preg_replace('/\D+/', '', (string) $request->input('phone'));
        if ($countryCode === '+91' && strlen($phone) === 12 && str_starts_with($phone, '91')) {
            $phone = substr($phone, 2);
        }

        $alternate = preg_replace('/\D+/', '', (string) $request->input('alternate_phone'));

        $gst = $request->input('gst_number');
        $gst = $gst ? strtoupper(preg_replace('/[^0-9A-Za-z]/', '', $gst)) : null;

        $request->merge([
            'phone' => $phone,
            'alternate_phone' => $alternate ?: null,
            'gst_number' => $gst,
        ]);
    }

    protected function validateProposal(Request $request, float $discountMax): array
    {
        $this->normalizePhoneAndGst($request);

        $selectedProduct = LiftProduct::active()->find($request->input('product_id'));

        $validated = $request->validate([
            'salutation' => ['nullable', 'in:Mr.,Mrs.,Ms.,Dr.'],
            'contact_person' => ['required', 'string', 'max:255'],
            'phone_country_code' => ['required', 'regex:/^\+[0-9]{1,4}$/'],
            'phone' => ['required', 'regex:/^[0-9]{4,15}$/'],
            'alternate_phone' => ['nullable', 'regex:/^[6-9][0-9]{9}$/'],
            'email' => ['nullable', 'string', 'max:255'],
            'gst_number' => ['nullable', 'regex:/^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$/'],
            'billing_address1' => ['required', 'string', 'max:255'],
            'billing_area' => ['nullable', 'string', 'max:255'],
            'billing_country_id' => ['required', 'exists:countries,id'],
            'billing_state_id' => ['required', 'exists:proposal_address_states,id'],
            'billing_district_id' => ['required', 'exists:proposal_address_districts,id'],
            'billing_city_id' => ['required', 'string', 'max:255'],
            'billing_pincode' => ['required', 'regex:/^[0-9]{6}$/'],
            'billing_landmark' => ['nullable', 'string', 'max:255'],
            'same_as_billing_address' => ['nullable', 'boolean'],
            'site_address1' => ['required_unless:same_as_billing_address,1', 'nullable', 'string', 'max:255'],
            'site_area' => ['nullable', 'string', 'max:255'],
            'site_country_id' => ['required_unless:same_as_billing_address,1', 'nullable', 'exists:countries,id'],
            'site_state_id' => ['required_unless:same_as_billing_address,1', 'nullable', 'exists:proposal_address_states,id'],
            'site_district_id' => ['required_unless:same_as_billing_address,1', 'nullable', 'exists:proposal_address_districts,id'],
            'site_city_id' => ['required_unless:same_as_billing_address,1', 'nullable', 'string', 'max:255'],
            'site_pincode' => ['required_unless:same_as_billing_address,1', 'nullable', 'regex:/^[0-9]{6}$/'],
            'site_landmark' => ['nullable', 'string', 'max:255'],
            'elevator_category' => ['required', Rule::in(LiftProduct::ELEVATOR_CATEGORIES)],
            'product_type' => ['required', 'string', 'max:100'],
            'technology' => ['required', 'string', 'max:100'],
            // The model must be active and sit exactly under the submitted category/type/technology.
            'product_id' => ['required', Rule::exists('lift_products', 'id')->where(fn ($query) => $query
                ->where('status', true)
                ->whereNull('deleted_at')
                ->where('elevator_category', $request->input('elevator_category'))
                ->where('type', $request->input('product_type'))
                ->where('category', $request->input('technology')))],
            'motor_type' => [Rule::requiredIf(! $selectedProduct?->isStairlift()), 'nullable', 'in:single_phase,3_phase'],
            'upgrade_selections' => ['nullable', 'array'],
            'upgrade_selections.*' => ['array'],
            'upgrade_selections.*.selected' => ['nullable', 'boolean'],
            'upgrade_selections.*.quantity' => ['nullable', 'integer', 'min:1'],
            'upgrade_colour_finish' => ['nullable', 'string', 'max:50'],
            'upgrade_access_quantity' => ['nullable', 'integer', 'min:0'],
            'no_of_floor' => ['required', 'integer'],
            'number_of_floor' => ['required', 'integer', 'min:1'],
            'installation_cost' => ['nullable', 'numeric', 'min:0'],
            'transport_cost' => ['nullable', 'numeric', 'min:0'],
            'unloading_cost' => ['nullable', 'numeric', 'min:0'],
            'delivery_type' => ['required', Rule::in(array_keys(self::DELIVERY_TERMS))],
            'delivery_term' => ['required', Rule::in(array_keys(self::DELIVERY_TERMS[$request->input('delivery_type')] ?? []))],
            'delivery_amount' => ['nullable', 'numeric', 'min:0'],
            'payment_terms_preset' => ['required', 'in:preset_1,preset_2,preset_3,preset_4'],
            'payment_terms' => ['nullable', 'string'],
            'delivery_terms' => ['nullable', 'string'],
            'installation_notes' => ['nullable', 'string'],
            'warranty_type' => ['nullable', 'in:motor,all_parts'],
            'warranty_years' => ['nullable', 'required_with:warranty_type', 'integer', 'in:'.implode(',', $this->warrantyYearOptions($request->user()))],
            'amc_type' => ['nullable', 'in:comprehensive,non_comprehensive'],
            'amc_percentage' => ['nullable', 'required_with:amc_type', 'numeric'],
            'valid_until' => ['nullable', 'date'],
            'client_signature' => ['nullable', 'string'],
            'discount_amount' => ['nullable', 'numeric', 'min:0', "max:{$discountMax}"],
        ], [
            'discount_amount.max' => "Your role may only apply a discount of up to ₹{$discountMax}.",
            'warranty_years.in' => 'Your role may not offer that many warranty years.',
            'warranty_years.required_with' => 'Select the warranty duration.',
            'product_id.exists' => 'The selected model is not available for the chosen category, product type and technology.',
            'delivery_term.in' => 'Select a valid delivery schedule for the selected delivery type.',
        ]);

        if ($validated['phone_country_code'] === '+91' && ! preg_match('/^[6-9][0-9]{9}$/', $validated['phone'])) {
            throw \Illuminate\Validation\ValidationException::withMessages([
                'phone' => 'Enter a valid 10 digit Indian mobile number.',
            ]);
        }

        return $validated;
    }

    /**
     * Resolves the variant, addresses, upgrade pricing, and grand-total math from a
     * validated payload — shared between store()/update() (which persist the result) and
     * preview() (which discards it), so the preview a salesperson sees is guaranteed to be
     * computed by the exact same code path that will actually save the proposal.
     */
    protected function buildPricingContext(array $validated): array
    {
        $product = LiftProduct::findOrFail($validated['product_id']);

        $variant = LiftProductVariant::where('id', $validated['no_of_floor'])
            ->where('lift_product_id', $product->id)
            ->where('status', true)
            ->first();

        if (! $variant) {
            throw ValidationException::withMessages([
                'no_of_floor' => 'The selected configuration is not valid for this model.',
            ]);
        }

        if ((int) $validated['number_of_floor'] !== (int) $variant->number_of_stops) {
            throw ValidationException::withMessages([
                'number_of_floor' => 'Floor count does not match the selected configuration.',
            ]);
        }

        // Price is never trusted from client input — it always comes from the selected
        // variant's own catalog price, matching the portal's readonly requested_price
        // field (auto-filled by JS, never freely typed by the salesperson).
        $requestedPrice = (float) $variant->mrp_price;

        if ($requestedPrice <= 0) {
            throw ValidationException::withMessages([
                'no_of_floor' => 'The selected configuration has no catalog price.',
            ]);
        }

        $billingAddress = $this->composeAddress($validated, 'billing');
        $siteAddress = $validated['same_as_billing_address'] ?? false ? $billingAddress : $this->composeAddress($validated, 'site');

        // Upgrade amounts are final and GST-inclusive; product GST below covers only the
        // product price and its base cost components.
        $upg = $this->upgradations->price($product, $variant, $validated);
        $motorType = $product->isStairlift() ? null : $validated['motor_type'];

        $installationCost = (float) ($variant->installation_cost ?? $validated['installation_cost'] ?? 0);
        $transportCost = (float) ($variant->transport_cost ?? $validated['transport_cost'] ?? 0);
        $unloadingCost = (float) ($variant->unloading_cost ?? $validated['unloading_cost'] ?? 0);
        $gstPercentage = (float) ($variant->gst_percentage ?? 0);

        $discountAmount = (float) ($validated['discount_amount'] ?? 0);

        if ($discountAmount > $requestedPrice) {
            throw ValidationException::withMessages([
                'discount_amount' => 'The special offer cannot exceed the product price.',
            ]);
        }

        $deliveryAmount = (float) self::DELIVERY_TERMS[$validated['delivery_type']][$validated['delivery_term']];

        $subtotal = $requestedPrice + $installationCost + $transportCost + $unloadingCost;
        $gstAmount = $subtotal * ($gstPercentage / 100);
        $finalAmount = max(0, $subtotal + $gstAmount + $deliveryAmount + $upg['total'] - $discountAmount);
        $discountPercentage = round(($discountAmount / $requestedPrice) * 100, 2);

        return compact(
            'product', 'variant', 'motorType', 'billingAddress', 'siteAddress', 'upg',
            'installationCost', 'transportCost', 'unloadingCost', 'gstPercentage',
            'requestedPrice', 'discountAmount', 'deliveryAmount', 'gstAmount', 'finalAmount', 'discountPercentage'
        );
    }

    /**
     * Computes everything store() would save, but persists nothing — lets a salesperson
     * check the fully-resolved proposal (client, address, specs, pricing) before
     * submitting. Returns a rendered HTML fragment for the create form's preview modal.
     */
    public function preview(Request $request): \Illuminate\Http\Response
    {
        $user = $request->user();
        abort_unless($this->approvals->canCreateProposal($user), 403);

        $discountMax = $this->creationDiscountLimit($user);

        try {
            $validated = $this->applySameAsBillingAddress($this->validateProposal($request, $discountMax));
            $context = $this->buildPricingContext($validated);
        } catch (ValidationException $e) {
            return response()->view('crm.proposals._preview-errors', ['errors' => $e->errors()], 422);
        }

        $product = $context['product'];

        $detail = [
            'client' => [
                'salutation' => $validated['salutation'] ?? null,
                'contact_person' => $validated['contact_person'],
                'phone' => trim($validated['phone_country_code'].' '.$validated['phone']),
                'alternate_phone' => $validated['alternate_phone'] ?? null,
                'email' => $validated['email'] ?? null,
                'gst_number' => $validated['gst_number'] ?? null,
            ],
            'location' => [
                'billing_address' => $context['billingAddress'],
                'site_address' => $context['siteAddress'],
            ],
            'product' => ['name' => $product->name, 'product_code' => $product->product_code, 'type' => $product->type],
            'variant' => ['name' => $context['variant']->variant_name, 'number_of_stops' => $context['variant']->number_of_stops, 'total_travel_height' => $context['variant']->total_travel_height],
            'specifications' => [
                'motor_type_label' => $context['motorType'] ? ucwords(str_replace('_', ' ', $context['motorType'])) : null,
            ],
            'upgradations' => $context['upg']['selected'],
            'pricing' => [
                'original_price' => $context['requestedPrice'],
                'installation_cost' => $context['installationCost'],
                'transport_cost' => $context['transportCost'],
                'unloading_cost' => $context['unloadingCost'],
                'upgradation_total' => $context['upg']['total'],
                'discount_amount' => $context['discountAmount'],
                'discount_percentage' => $context['discountPercentage'],
                'gst_percentage' => $context['gstPercentage'],
                'gst_amount' => $context['gstAmount'],
                'final_amount' => $context['finalAmount'],
            ],
            'commercial_terms' => [
                'valid_until' => $validated['valid_until'] ?? null,
                'payment_terms' => $validated['payment_terms'] ?? null,
                'delivery_terms' => $validated['delivery_terms'] ?? null,
                'installation_notes' => $validated['installation_notes'] ?? null,
                'warranty_type' => $validated['warranty_type'] ?? null,
                'warranty_years' => $validated['warranty_years'] ?? null,
                'amc_type' => $validated['amc_type'] ?? null,
                'amc_percentage' => $validated['amc_percentage'] ?? null,
            ],
        ];

        return response()->view('crm.proposals._preview', compact('detail'));
    }

    /**
     * When "same as billing address" is checked, mirrors the billing fields into the site
     * fields server-side — matching the portal's syncSiteAddressWithBilling() JS, but
     * enforced here too so the discrete site_* columns are never left blank regardless of
     * what the browser did.
     */
    protected function applySameAsBillingAddress(array $validated): array
    {
        if (empty($validated['same_as_billing_address'])) {
            return $validated;
        }

        foreach (['address1', 'area', 'country_id', 'state_id', 'district_id', 'city_id', 'pincode', 'landmark'] as $field) {
            $validated["site_{$field}"] = $validated["billing_{$field}"] ?? null;
        }

        return $validated;
    }

    /**
     * Builds a free-text address string from discrete country/state/district/city/
     * pincode/landmark fields, mirroring the portal's composeAddress() closure.
     */
    protected function composeAddress(array $validated, string $prefix): string
    {
        $country = DB::table('countries')->where('id', $validated["{$prefix}_country_id"] ?? null)->value('name');
        $state = DB::table('proposal_address_states')->where('id', $validated["{$prefix}_state_id"] ?? null)->value('name');
        $district = DB::table('proposal_address_districts')->where('id', $validated["{$prefix}_district_id"] ?? null)->value('name');

        return collect([
            $validated["{$prefix}_address1"] ?? null,
            $validated["{$prefix}_area"] ?? null,
            $validated["{$prefix}_city_id"] ?? null,
            $district,
            $state,
            $country,
            isset($validated["{$prefix}_pincode"]) ? "Pincode: {$validated["{$prefix}_pincode"]}" : null,
            isset($validated["{$prefix}_landmark"]) ? "Landmark: {$validated["{$prefix}_landmark"]}" : null,
        ])->filter()->implode(', ');
    }

    protected function generateProposalNumber(?int $stateId): string
    {
        $year = now()->format('Y');
        $stateCode = 'XX';

        if ($stateId) {
            $state = DB::table('proposal_address_states')->where('id', $stateId)->first();
            if ($state) {
                $stateCode = ! empty($state->code)
                    ? strtoupper(trim($state->code))
                    : strtoupper(substr(preg_replace('/[^A-Za-z]/', '', $state->name), 0, 2));
            }
        }

        $prefix = "PROP-{$stateCode}-{$year}-";

        $sequence = 1;
        do {
            $number = $prefix.str_pad((string) $sequence, 4, '0', STR_PAD_LEFT);
            $exists = Proposal::where('proposal_number', $number)->exists();
            $sequence++;
        } while ($exists);

        return $number;
    }
}
