<?php

namespace App\Http\Controllers;

use App\Models\Account;
use App\Models\Activity;
use App\Models\BillingFrequency;
use App\Models\Company;
use App\Models\Contact;
use App\Models\ContactEmail;
use App\Models\ContactLanguage;
use App\Models\ContactPhoneNum;
use App\Models\ContactSocialMedia;
use App\Models\Country;
use App\Models\Deal;
use App\Models\DealProductLine;
use App\Models\DealFile;
use App\Models\DealStage;
use App\Models\DealStageStatus;
use App\Models\DealStageTransition;
use App\Models\DealApprovalHistory;
use App\Models\DealReopenHistory;
use App\Models\DealCompetitor;
use App\Models\Project;
use App\Models\CrmCase;
use App\Models\Survey;
use App\Models\TradeDocument;
use App\Models\DocumentTemplate;
use App\Models\Language;
use App\Models\Lead;
use App\Models\MeetingThirdPartyIntegration;
use App\Models\Note;
use App\Models\Priority;
use App\Models\Product;
use App\Models\PriceBook;
use App\Models\PriceBookItem;
use App\Models\InventoryReservation;
use App\Models\Repeat;
use App\Models\Stage;
use App\Models\Status;
use App\Models\User;
use App\Models\UserAccess;
use App\Models\AccessType;
use App\Models\WonLost;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use App\Services\CrmNotificationService;
use App\Services\TradeDocumentService;
use App\Http\Controllers\OperationsFoundationController;
use App\Support\CrmAccess;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Spatie\Permission\Models\Permission;

class DealController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');

        $this->middleware('permission:list_deals')->only(['index']);
        $this->middleware('permission:list_deals')->only(['show']);
        $this->middleware('permission:create_deals')->only(['create', 'store']);
        $this->middleware('permission:update_deals')->only(['edit', 'update', 'updateDealStage', 'updateStageStatus', 'updateStage', 'storeWonClosureDetails']);
        $this->middleware('permission:delete_deals')->only(['destroy']);
    }
    
    public function index(Request $request)
    {
        $user = auth()->user();
        $businessId = $user->business_id;
        $this->useDefaultDateRange($request);

        // Get deals based on user access permissions
        $request->validate([
            'q'=>['nullable','string','max:100'],'status'=>['nullable',Rule::in(['active','inactive'])],
            'stage_id'=>['nullable','integer'],'priority_id'=>['nullable','integer'],
            'date_from'=>['nullable','date'],'date_to'=>['nullable','date','after_or_equal:date_from'],
        ]);
        $search = trim((string) $request->input('q'));
        $dealPaginator = $this->getAccessibleDeals($user->id)
            ->when($request->filled('status'),fn($q)=>$q->where('status',$request->status))
            ->when($request->filled('stage_id'),fn($q)=>$q->where('stage_id',$request->integer('stage_id')))
            ->when($request->filled('priority_id'),fn($q)=>$q->where('priority_id',$request->integer('priority_id')))
            ->when($request->filled('date_from'),fn($q)=>$q->whereDate('created_at','>=',$request->date_from))
            ->when($request->filled('date_to'),fn($q)=>$q->whereDate('created_at','<=',$request->date_to))
            ->when($search !== '', fn ($query) => $query->where(fn ($filter) => $filter
                ->where('name', 'like', "%{$search}%")
                ->orWhere('sic_code', 'like', "%{$search}%")
                ->orWhere('deal_type', 'like', "%{$search}%")
                ->orWhere('comments', 'like', "%{$search}%")))
            ->latest()->paginate(25)->withQueryString();
        $deals = collect($dealPaginator->items());

        $stages = DealStage::all();
        $activities = collect();
        $priorities = Priority::all();
        $countries = Country::all();
        $meetings = MeetingThirdPartyIntegration::all();
        $eventStatuses = Status::all();
        $companies = Company::all();
        $repeats = Repeat::all();
        $users = $this->getAccessibleUsers();
        $documentTemplates = DocumentTemplate::where('business_id', $businessId)->orderBy('type')->orderBy('name')->get();
        $contacts = Contact::where('business_id', $businessId)
            ->orderBy('first_name')->limit(250)->get(['id', 'first_name', 'last_name']);

        $activityTypes = [
            1 => 'Call',
            2 => 'Appointment',
            3 => 'Task',
            4 => 'Event',
            5 => 'Deadline',
            6 => 'Other Activity',
            7 => 'Letter',
            8 => 'Fax',
            9 => 'Email',
            10 => 'Campaign',
        ];

        // --------------------------
        // Calculate Won/Lost Counts
        // --------------------------

        // Identify Won and Lost stage IDs
        $wonStageIds = DealStage::where('name', 'Closed Won')->pluck('id')->toArray();
        $lostStageIds = DealStage::where('name', 'Closed Lost')->pluck('id')->toArray();

        $businessDeals = Deal::where('business_id', $businessId);

        // Monthly counts
        $monthDealWon = (clone $businessDeals)
            ->whereIn('stage_id', $wonStageIds)
            ->whereMonth('updated_at', now()->month)
            ->whereYear('updated_at', now()->year)
            ->count();

        $monthDealLost = (clone $businessDeals)
            ->whereIn('stage_id', $lostStageIds)
            ->whereMonth('updated_at', now()->month)
            ->whereYear('updated_at', now()->year)
            ->count();

        // Yearly counts
        $yearDealWon = (clone $businessDeals)
            ->whereIn('stage_id', $wonStageIds)
            ->whereYear('updated_at', now()->year)
            ->count();

        $yearDealLost = (clone $businessDeals)
            ->whereIn('stage_id', $lostStageIds)
            ->whereYear('updated_at', now()->year)
            ->count();

        // Return to view
        return view('deals.deals', compact(
            'deals',
            'stages',
            'activities',
            'activityTypes',
            'priorities',
            'countries',
            'meetings',
            'eventStatuses',
            'companies',
            'repeats',
            'contacts',
            'users',
            'monthDealWon',
            'monthDealLost',
            'yearDealWon',
            'yearDealLost',
            'dealPaginator'
        ));
    }


    private function getAccessibleDeals($userId)
    {
        $accessibleUserIds = $this->getAccessibleUserIds($userId);
        
        return Deal::with([
            'contactPhones', 'assignedUser:id,name,email', 'contact:id,first_name,last_name',
            'contacts:id,first_name,last_name', 'accounts:id,account_name', 'latestActivity',
        ])
            ->whereIn('owner_id', $accessibleUserIds)
            ->where('business_id', auth()->user()->business_id)
            ->where(function ($query) {
                $query->whereNull('status')
                    ->orWhere('status', 'active')
                    ->orWhereIn('stage_id', [9, 10]);
            });
    }

    private function getAccessibleUserIds($currentUserId)
    {
        return CrmAccess::accessibleUserIds(User::findOrFail($currentUserId));
    }

    private function getAccessibleUsers()
    {
        $accessibleUserIds = $this->getAccessibleUserIds(auth()->id());
        return User::whereIn('id', $accessibleUserIds)->get();
    }

    public function updateDealStage(Request $request, $id)
    {
        $deal = Deal::findOrFail($id);
        
        // Check access permission
        $accessibleUserIds = $this->getAccessibleUserIds(auth()->id());
        
        if (!in_array($deal->owner_id, $accessibleUserIds)) {
            abort(403, 'You do not have permission to update this deal.');
        }

        $validated = $request->validate([
            'stage_id' => 'required|integer|exists:deal_stages,id|between:1,10',
        ]);

        $currentStageId = $deal->stage_id;
        $this->assertDealIsOpen($deal);
        $this->assertStageTransition($deal, (int) $validated['stage_id']);
        $this->assertStageRequirements($deal, (int) $validated['stage_id']);

        if (in_array((int) $validated['stage_id'], [9, 10], true)) {
            return $this->requestClosureApproval($deal, (int) $validated['stage_id']);
        }

        $deal->stage_id = $validated['stage_id'];
        $deal->closure_approval_status = null;
        $deal->requested_closure_stage_id = null;
        $deal->save();

        for ($i = 1; $i < $validated['stage_id']; $i++) {
            $dealStageStatus = DealStageStatus::firstOrCreate([
                'deal_id' => $deal->id,
                'stage_id' => $i,
            ]);

            $dealStageStatus->stage_status = 3;
            $dealStageStatus->save();
        }

        $dealStageStatus = DealStageStatus::firstOrCreate([
            'deal_id' => $deal->id,
            'stage_id' => $validated['stage_id'],
        ]);

        $dealStageStatus->stage_status = 1;
        $dealStageStatus->save();

        $allowedStageIds = $this->allowedDealStageTransitions((int) $deal->business_id)
            ->get((int) $deal->stage_id, collect())
            ->values();

        return response()->json([
            'message' => 'Deal Status updated successfully!',
            'stage_id' => (int) $deal->stage_id,
            'stage_name' => DealStage::find($deal->stage_id)?->name,
            'allowed_stage_ids' => $allowedStageIds,
        ]);
    }


    public function updateStageStatus(Request $request)
    {
        $request->validate([
            'deal_id' => 'required|exists:deals,id',
            'stage_status' => 'required|in:1,2,3',
            'stage_id' => 'required|integer|exists:deal_stages,id|between:1,10',
        ]);

        $deal = Deal::findOrFail($request->deal_id);
        $this->assertDealIsOpen($deal);
        
        // Check access permission
        $accessibleUserIds = $this->getAccessibleUserIds(auth()->id());
        
        if (!in_array($deal->owner_id, $accessibleUserIds)) {
            abort(403, 'You do not have permission to update this deal.');
        }

        $dealStageStatus = DealStageStatus::updateOrCreate(
            ['deal_id' => $request->deal_id, 'stage_id' => $request->stage_id],
            ['stage_status' => $request->stage_status]
        );

        $highestStageId = DealStageStatus::where('deal_id', $request->deal_id)
            ->max('stage_id');

        $highestStageStatus = DealStageStatus::where('deal_id', $request->deal_id)
            ->where('stage_id', $highestStageId)
            ->value('stage_status');

        if ($highestStageStatus == 3) {
            if ($deal) {
                $this->assertStageTransition($deal, (int) $highestStageId);
                $this->assertStageRequirements($deal, (int) $highestStageId);
                if (in_array((int) $highestStageId, [9, 10], true)) {
                    return $this->requestClosureApproval($deal, (int) $highestStageId);
                }
                $deal->stage_id = $highestStageId;
                $deal->save();
            }
        }

        return response()->json([
            'success' => true,
            'message' => 'Deal Status updated successfully!',
        ]);
    }


    public function dashboard()
    {
        $stages = DealStage::all();
        $accessibleUserIds = $this->getAccessibleUserIds(auth()->id());

        $deals = Deal::whereIn('owner_id', $accessibleUserIds)
            ->where('business_id', auth()->user()->business_id)
            ->where(function ($query) {
                $query->whereNull('status')->orWhere('status', 'active');
            })
            ->get()
            ->map(function ($deal) {
                $deal->current_stage_id = $deal->stage_id;

                return $deal;
            })
            ->groupBy('current_stage_id');

        return view('deals.deals-dashboard', compact('deals', 'stages'));
    }


    public function updateStage(Request $request, $dealId)
    {

        $request->validate([
            'stage_id' => 'required|exists:deal_stages,id|between:1,8',
        ]);

        $deal = Deal::find($dealId);

        if (!$deal) {
            return response()->json(['message' => 'Deal not found'], 404);
        }

        // Check access permission
        $accessibleUserIds = $this->getAccessibleUserIds(auth()->id());
        
        if (!in_array($deal->owner_id, $accessibleUserIds)) {
            return response()->json(['message' => 'You do not have permission to update this deal.'], 403);
        }
        $this->assertDealIsOpen($deal);
        $this->assertStageTransition($deal, (int) $request->stage_id);
        $this->assertStageRequirements($deal, (int) $request->stage_id);

        if (in_array((int) $request->stage_id, [9, 10], true)) {
            return $this->requestClosureApproval($deal, (int) $request->stage_id);
        }

        $deal->stage_id = $request->stage_id;
        $deal->closure_approval_status = null;
        $deal->requested_closure_stage_id = null;
        $deal->save();

        return response()->json(['message' => 'Deal status updated successfully']);
    }

    private function requestClosureApproval(Deal $deal, int $targetStageId)
    {
        $this->assertDealIsOpen($deal);
        abort_if($deal->closure_approval_status === 'pending', 409, 'This Deal already has a pending closure approval.');
        $this->assertStageTransition($deal, $targetStageId);
        $this->assertStageRequirements($deal, $targetStageId);
        if (! $this->hasEligibleClosureApprover($deal)) {
            return response()->json([
                'message' => 'Assign a reporting manager or a user with the Deal closure approval permission before this Deal can be closed.',
            ], 422);
        }

        $action = $deal->closure_approval_status === 'rejected' ? 'resubmitted' : 'submitted';
        $deal->update([
            'closure_approval_status' => 'pending',
            'requested_closure_stage_id' => $targetStageId,
            'closure_requested_by' => auth()->id(),
            'closure_requested_at' => now(),
            'closure_approved_by' => null,
            'closure_approved_at' => null,
            'closure_approval_note' => null,
        ]);
        DealApprovalHistory::create([
            'business_id' => $deal->business_id, 'deal_id' => $deal->id,
            'action' => $action, 'requested_stage_id' => $targetStageId,
            'actor_id' => auth()->id(), 'note' => request('approval_note'),
        ]);

        $this->notifyDealLifecycle(
            $deal,
            'Closure Approval Requested',
            'Pending Approval',
            "Deal “{$deal->name}” was submitted for closure approval.",
            route('deals.closure-approval.page', $deal)
        );

        return response()->json([
            'message' => 'Deal closure submitted for approval.',
            'approval_status' => 'pending',
            'stage_id' => (int) $deal->stage_id,
            'requested_stage_id' => $targetStageId,
        ], 202);
    }

    public function closureApprovalPage(Deal $deal)
    {
        $deal->loadMissing('assignedUser');
        $this->assertClosureApprovalReviewer($deal);
        abort_unless(
            $deal->closure_approval_status === 'pending' && $deal->requested_closure_stage_id,
            422,
            'This Deal has no pending closure approval.'
        );

        $requestedStage = DealStage::find($deal->requested_closure_stage_id);

        return view('deals.closure-approval', compact('deal', 'requestedStage'));
    }

    public function closureApproval(Request $request, Deal $deal)
    {
        $validated = $request->validate([
            'decision' => 'required|in:approve,reject',
            'note' => [Rule::requiredIf(fn () => $request->decision === 'reject'), 'nullable', 'string', 'max:1000'],
        ]);

        $this->assertClosureApprovalReviewer($deal);
        abort_unless(
            $deal->closure_approval_status === 'pending' && $deal->requested_closure_stage_id,
            422,
            'This Deal has no pending closure approval.'
        );

        if ($validated['decision'] === 'approve') {
            $deal->stage_id = $deal->requested_closure_stage_id;
            $deal->status = 'inactive';
            $deal->closure_approval_status = 'approved';
            $message = 'Deal closure approved and finalized.';
        } else {
            $deal->closure_approval_status = 'rejected';
            $message = 'Deal closure rejected. The Deal remains open.';
        }

        $deal->closure_approved_by = auth()->id();
        $deal->closure_approved_at = now();
        $deal->closure_approval_note = $validated['note'] ?? null;
        $deal->save();
        DealApprovalHistory::create([
            'business_id' => $deal->business_id, 'deal_id' => $deal->id,
            'action' => $validated['decision'] === 'approve' ? 'approved' : 'rejected',
            'requested_stage_id' => $deal->requested_closure_stage_id,
            'actor_id' => auth()->id(), 'note' => $validated['note'] ?? null,
        ]);
        if ($validated['decision'] === 'approve') {
            $deal->closed_at = now();
            $deal->save();

            for ($stageId = 1; $stageId <= (int) $deal->stage_id; $stageId++) {
                DealStageStatus::updateOrCreate(
                    ['deal_id' => $deal->id, 'stage_id' => $stageId],
                    ['stage_status' => 3]
                );
            }
        }

        if ($validated['decision'] === 'approve') {
            $finalStatus = (int) $deal->stage_id === 9 ? 'Closed Won' : 'Closed Lost';
            $this->notifyDealLifecycle(
                $deal,
                "Deal {$finalStatus}",
                $finalStatus,
                "Deal “{$deal->name}” was approved as {$finalStatus} and removed from the active Deal list."
            );
        }

        return redirect()->back()->with('success', $message);
    }

    private function assertClosureApprovalReviewer(Deal $deal): void
    {
        $user = auth()->user();

        abort_unless($user && (int) $deal->business_id === (int) $user->business_id, 403);
        abort_if((int) $deal->closure_requested_by === (int) $user->id, 403,
            'The employee who requested closure cannot approve their own request.');

        $owner = $deal->assignedUser ?: User::find($deal->owner_id);
        $canApprove = $user->is_admin
            || (int) $owner?->reporting_manager_id === (int) $user->id
            || $user->can('approve_deal_closure');

        abort_unless($canApprove, 403, 'Deal closure approval permission is required to approve this closure.');
    }

    private function hasEligibleClosureApprover(Deal $deal): bool
    {
        return $this->closureApprovalRecipients($deal)
            ->contains(fn (User $recipient) => (int) $recipient->id !== (int) auth()->id());
    }

    private function closureApprovalRecipients(Deal $deal)
    {
        $owner = $deal->assignedUser ?: User::find($deal->owner_id);
        $recipients = User::where('business_id', $deal->business_id)
            ->where(function ($query) use ($owner) {
                $query->where('is_admin', 1);
                if ($owner?->reporting_manager_id) {
                    $query->orWhere('id', $owner->reporting_manager_id);
                }
            })
            ->get();

        $approvalPermission = Permission::where([
            'name' => 'approve_deal_closure',
            'guard_name' => 'web',
        ])->first();

        if ($approvalPermission) {
            $recipients = $recipients->merge(
                User::permission($approvalPermission)
                    ->where('business_id', $deal->business_id)
                    ->get()
            );
        }

        return $recipients->unique('id')->values();
    }

    public function storeWonClosureDetails(Request $request, Deal $deal)
    {
        $this->authorizeDealUpdate($deal);

        $data = $request->validate([
            'final_proposal' => 'required|in:none,mark,completed',
            'final_quotation' => 'required|in:none,mark,completed',
            'final_invoice' => 'required|in:none,mark,completed',
            'closed_date' => 'required|date',
            'delivery_date' => 'required|date|after_or_equal:closed_date',
            'payment_status' => 'required|in:none,advance_paid,full_paid',
            'sent_feedback' => 'required|string|max:2000',
        ]);

        abort_if($deal->closure_approval_status === 'pending', 409,
            'This Deal already has a pending closure approval.');

        if (! $this->hasEligibleClosureApprover($deal)) {
            return back()->withErrors([
                'closure' => 'Assign a reporting manager or a user with the Deal closure approval permission before this Deal can be closed.',
            ])->withInput();
        }

        DB::transaction(function () use ($deal, $data) {
            WonLost::updateOrCreate(
                ['deal_id' => $deal->id, 'status' => 'WON'],
                $data + ['lead_id' => null, 'ticket_id' => null]
            );

            $deal->update([
                'closing_date' => Carbon::parse($data['closed_date'])->toDateString(),
                'delivery_status' => 'scheduled',
                'payment_status' => $data['payment_status'],
            ]);
        });

        $response = $this->requestClosureApproval($deal->fresh(), 9);
        $message = $response->getData(true)['message'] ?? 'Deal Won details saved.';

        return redirect()->route('deals.view', ['id' => $deal->id])->with('success', $message);
    }

    public function reopen(Request $request, Deal $deal)
    {
        $validated = $request->validate(['reason' => 'required|string|min:10|max:2000']);
        abort_unless($deal->business_id === auth()->user()->business_id, 403);
        abort_unless(in_array((int) $deal->stage_id, [9, 10], true), 422, 'Only closed Deals can be reopened.');
        $owner = User::find($deal->owner_id);
        $canReopen = auth()->user()->is_admin
            || (int) $owner?->reporting_manager_id === (int) auth()->id()
            || auth()->user()->can('reopen_closed_deals');
        abort_unless($canReopen, 403, 'Management permission is required to reopen a closed Deal.');

        $fromStage = (int) $deal->stage_id;
        $deal->update([
            'stage_id' => 8, 'status' => 'active', 'closure_approval_status' => null,
            'requested_closure_stage_id' => null, 'reopened_at' => now(), 'closed_at' => null,
            'reopen_count' => $deal->reopen_count + 1,
        ]);
        DealStageStatus::where('deal_id', $deal->id)->whereIn('stage_id', [9, 10])->delete();
        DealStageStatus::updateOrCreate(
            ['deal_id' => $deal->id, 'stage_id' => 8],
            ['stage_status' => 1]
        );
        DealReopenHistory::create([
            'business_id' => $deal->business_id, 'deal_id' => $deal->id,
            'from_stage_id' => $fromStage, 'to_stage_id' => 8,
            'reopened_by' => auth()->id(), 'reason' => $validated['reason'],
        ]);
        DealApprovalHistory::create([
            'business_id' => $deal->business_id, 'deal_id' => $deal->id,
            'action' => 'reopened', 'requested_stage_id' => 8,
            'actor_id' => auth()->id(), 'note' => $validated['reason'],
        ]);
        $this->notifyDealLifecycle(
            $deal,
            'Deal Reopened',
            'Awaiting Decision',
            "Deal “{$deal->name}” was reopened and returned to Awaiting Decision."
        );
        return redirect()->back()->with('success', 'Deal reopened at Awaiting Decision.');
    }

    public function storeCompetitor(Request $request, Deal $deal)
    {
        abort_unless($deal->business_id === auth()->user()->business_id, 403);
        $this->assertDealIsOpen($deal);
        $data = $request->validate([
            'name' => 'required|string|max:255', 'website' => 'nullable|url|max:255',
            'estimated_value' => 'nullable|numeric|min:0', 'strength' => 'nullable|string|max:255',
            'weakness' => 'nullable|string|max:255', 'status' => 'required|in:active,won,lost',
            'notes' => 'nullable|string|max:2000',
        ]);
        $deal->competitors()->create($data + ['business_id' => $deal->business_id]);
        return redirect()->back()->with('success', 'Competitor added.');
    }

    public function destroyCompetitor(Deal $deal, DealCompetitor $competitor)
    {
        abort_unless($deal->business_id === auth()->user()->business_id && $competitor->deal_id === $deal->id, 403);
        $this->assertDealIsOpen($deal);
        $competitor->delete();
        return redirect()->back()->with('success', 'Competitor removed.');
    }

    private function assertDealIsOpen(Deal $deal): void
    {
        abort_if(in_array((int) $deal->stage_id, [9, 10], true), 423, 'Closed Deals are read-only. Reopen the Deal first.');
    }

    private function assertStageTransition(Deal $deal, int $toStageId): void
    {
        abort_unless(DealStage::whereKey($toStageId)->exists(), 422, 'The selected Deal Status is invalid.');
    }

    private function assertStageRequirements(Deal $deal, int $stageId): void
    {
        $required = [
            9 => ['delivery_status', 'payment_status'],
            10 => ['lost_reason'],
        ][$stageId] ?? [];
        $missing = collect($required)->filter(fn ($field) => blank($deal->{$field}))->values();
        abort_if($missing->isNotEmpty(), 422, 'Complete required fields before this status: '.$missing->join(', ').'.');
    }

    private function notifyDealLifecycle(
        Deal $deal,
        string $title,
        string $status,
        string $message,
        ?string $url = null
    ): void {
        $recipients = $this->closureApprovalRecipients($deal);

        if ($recipients->isEmpty()) {
            return;
        }

        $event = match (true) {
            str_contains(strtolower($title), 'approval requested') => 'approval_requested',
            str_contains(strtolower($title), 'approved') => 'approval_approved',
            str_contains(strtolower($title), 'rejected') => 'approval_rejected',
            str_contains(strtolower($status), 'won') => 'deal_closed_won',
            str_contains(strtolower($status), 'lost') => 'deal_closed_lost',
            default => 'document_updated',
        };
        app(CrmNotificationService::class)->send(
            $recipients,
            $event,
            $title,
            $message,
            $url ?? route('deals.view', ['id' => $deal->id]),
            'Deal',
            $deal->id,
            $status
        );
    }


    public function create(Request $request)
    {
        $businessId = auth()->user()->business_id;
        $selectedLeadId = $request->filled('leadId') ? $request->integer('leadId') : null;
        $selectedLead = $selectedLeadId
            ? Lead::where('business_id', $businessId)->whereNull('moved_at')->findOrFail($selectedLeadId)
            : null;
        if ($selectedLead) {
            abort_unless((int) $selectedLead->stage_id === 6, 422, 'Change the Lead Status to Qualified before converting it to a Deal.');
        }
        $selectedContactId = $request->filled('contactId')
            ? $request->integer('contactId')
            : ($selectedLead?->primary_contact_id ?? null);
        $languages = Language::all();
        $contacts = Contact::with('assignedUser:id,name')->where('business_id', $businessId)
            ->where(fn ($query) => $query->where('contact_owner_id', auth()->id())
                ->when($selectedContactId, fn ($ownerQuery) => $ownerQuery->orWhere('id', $selectedContactId)))
            ->orderBy('first_name')->limit(100)->get();
        if ($selectedContactId && !$contacts->contains('id', $selectedContactId)) {
            $selectedContact = Contact::where('business_id', $businessId)->find($selectedContactId);
            if ($selectedContact) $contacts->prepend($selectedContact);
        }
        $accounts = Account::where('business_id', $businessId)
            ->latest('updated_at')->limit(100)->get();
        $leads = Lead::where('business_id', $businessId)->whereNull('moved_at')
            ->latest('updated_at')->limit(50)->get(['id', 'topic', 'first_name', 'last_name', 'primary_contact_id', 'stage_id']);
        if ($selectedLead && !$leads->contains('id', $selectedLead->id)) {
            $leads->prepend($selectedLead);
        }
        $priorities = Priority::where(fn ($query) => $query->where('business_id', $businessId)->orWhereNull('business_id'))->get();
        $billing_frequencies = BillingFrequency::whereIn('name', ['Daily', 'Weekly', 'Monthly', 'Yearly'])
            ->orderBy('id')
            ->get();
        $products = Product::where('business_id', $businessId)->latest('updated_at')->limit(100)->get();
        $stages = DealStage::all();
        $companies = Company::latest('updated_at')->limit(100)->get();
        $users = $this->getAccessibleUsers();
        $documentTemplates = DocumentTemplate::where('business_id', $businessId)
            ->orderBy('type')->orderBy('name')->get();
        $existingDeals = Deal::where('business_id', auth()->user()->business_id)
            ->where(function ($query) {
                $query->whereNull('status')->orWhere('status', 'active');
            })
            ->latest('updated_at')
            ->limit(100)->get();

        $ticketId = $request->query('ticketId');

        return view('deals.deals-create', compact('languages', 'contacts', 'accounts', 'leads', 'priorities', 'billing_frequencies', 'products', 'stages', 'companies', 'users', 'existingDeals', 'documentTemplates', 'selectedLeadId', 'selectedContactId', 'ticketId'));
    }

    public function store(Request $request)
    {
        $this->normalizeDealAccounts($request);
        if (! $request->filled('primary_contact')) {
            $request->merge(['primary_contact' => collect($request->input('contact_ids', []))->filter()->first()]);
        }
        if (! $request->filled('lead_owner')) {
            $countryId = $request->filled('primary_contact') ? Contact::whereKey($request->primary_contact)->value('country_id') : null;
            $request->merge(['lead_owner'=>OperationsFoundationController::nextAssignee((int)auth()->user()->business_id,'deals',$countryId)]);
        }
        // Validate that the selected owner is accessible to current user
        $accessibleUserIds = $this->getAccessibleUserIds(auth()->id());
        
        $request->validate([
            'lead_owner' => 'required|in:' . implode(',', $accessibleUserIds),
            'deal_name' => 'required|string|max:255',
            'stage_id' => 'required|exists:deal_stages,id|between:1,8',
            'currency' => 'required|integer|between:1,6',
            'create_date' => 'required|date',
            'billing_frequency' => 'nullable|exists:billing_frequencies,id',
            'billing_repeat' => 'nullable|required_with:billing_frequency|integer|min:1|max:999',
            'primary_contact' => ['required', Rule::exists('contacts', 'id')->where(fn ($query) => $query->where('business_id', auth()->user()->business_id))],
            'contact_ids' => ['nullable', 'array'],
            'contact_ids.*' => ['integer', Rule::exists('contacts', 'id')->where(fn ($query) => $query->where('business_id', auth()->user()->business_id))],
            'total_amount' => ['required', 'numeric', 'min:0'],
            'sub_total' => ['nullable', 'numeric', 'min:0'],
            'total_discount' => ['nullable', 'numeric', 'min:0'],
            'total_tax' => ['nullable', 'numeric', 'min:0'],
            'grand_total' => ['nullable', 'numeric', 'min:0'],
            'trade_type' => ['nullable', Rule::in(['quotation','invoice','proposal'])],
            'document_template_id' => ['nullable', Rule::exists('document_templates', 'id')->where(fn ($query) => $query->where('business_id', auth()->user()->business_id))],
            'valid_until' => ['nullable', 'date', 'after_or_equal:create_date'],
            'close_probability' => [Rule::requiredIf(fn () => (int) $request->stage_id >= 6), 'nullable', 'integer', 'min:0', 'max:100'],
            'comments' => [Rule::requiredIf(fn () => in_array((int) $request->stage_id, [4,5,7], true)), 'nullable', 'string'],
            'closing_date' => ['required', 'date'],
            'lost_reason' => 'nullable|required_if:stage_id,10|string|max:255',
            'lost_competitor' => 'nullable|string|max:255',
            'delivery_status' => 'nullable|string|max:255',
            'payment_status' => 'nullable|string|max:255',
            'payment_method' => 'nullable|string|max:255',
            'account_ids' => 'nullable|array',
            'primary_account' => ['nullable', Rule::exists('accounts', 'id')->where(
                fn ($query) => $query->where('business_id', auth()->user()->business_id)
            )],
            'account_ids.*' => [
                Rule::exists('accounts', 'id')->where(
                    fn ($query) => $query->where('business_id', auth()->user()->business_id)
                ),
            ],
            'deal_type' => 'required|in:new_business,existing_business',
            'languages' => 'nullable|array',
            'languages.*' => ['integer', Rule::exists('languages', 'id')->where(fn ($query) => $query->where('status', 1))],
        ]);
        $this->validateSelectedDocumentTemplate($request);

        if ($request->filled('source_lead_id')) {
            $conversionLead = Lead::where('id', $request->source_lead_id)
                ->where('business_id', auth()->user()->business_id)->firstOrFail();
            abort_unless((int) $conversionLead->stage_id === 6, 422, 'Only Qualified Leads can be converted.');
        }

        $deal = Deal::create([
            'business_id' => auth()->user()->business_id,
            'name' => $request->deal_name,
            'owner_id' => $request->lead_owner,
            'contact_id' => $request->primary_contact,
            'account_id' => $request->primary_account,
            'lead_id' => $request->lead_name,
            'company_id' => $request->company_id,
            'sic_code' => $request->sic_code,
            'time_frame_id' => $request->time_frame,
            'priority_id' => $request->priority,
            'deal_type' => $request->deal_type,
            'parent_deal_id' => null,
            'currency_id' => $request->currency,
            'estimate_amount' => $request->total_amount,
            'close_probability' => $request->integer('close_probability'),
            'forecast_value' => round(((float) $request->total_amount) * $request->integer('close_probability') / 100, 2),
            'comments' => $request->comments,
            'trade_type' => $request->trade_type,
            'document_template_id' => $request->document_template_id,
            'valid_until' => $request->valid_until,
            'lost_reason' => $request->lost_reason,
            'lost_competitor' => $request->lost_competitor,
            'delivery_status' => $request->delivery_status,
            'payment_status' => $request->payment_status,
            'payment_method' => $request->payment_method,
            'closing_date' => $request->closing_date,
            'create_date' => $request->create_date,
            'billing_frequency_id' => $request->billing_frequency,
            'billing_frequency_repeat_status' => $request->billing_repeat,
            'next_billing_at' => $this->calculateNextBillingAt($request),
            'campaign_source_id' => $request->campaign_source,
            'contact_method' =>  json_encode($request->input('contact_methods')),
            'total' => $request->sub_total,
            'discount' => $request->total_discount,
            'tax' => $request->total_tax,
            'grand_total' => $request->input('grand_total', $request->total_amount),
            'stage_id' => $request->stage_id,
            'status' => $request->input('status', 'active'),
        ]);
        OperationsFoundationController::recordAssignment($deal, 'deals');
        if ($owner = User::find($deal->owner_id)) {
            app(CrmNotificationService::class)->assignment(
                $owner, $deal, 'Deal', route('deals.view', ['id' => $deal->id])
            );
        }

        $deal->accounts()->sync(collect($request->account_ids)->filter()->unique()->values());
        $this->syncDealContacts($deal, $request);
        foreach (collect($request->input('languages', []))->map(fn ($id) => (int) $id)->unique() as $languageId) {
            ContactLanguage::create(['deal_id' => $deal->id, 'language_id' => $languageId]);
        }

        if ($request->filled('source_contact_id')) {
            Contact::where('id', $request->source_contact_id)
                ->where('business_id', auth()->user()->business_id)
                ->firstOrFail()
                ->recordMove('deal', $deal, $request->input('move_reason'));
        }

        if ($request->filled('source_ticket_id')) {
            $sourceTicket = \App\Models\Ticket::where('id', $request->source_ticket_id)
                ->where('business_id', auth()->user()->business_id)->firstOrFail();
            $sourceTicket->recordMove('deal', $deal, $request->input('move_reason'));
        }

        if ($request->filled('source_lead_id')) {
            $sourceLead = Lead::where('id', $request->source_lead_id)
                ->where('business_id', auth()->user()->business_id)->firstOrFail();
            abort_unless((int) $sourceLead->stage_id === 6, 422, 'Only Qualified Leads can be converted.');
            $duplicateDealIds = Deal::where('business_id', $sourceLead->business_id)
                ->where('id', '!=', $deal->id)
                ->where(fn ($query) => $query->where('lead_id', $sourceLead->id)
                    ->when($sourceLead->primary_contact_id, fn ($q) => $q->orWhere('contact_id', $sourceLead->primary_contact_id)))
                ->pluck('id')->all();
            $sourceLead->recordConversion($deal, $duplicateDealIds, $request->input('conversion_notes'));
        }

        app(\App\Services\DealInventoryService::class)->replaceFromArrays($deal, $request->all());
        foreach ([] as $index => $product) {
            if (!is_null($product) && !is_null($request->input('qty')[$index]) && !is_null($request->input('price')[$index])) {
                DealProductLine::create([
                    'deal_id' => $deal->id,
                    'currency_id' => $request->input('currency'),
                    'product_id' => $product,
                    'unit_id' => $request->input('unit')[$index],
                    'discount' => $request->input('discount')[$index],
                    'tax' => $request->input('tax')[$index],
                    'product_price' => $request->input('price')[$index],
                    'quantity' => $request->input('qty')[$index],
                    'line_total' => $request->input('total')[$index],
                ]);
            }
        }


        foreach ($request->input('phones', []) as $index => $phone) {
            if (!empty($phone)) { // only create if phone is not null or empty
                ContactPhoneNum::create([
                    'deal_id' => $deal->id,
                    'phone_type_id' => $request->input('phone_types')[$index],
                    'phone' => $phone,
                ]);
            }
        }


        if ($response = $this->handleAfterSave($deal->fresh(), $request)) {
            return $response;
        }

        return redirect()->route('deals')->with('success', 'Deal created successfully!');
    }

    public function show(Request $request)
    {

        $dealId = $request->query('id');
        $deal = Deal::with([
            'contactPhones',
            'priority',
            'assignedUser',
            'parentDeal',
            'billingFrequency',
            'accounts',
            'dealProductLine.product', 'dealProductLine.discountApproval',
            'files.uploader',
            'activities', 'tradeDocuments.lines', 'projects', 'cases', 'surveys',
            'competitors', 'approvalHistories.actor', 'approvalHistories.requestedStage',
            'reopenHistories.actor',
        ])->where('business_id', auth()->user()->business_id)->findOrFail($dealId);

        // Check if current user can access this deal's owner
        $accessibleUserIds = $this->getAccessibleUserIds(auth()->id());
        
        if (!in_array($deal->owner_id, $accessibleUserIds)) {
            abort(403, 'You do not have permission to view this deal.');
        }

        $notes = Note::visibleTo(auth()->user())->where('deal_id', $dealId)->get();
        $activities = $deal->activities()->latest('start_date')->latest('created_at')->get();
        $invitedMeetings = $activities->filter(fn ($activity) =>
            (int) $activity->activity_type_id === 2 || filled($activity->meeting_type) || filled($activity->meeting_integration_id)
        );

        $won = WonLost::where('deal_id', $dealId)
            ->where('status', 'WON')
            ->get();
        $lost = WonLost::where('deal_id', $dealId)
            ->where('status', 'LOST')
            ->get();

        $stages = DealStage::all();
        $products = Product::where('business_id', auth()->user()->business_id)
            ->where(function ($query) {
                $query->whereNull('status')->orWhere('status', 'active');
            })
            ->orderBy('product_name')
            ->get();
        $priceBooks = PriceBook::with(['items.product'])
            ->where('business_id', $deal->business_id)->where('active', true)
            ->where('currency', $deal->currency_name)->orderBy('name')->get();
        $tradeDocuments = $deal->tradeDocuments()->with('lines')->latest()->get();
        $projects = $deal->projects()->latest()->get();
        $cases = $deal->cases()->where('business_id', $deal->business_id)->latest()->get();
        $surveys = $deal->surveys()->where('surveys.business_id', $deal->business_id)
            ->orderByDesc('surveys.created_at')->get();
        $competitors = $deal->competitors()->latest()->get();
        $approvalHistories = $deal->approvalHistories()->with(['actor', 'requestedStage'])->latest()->get();
        $reopenHistories = $deal->reopenHistories()->with('actor')->latest()->get();
        $reporting = [
            'activity_count' => $activities->count(),
            'completed_activities' => $activities->where('lifecycle_status', 'completed')->count(),
            'product_count' => $deal->dealProductLine->count(),
            'document_total' => (float) $tradeDocuments->sum('total'),
            'paid_total' => (float) $tradeDocuments->sum('paid_amount'),
            'forecast_value' => (float) $deal->forecast_value,
            'days_open' => $deal->create_date ? $deal->create_date->diffInDays($deal->closed_at ?: now()) : null,
        ];
        $canReopen = in_array((int) $deal->stage_id, [9, 10], true) && (
            auth()->user()->is_admin ||
            (int) $deal->assignedUser?->reporting_manager_id === (int) auth()->id() ||
            auth()->user()->can('reopen_closed_deals')
        );
        $allowedStageTransitions = $this->allowedDealStageTransitions((int) $deal->business_id);

        // dd($contacts);
        return view('deals.deals-view', compact(
            'deal', 'notes', 'activities', 'invitedMeetings', 'won', 'lost', 'stages', 'products',
            'tradeDocuments', 'projects', 'cases', 'surveys', 'competitors', 'approvalHistories',
            'reopenHistories', 'reporting', 'canReopen', 'priceBooks', 'allowedStageTransitions'
        ));
    }

    private function allowedDealStageTransitions(int $businessId)
    {
        $stageIds = DealStage::orderBy('id')->pluck('id')->map(fn ($stageId) => (int) $stageId);

        return $stageIds
            ->filter(fn ($stageId) => ! in_array($stageId, [9, 10], true))
            ->mapWithKeys(fn ($fromStageId) => [
                $fromStageId => $stageIds->reject(fn ($stageId) => $stageId === $fromStageId)->values(),
            ]);
    }

    public function edit(Request $request)
    {
        $dealId = $request->query('id');

        $deal = Deal::with(['contactPhones', 'contactLanguages.language', 'dealProductLine', 'contacts', 'accounts'])->findOrFail($dealId);
        $this->assertDealIsOpen($deal);

        // Check access permission
        $accessibleUserIds = $this->getAccessibleUserIds(auth()->id());
        
        if (!in_array($deal->owner_id, $accessibleUserIds)) {
            abort(403, 'You do not have permission to edit this deal.');
        }

        $businessId = auth()->user()->business_id;
        $languages = Language::all();
        $selectedDealContactIds = $deal->contacts->pluck('id')->push($deal->contact_id)->filter()->unique();
        $contacts = Contact::with('assignedUser:id,name')->where('business_id', $businessId)
            ->where(fn ($query) => $query->where('contact_owner_id', $deal->owner_id)
                ->orWhereIn('id', $selectedDealContactIds))
            ->orderBy('first_name')->limit(100)->get();
        $leads = Lead::where('business_id', $businessId)->orderBy('topic')->get();
        $priorities = Priority::where(fn ($query) => $query->where('business_id', $businessId)->orWhereNull('business_id'))->get();
        $billing_frequencies = BillingFrequency::whereIn('name', ['Daily', 'Weekly', 'Monthly', 'Yearly'])
            ->orWhere('id', $deal->billing_frequency_id)
            ->orderBy('id')
            ->get();
        $products = Product::where('business_id', $businessId)->orderBy('product_name')->get();
        $stages = DealStage::all();
        $accounts = Account::where('business_id', auth()->user()->business_id)
            ->orderBy('account_name')->get();
        $companies = Company::all();
        $users = $this->getAccessibleUsers();
        $documentTemplates = DocumentTemplate::where('business_id', $businessId)->orderBy('type')->orderBy('name')->get();
        $lastInteraction = $deal->activities()->latest('updated_at')->first();
        $latestTradeDocument = $deal->tradeDocuments()->latest()->first();
        $existingDeals = Deal::where('business_id', auth()->user()->business_id)
            ->whereKeyNot($deal->id)
            ->orderBy('name')
            ->get();

        // dd($deal);
        $dealProductLines = $deal->dealProductLine;

        return view('deals.deals-edit', compact('deal', 'languages', 'contacts', 'leads', 'priorities', 'billing_frequencies', 'dealProductLines', 'products', 'stages', 'accounts', 'companies', 'users', 'existingDeals', 'documentTemplates', 'lastInteraction', 'latestTradeDocument'));
    }



    public function update(Request $request, $id)
    {
        $this->normalizeDealAccounts($request);
        if (! $request->filled('primary_contact')) {
            $request->merge(['primary_contact' => collect($request->input('contact_ids', []))->filter()->first()]);
        }
        $deal = Deal::findOrFail($id);
        $previousOwnerId = (int) $deal->owner_id;
        $this->assertDealIsOpen($deal);

        // Check access permission
        $accessibleUserIds = $this->getAccessibleUserIds(auth()->id());
        
        if (!in_array($deal->owner_id, $accessibleUserIds)) {
            abort(403, 'You do not have permission to update this deal.');
        }

        // Validate that the new owner is accessible
        $request->validate([
            'lead_owner' => 'required|in:' . implode(',', $accessibleUserIds),
            'deal_name' => 'required|string|max:255',
            'primary_contact' => ['required', Rule::exists('contacts', 'id')->where(fn ($query) => $query->where('business_id', auth()->user()->business_id))],
            'contact_ids' => ['nullable', 'array'],
            'contact_ids.*' => ['integer', Rule::exists('contacts', 'id')->where(fn ($query) => $query->where('business_id', auth()->user()->business_id))],
            'currency' => 'required|integer|between:1,6',
            'create_date' => 'required|date',
            'billing_frequency' => 'nullable|exists:billing_frequencies,id',
            'billing_repeat' => 'nullable|required_with:billing_frequency|integer|min:1|max:999',
            'stage_id' => 'required|exists:deal_stages,id|between:1,8',
            'total_amount' => [Rule::requiredIf(fn () => (int) $request->stage_id >= 2), 'nullable', 'numeric', 'min:0'],
            'sub_total' => ['nullable', 'numeric', 'min:0'],
            'total_discount' => ['nullable', 'numeric', 'min:0'],
            'total_tax' => ['nullable', 'numeric', 'min:0'],
            'grand_total' => ['nullable', 'numeric', 'min:0'],
            'trade_type' => ['nullable', Rule::in(['quotation','invoice','proposal'])],
            'document_template_id' => ['nullable', Rule::exists('document_templates', 'id')->where(fn ($query) => $query->where('business_id', auth()->user()->business_id))],
            'valid_until' => ['nullable', 'date', 'after_or_equal:create_date'],
            'close_probability' => [Rule::requiredIf(fn () => (int) $request->stage_id >= 6), 'nullable', 'integer', 'min:0', 'max:100'],
            'comments' => [Rule::requiredIf(fn () => in_array((int) $request->stage_id, [4,5,7], true)), 'nullable', 'string'],
            'closing_date' => ['required', 'date'],
            'lost_reason' => [Rule::requiredIf(fn () => (int) $request->stage_id === 8), 'nullable', 'string', 'max:255'],
            'lost_competitor' => [Rule::requiredIf(fn () => (int) $request->stage_id === 8), 'nullable', 'string', 'max:255'],
            'loss_category' => [Rule::requiredIf(fn () => (int) $request->stage_id === 8), 'nullable', Rule::in(['price','product_fit','service','competitor','timing','other'])],
            'loss_notes' => [Rule::requiredIf(fn () => (int) $request->stage_id === 8), 'nullable', 'string', 'max:5000'],
            'reengagement_date' => 'nullable|date',
            'delivery_status' => 'nullable|string|max:255',
            'payment_status' => 'nullable|string|max:255',
            'payment_method' => 'nullable|string|max:255',
            'account_ids' => 'nullable|array',
            'primary_account' => ['nullable', Rule::exists('accounts', 'id')->where(
                fn ($query) => $query->where('business_id', auth()->user()->business_id)
            )],
            'account_ids.*' => [
                Rule::exists('accounts', 'id')->where(
                    fn ($query) => $query->where('business_id', auth()->user()->business_id)
                ),
            ],
            'deal_type' => 'required|in:new_business,existing_business',
            'languages' => 'nullable|array',
            'languages.*' => ['integer', Rule::exists('languages', 'id')->where(fn ($query) => $query->where('status', 1))],
        ]);
        $this->validateSelectedDocumentTemplate($request);
        $this->assertStageTransition($deal, (int) $request->stage_id);

        $deal->update([
            'business_id' => auth()->user()->business_id,
            'name' => $request->deal_name,
            'owner_id' => $request->lead_owner,
            'contact_id' => $request->primary_contact,
            'account_id' => $request->primary_account,
            'lead_id' => $request->lead_name,
            'company_id' => $request->company_id,
            'sic_code' => $request->sic_code,
            'time_frame_id' => $request->time_frame,
            'priority_id' => $request->priority,
            'deal_type' => $request->deal_type,
            'parent_deal_id' => null,
            'currency_id' => $request->currency,
            'estimate_amount' => $request->total_amount,
            'close_probability' => $request->integer('close_probability'),
            'forecast_value' => round(((float) $request->total_amount) * $request->integer('close_probability') / 100, 2),
            'comments' => $request->comments,
            'trade_type' => $request->trade_type,
            'document_template_id' => $request->document_template_id,
            'valid_until' => $request->valid_until,
            'lost_reason' => $request->lost_reason,
            'lost_competitor' => $request->lost_competitor,
            'loss_category'=>$request->loss_category,'loss_price_factor'=>$request->boolean('loss_price_factor'),'loss_fit_factor'=>$request->boolean('loss_fit_factor'),'loss_service_factor'=>$request->boolean('loss_service_factor'),'loss_notes'=>$request->loss_notes,'reengagement_date'=>$request->reengagement_date,
            'delivery_status' => $request->delivery_status,
            'payment_status' => $request->payment_status,
            'payment_method' => $request->payment_method,
            'closing_date' => $request->closing_date,
            'create_date' => $request->create_date,
            'billing_frequency_id' => $request->billing_frequency,
            'billing_frequency_repeat_status' => $request->billing_repeat,
            'next_billing_at' => $this->calculateNextBillingAt($request),
            'campaign_source_id' => $request->campaign_source,
            'contact_method' =>  json_encode($request->input('contact_methods')),
            'total' => $request->sub_total,
            'discount' => $request->total_discount,
            'tax' => $request->total_tax,
            'grand_total' => $request->input('grand_total', $request->total_amount),
            'stage_id' => $request->stage_id,
            'status' => $request->input('status', $deal->status ?: 'active'),
        ]);
        $deal->contactLanguages()->delete();
        foreach (collect($request->input('languages', []))->map(fn ($languageId) => (int) $languageId)->unique() as $languageId) {
            ContactLanguage::create(['deal_id' => $deal->id, 'language_id' => $languageId]);
        }
        if ($previousOwnerId !== (int) $deal->owner_id
            && ($owner = User::find($deal->owner_id))) {
            app(CrmNotificationService::class)->assignment(
                $owner, $deal, 'Deal', route('deals.view', ['id' => $deal->id])
            );
        }

        $deal->accounts()->sync(collect($request->account_ids)->filter()->unique()->values());
        $this->syncDealContacts($deal, $request);

        if ($request->has('product')) {
            app(\App\Services\DealInventoryService::class)->replaceFromArrays($deal, $request->all());

            foreach ([] as $index => $product) {
                if (!is_null($product) && !is_null($request->input('qty')[$index]) && !is_null($request->input('price')[$index])) {
                    DealProductLine::create([
                        'deal_id' => $deal->id,
                        'currency_id' => $request->input('currency'),
                        'product_id' => $request->input('product')[$index],
                        'unit_id' => $request->input('unit')[$index],
                        'discount' => $request->input('discount')[$index],
                        'tax' => $request->input('tax')[$index],
                        'product_price' => $request->input('price')[$index],
                        'quantity' => $request->input('qty')[$index],
                        'line_total' => $request->input('total')[$index],
                    ]);
                }
            }
        }

        if ($request->has('phones')) {
            $deal->contactPhones()->delete();

            foreach ($request->input('phones', []) as $index => $phone) {
                ContactPhoneNum::create([
                    'deal_id' => $deal->id,
                    'phone_type_id' => $request->input('phone_types')[$index],
                    'phone' => $phone,
                ]);
            }
        }

        if ($response = $this->handleAfterSave($deal->fresh(), $request)) {
            return $response;
        }

        return redirect()->route('deals')->with('success', 'Deal updated successfully!');
    }

    private function validateSelectedDocumentTemplate(Request $request): void
    {
        if (! $request->filled('document_template_id')) {
            return;
        }

        $template = DocumentTemplate::where('business_id', auth()->user()->business_id)
            ->findOrFail($request->integer('document_template_id'));
        abort_unless($request->filled('trade_type'), 422, 'Select a Trade Type before choosing a document template.');
        abort_unless(in_array($template->type, ['all', $request->trade_type], true), 422,
            'The selected template does not match the Trade Type.');
    }

    private function syncDealContacts(Deal $deal, Request $request): void
    {
        $contactIds = collect($request->input('contact_ids', []))
            ->push($request->primary_contact)->filter()->map(fn ($id) => (int) $id)->unique()->values();
        $deal->contacts()->sync($contactIds->mapWithKeys(fn ($id) => [
            $id => ['is_primary' => $id === (int) $request->primary_contact],
        ])->all());
    }

    private function normalizeDealAccounts(Request $request): void
    {
        $primaryAccount = $request->input('primary_account');
        if (! filled($primaryAccount)) {
            $primaryAccount = collect($request->input('account_ids', []))->filter()->first();
        }

        $request->merge([
            'primary_account' => filled($primaryAccount) ? (int) $primaryAccount : null,
            'account_ids' => filled($primaryAccount) ? [(int) $primaryAccount] : [],
        ]);
    }

    private function handleAfterSave(Deal $deal, Request $request)
    {
        $action = $request->input('after_save', 'save');
        if ($action === 'save') {
            return null;
        }

        abort_unless(in_array($action, ['approval', 'customer', 'sent', 'print'], true), 422, 'Invalid Deal action.');
        abort_unless($deal->trade_type, 422, 'Select a Trade Type before using a document action.');

        $document = $this->syncTradeDocument($deal);

        if ($action === 'print') {
            $pdf = app(TradeDocumentService::class)->brandedPdf($document);
            return response($pdf, 200, [
                'Content-Type' => 'application/pdf',
                'Content-Disposition' => 'attachment; filename="'.$document->document_number.'.pdf"',
            ]);
        }

        if ($action === 'approval') {
            abort_unless(in_array($document->status, ['draft', 'rejected'], true), 422,
                'Only a draft or rejected document can be sent for approval.');
            $from = $document->status;
            $document->update([
                'status' => 'pending_approval', 'requested_by' => auth()->id(), 'requested_at' => now(),
                'rejection_reason' => null,
                'resubmission_count' => $from === 'rejected' ? $document->resubmission_count + 1 : $document->resubmission_count,
            ]);
            $document->approvalHistories()->create([
                'user_id' => auth()->id(), 'action' => 'pending_approval',
                'from_status' => $from, 'to_status' => 'pending_approval', 'comment' => $deal->comments,
            ]);
            return redirect()->route('deals.view', ['id' => $deal->id])
                ->with('success', ucfirst($deal->trade_type).' sent for approval.');
        }

        abort_unless(in_array($document->status, ['approved', 'sent'], true), 422,
            'The document must be approved before it can be sent.');

        if ($action === 'sent') {
            if ($document->status === 'approved') {
                $document->update(['status' => 'sent', 'sent_at' => now()]);
                $document->approvalHistories()->create([
                    'user_id' => auth()->id(), 'action' => 'sent',
                    'from_status' => 'approved', 'to_status' => 'sent', 'comment' => $deal->comments,
                ]);
            }
            return redirect()->route('deals.view', ['id' => $deal->id])->with('success', 'Document marked as sent.');
        }

        $recipient = ContactEmail::where('contact_id', $deal->contact_id)->value('email')
            ?: ContactEmail::where('deal_id', $deal->id)->value('email');
        abort_unless($recipient, 422, 'Add an email address to the selected Contact before sending.');
        $document->update([
            'share_token' => $document->share_token ?: (string) Str::uuid(),
            'acceptance_token' => $document->acceptance_token ?: (string) Str::uuid(),
            'customer_email' => $recipient,
        ]);
        $delivery = $document->deliveries()->create([
            'sent_by' => auth()->id(), 'recipient' => $recipient, 'status' => 'pending',
        ]);
        try {
            $pdf = app(TradeDocumentService::class)->brandedPdf($document);
            Mail::html(($deal->comments ?: 'Please review the attached document.').'<p><a href="'.route('trade-documents.public', $document->share_token).'">Review and respond</a></p>',
                fn ($mail) => $mail->to($recipient)->subject(ucfirst($document->type).' '.$document->document_number)
                    ->attachData($pdf, $document->document_number.'.pdf', ['mime' => 'application/pdf']));
            $delivery->update(['status' => 'sent', 'sent_at' => now()]);
            $document->update(['status' => 'sent', 'sent_at' => now()]);
        } catch (\Throwable $exception) {
            $delivery->update(['status' => 'failed', 'error' => $exception->getMessage()]);
            return redirect()->route('deals.edit', ['id' => $deal->id])
                ->withErrors(['document' => 'Customer delivery failed: '.$exception->getMessage()]);
        }

        return redirect()->route('deals.view', ['id' => $deal->id])->with('success', 'Document sent to '.$recipient.'.');
    }

    private function syncTradeDocument(Deal $deal): TradeDocument
    {
        $document = $deal->tradeDocuments()
            ->where('type', $deal->trade_type)
            ->whereIn('status', ['draft', 'rejected', 'pending_approval', 'approved', 'sent'])
            ->latest()->first();

        if (! $document) {
            $document = $deal->tradeDocuments()->create([
                'business_id' => $deal->business_id,
                'created_by' => auth()->id(),
                'account_id' => $deal->account_id,
                'contact_id' => $deal->contact_id,
                'salesperson_id' => $deal->owner_id,
                'type' => $deal->trade_type,
                'document_number' => app(TradeDocumentService::class)->nextNumber($deal->business_id, $deal->trade_type),
                'status' => 'draft',
                'currency' => substr(Deal::$currencies[$deal->currency_id] ?? 'USD', 0, 3),
                'issue_date' => optional($deal->create_date)->toDateString() ?: now()->toDateString(),
                'due_date' => optional($deal->closing_date)->toDateString(),
                'valid_until' => optional($deal->valid_until)->toDateString(),
                'template_id' => $deal->document_template_id,
                'notes' => $deal->comments,
            ]);
        }

        if (in_array($document->status, ['draft', 'rejected'], true)) {
            $document->update([
                'account_id' => $deal->account_id, 'contact_id' => $deal->contact_id,
                'salesperson_id' => $deal->owner_id, 'template_id' => $deal->document_template_id,
                'currency' => substr(Deal::$currencies[$deal->currency_id] ?? 'USD', 0, 3),
                'issue_date' => optional($deal->create_date)->toDateString() ?: now()->toDateString(),
                'due_date' => optional($deal->closing_date)->toDateString(),
                'valid_until' => optional($deal->valid_until)->toDateString(), 'notes' => $deal->comments,
                'subtotal' => $deal->total ?: 0, 'discount' => $deal->discount ?: 0,
                'tax' => $deal->tax ?: 0, 'total' => $deal->grand_total ?: $deal->estimate_amount ?: 0,
            ]);
            $document->lines()->delete();
            foreach ($deal->dealProductLine()->with('product')->get() as $line) {
                $base = (float) $line->quantity * (float) $line->product_price;
                $discount = $base * (float) $line->discount / 100;
                $tax = ($base - $discount) * (float) $line->tax / 100;
                $document->lines()->create([
                    'product_id' => $line->product_id,
                    'description' => $line->product_name_snapshot ?: $line->product?->product_name ?: 'Product',
                    'quantity' => $line->quantity, 'unit_price' => $line->product_price,
                    'discount' => $discount, 'tax' => $tax, 'line_total' => $line->line_total,
                ]);
            }
        }

        return $document->fresh(['lines.product', 'template', 'deal']);
    }

    public function addProduct(Request $request, Deal $deal)
    {
        $this->authorizeDealUpdate($deal);

        $validated = $request->validate([
            'product_id' => [
                'required',
                Rule::exists('products', 'id')->where(
                    fn ($query) => $query->where('business_id', auth()->user()->business_id)
                ),
            ],
            'unit_id' => 'required|integer|between:1,3',
            'quantity' => 'required|numeric|min:0.01|max:999999',
            'product_price' => 'required|numeric|min:0|max:999999999999',
            'discount' => 'nullable|numeric|min:0|max:100',
            'tax' => 'nullable|numeric|min:0|max:100',
            'price_book_item_id' => 'nullable|integer|exists:price_book_items,id',
            'exchange_rate' => 'nullable|numeric|gt:0|max:1000000',
            'discount_reason' => 'nullable|string|max:2000',
        ]);

        $line = app(\App\Services\DealInventoryService::class)->add($deal, $validated);
        $this->refreshDealInventoryTotals($deal);
        return back()->with('success', $line->discount_approval_status === 'pending'
            ? 'Product added. The requested discount is pending manager approval.'
            : 'Product added and stock reserved successfully.');

        $product = Product::where('business_id', $deal->business_id)->findOrFail($validated['product_id']);
        $priceBookItem = null;
        $price = (float) $validated['product_price'];
        $maxDiscount = 100.0;
        if (!empty($validated['price_book_item_id'])) {
            $priceBookItem = PriceBookItem::whereKey($validated['price_book_item_id'])
                ->where('product_id', $product->id)
                ->whereHas('priceBook', fn ($query) => $query
                    ->where('business_id', $deal->business_id)->where('active', true))
                ->firstOrFail();
            $price = (float) $priceBookItem->unit_price;
            $maxDiscount = (float) $priceBookItem->max_discount_percent;
        }
        $discount = (float) ($validated['discount'] ?? 0);
        abort_if($discount > $maxDiscount, 422, "Discount exceeds the allowed {$maxDiscount}% threshold.");
        $tax = (float) ($validated['tax'] ?? 0);
        $quantity = (float) $validated['quantity'];
        $exchangeRate = (float) ($validated['exchange_rate'] ?? 1);
        $reserved = (float) InventoryReservation::where('product_id', $product->id)
            ->where('status', 'reserved')->sum('quantity');
        abort_if($product->stock_quantity !== null && $quantity > ((float) $product->stock_quantity - $reserved),
            422, 'Requested quantity is greater than available stock.');
        $price *= $exchangeRate;
        $discounted = $price * (1 - ($discount / 100));
        $lineTotal = ($discounted * $quantity) * (1 + ($tax / 100));

        DB::transaction(function () use ($deal, $validated, $product, $priceBookItem, $quantity, $price, $discount, $tax, $lineTotal, $exchangeRate, $maxDiscount) {
            $deal->dealProductLine()->create([
                'currency_id' => $deal->currency_id,
                'product_id' => $product->id,
                'unit_id' => $validated['unit_id'],
                'quantity' => $quantity,
                'product_price' => $price,
                'discount' => $discount,
                'tax' => $tax,
                'line_total' => round($lineTotal, 2),
                'price_book_item_id' => $priceBookItem?->id,
                'product_name_snapshot' => $product->product_name,
                'unit_price_snapshot' => $price,
                'exchange_rate_snapshot' => $exchangeRate,
                'max_discount_snapshot' => $maxDiscount,
            ]);
            InventoryReservation::create([
                'business_id' => $deal->business_id,
                'deal_id' => $deal->id,
                'product_id' => $product->id,
                'quantity' => $quantity,
                'status' => 'reserved',
                'expires_at' => now()->addDays(30),
            ]);
        });

        $this->refreshDealInventoryTotals($deal);

        return back()->with('success', 'Product added to Deal inventory successfully.');
    }

    public function removeProduct(Deal $deal, DealProductLine $line)
    {
        $this->authorizeDealUpdate($deal);
        abort_unless((int) $line->deal_id === (int) $deal->id, 404);

        app(\App\Services\DealInventoryService::class)->remove($deal, $line);
        $this->refreshDealInventoryTotals($deal);

        return back()->with('success', 'Product removed from Deal inventory.');
    }

    public function decideDiscount(Request $request, Deal $deal, \App\Models\DealDiscountApproval $approval)
    {
        $this->assertDealIsOpen($deal);
        abort_unless($deal->business_id === auth()->user()->business_id
            && $approval->deal_id === $deal->id && $approval->status === 'pending', 404);
        $owner = $deal->assignedUser;
        abort_unless(auth()->user()->is_admin
            || (int)$owner?->reporting_manager_id === (int)auth()->id()
            || auth()->user()->can('approve_deal_closure'), 403);
        $data = $request->validate(['decision'=>'required|in:approved,rejected','decision_note'=>'nullable|string|max:2000']);
        DB::transaction(function() use ($approval,$data,$deal) {
            $line=$approval->line()->lockForUpdate()->firstOrFail();
            if($data['decision']==='approved'){
                $product=Product::where('business_id',$deal->business_id)->lockForUpdate()->findOrFail($line->product_id);
                $reserved=(float)InventoryReservation::where('product_id',$product->id)->where('status','reserved')->sum('quantity');
                abort_if($product->stock_quantity!==null&&(float)$line->quantity>((float)$product->stock_quantity-$reserved),422,'Stock is no longer available.');
                $discount=(float)$approval->requested_discount;
                $line->update(['discount'=>$discount,'discount_approval_status'=>'approved',
                    'line_total'=>round(((float)$line->product_price*(1-$discount/100)*(float)$line->quantity)*(1+(float)$line->tax/100),2)]);
                InventoryReservation::create(['business_id'=>$deal->business_id,'deal_id'=>$deal->id,'product_id'=>$product->id,
                    'quantity'=>$line->quantity,'status'=>'reserved','expires_at'=>now()->addDays(30)]);
            } else $line->update(['discount_approval_status'=>'rejected']);
            $approval->update(['status'=>$data['decision'],'decision_note'=>$data['decision_note']??null,
                'decided_by'=>auth()->id(),'decided_at'=>now()]);
        });
        $this->refreshDealInventoryTotals($deal);
        return back()->with('success','Discount request '.$data['decision'].'.');
    }

    public function uploadFile(Request $request, Deal $deal)
    {
        $this->authorizeDealUpdate($deal);

        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'file' => 'required|file|mimes:pdf,jpg,jpeg,png,webp|max:10240',
        ]);

        $uploaded = $request->file('file');
        $path = $uploaded->store("deal-files/{$deal->business_id}/{$deal->id}", 'public');

        try {
            $deal->files()->create([
                'business_id' => $deal->business_id,
                'uploaded_by' => auth()->id(),
                'name' => $validated['name'],
                'file_path' => $path,
                'original_name' => $uploaded->getClientOriginalName(),
                'mime_type' => $uploaded->getMimeType(),
                'file_size' => $uploaded->getSize(),
            ]);
        } catch (\Throwable $exception) {
            Storage::disk('public')->delete($path);
            throw $exception;
        }

        return back()->with('success', 'Deal file uploaded successfully.');
    }

    public function viewFile(Deal $deal, DealFile $file)
    {
        $this->authorizeDealFileAccess($deal, $file);

        return Storage::disk('public')->response(
            $file->file_path,
            $file->original_name,
            ['Content-Disposition' => 'inline; filename="'.$file->original_name.'"']
        );
    }

    public function downloadFile(Deal $deal, DealFile $file)
    {
        $this->authorizeDealFileAccess($deal, $file);
        return Storage::disk('public')->download($file->file_path, $file->original_name);
    }

    private function authorizeDealFileAccess(Deal $deal, DealFile $file): void
    {
        abort_unless($deal->business_id === auth()->user()->business_id, 403);
        abort_unless((int) $file->deal_id === (int) $deal->id, 404);
        abort_unless(in_array($deal->owner_id, $this->getAccessibleUserIds(auth()->id())), 403);
        abort_unless(Storage::disk('public')->exists($file->file_path), 404);
    }

    private function authorizeDealUpdate(Deal $deal): void
    {
        abort_unless($deal->business_id === auth()->user()->business_id, 403);
        abort_unless(in_array($deal->owner_id, $this->getAccessibleUserIds(auth()->id())), 403);
        abort_unless(auth()->user()->can('update_deals'), 403);
        $this->assertDealIsOpen($deal);
    }

    private function refreshDealInventoryTotals(Deal $deal): void
    {
        $total = (float) $deal->dealProductLine()->sum('line_total');
        $deal->update([
            'total' => $total,
            'estimate_amount' => $total,
            'grand_total' => $total,
        ]);
    }

    private function calculateNextBillingAt(Request $request)
    {
        if (!$request->filled('billing_frequency') || !$request->filled('billing_repeat')) {
            return null;
        }

        $frequency = BillingFrequency::find($request->billing_frequency);
        $interval = max(1, (int) $request->billing_repeat) * max(1, (int) $frequency?->value);
        $date = Carbon::parse($request->create_date);

        return match (strtolower(rtrim((string) $frequency?->type, 's'))) {
            'day' => $date->addDays($interval),
            'week' => $date->addWeeks($interval),
            'month' => $date->addMonthsNoOverflow($interval),
            'year' => $date->addYearsNoOverflow($interval),
            default => null,
        };
    }



    public function lossAnalysis(Request $request)
    {
        $query=Deal::where('business_id',$request->user()->business_id)->where('stage_id',8)->with(['owner','dealProductLine.product']);
        if($request->filled('from'))$query->whereDate('closing_date','>=',$request->from);
        if($request->filled('to'))$query->whereDate('closing_date','<=',$request->to);
        $deals=$query->latest('closing_date')->get();
        return view('deals.loss-analysis',compact('deals'));
    }

    public function destroy($id)
    {
        // Find the lead by ID
        $deal = Deal::findOrFail($id);
        abort_unless((int) $deal->business_id === (int) auth()->user()->business_id, 403);
        $this->assertDealIsOpen($deal);

        // Check access permission
        $accessibleUserIds = $this->getAccessibleUserIds(auth()->id());
        
        if (!in_array($deal->owner_id, $accessibleUserIds)) {
            abort(403, 'You do not have permission to delete this deal.');
        }

        $deal->contactPhones()->delete();
        $deal->dealProductLine()->delete();



        $deal->delete();

        return redirect()->route('deals')->with('success', 'Deal and associated Contact deleted successfully!');
    }
}
