<?php
namespace App\Http\Controllers\Warehouse\FixedAssets;

use App\Http\Controllers\Controller;
use App\Models\Warehouse\FixedAssets\FixedAssetAllocation;
use App\Models\Warehouse\FixedAssets\FixedAssetAllocationItem;
use App\Models\Warehouse\FixedAssets\FixedAsset;
use App\Models\Warehouse\FixedAssets\FixedAssetMovement;
use App\Models\Warehouse\Project;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;

class FixedAssetAllocationController extends Controller
{
    public function __construct()
    {
        $this->middleware('warehouse.access');
        $this->middleware('warehouse.permission:warehouse.fixed-asset-allocations.view')->only(['index', 'show', 'print']);
        $this->middleware('warehouse.permission:warehouse.fixed-asset-allocations.create')->only(['create', 'store']);
        $this->middleware('warehouse.permission:warehouse.fixed-asset-allocations.approve')->only(['pendingApproval', 'approve', 'reject']);
        $this->middleware('warehouse.permission:warehouse.fixed-asset-allocations.allocate')->only(['allocate', 'toggleAssetLocation']);
    }

    public function index(Request $request)
    {
        $query = FixedAssetAllocation::with(['project', 'requester', 'items'])
            ->latest('created_at');

        if ($request->filled('status')) {
            $query->where('status', $request->status);
        }
        if ($request->filled('project_id')) {
            $query->where('project_id', $request->project_id);
        }
        if ($request->filled('search')) {
            $search = $request->search;
            $query->where(function ($q) use ($search) {
                $q->where('allocation_number', 'LIKE', "%{$search}%")
                  ->orWhereHas('project', fn($pq) => $pq->where('project_name', 'LIKE', "%{$search}%"));
            });
        }

        $allocations = $query->paginate(25)->withQueryString();
        $projects = Project::active()->orderBy('project_name')->get();

        return view('warehouse.fixed-assets.allocations.index', compact('allocations', 'projects'));
    }

    public function create()
    {
        $projects = Project::active()->orderBy('project_name')->get();
        $availableAssets = FixedAsset::with(['category', 'currentProject', 'currentDivision'])
            ->where('status', 'active')
            ->whereNotIn('lifecycle_status', ['disposed', 'retired'])
            ->orderBy('asset_name')
            ->get();

        return view('warehouse.fixed-assets.allocations.create', compact('projects', 'availableAssets'));
    }

    public function toggleAssetLocation(Request $request)
    {
        $validated = $request->validate([
            'asset_id' => 'required|exists:fixed_assets,id',
            'project_id' => 'required|exists:projects,id',
        ]);

        try {
            $result = DB::transaction(function () use ($validated) {
                $asset = FixedAsset::with(['currentProject', 'currentDivision'])
                    ->lockForUpdate()
                    ->findOrFail($validated['asset_id']);
                $project = Project::findOrFail($validated['project_id']);
                $userId = Auth::id();

                if ((int) $asset->current_project_id === (int) $project->id) {
                    $lastAllocation = FixedAssetMovement::where('fixed_asset_id', $asset->id)
                        ->whereIn('movement_type', ['allocation', 'allocated', 'transferred', 'location_change'])
                        ->where('to_project_id', $project->id)
                        ->where('reference_type', 'direct_asset_allocation')
                        ->latest('id')
                        ->first();

                    if (! $lastAllocation) {
                        $lastAllocation = FixedAssetMovement::where('fixed_asset_id', $asset->id)
                            ->whereIn('movement_type', ['allocation', 'allocated', 'transferred', 'location_change'])
                            ->where('to_project_id', $project->id)
                            ->latest('id')
                            ->first();
                    }

                    if (! $lastAllocation) {
                        throw new \RuntimeException('Previous asset location was not found for this allocation.');
                    }

                    $fromLocation = $this->assetLocationLabel($asset);
                    $fromProjectId = $asset->current_project_id;
                    $fromDivisionId = $asset->current_division_id;
                    $statusBefore = $asset->lifecycle_status;
                    $asset->update([
                        'lifecycle_status' => $lastAllocation->lifecycle_status_before ?: 'in_warehouse',
                        'current_project_id' => $lastAllocation->from_project_id,
                        'current_division_id' => $lastAllocation->from_division_id,
                        'current_location' => $lastAllocation->from_location,
                    ]);

                    FixedAssetMovement::create([
                        'fixed_asset_id' => $asset->id,
                        'movement_type' => 'return',
                        'movement_date' => now(),
                        'reference_type' => 'direct_asset_allocation_reversal',
                        'reference_id' => $lastAllocation->id,
                        'from_location' => $fromLocation,
                        'to_location' => $lastAllocation->from_location,
                        'from_project_id' => $fromProjectId,
                        'to_project_id' => $lastAllocation->from_project_id,
                        'from_division_id' => $fromDivisionId,
                        'to_division_id' => $lastAllocation->from_division_id,
                        'condition_before' => $asset->condition,
                        'condition_after' => $asset->condition,
                        'lifecycle_status_before' => $statusBefore,
                        'lifecycle_status_after' => $lastAllocation->lifecycle_status_before ?: 'in_warehouse',
                        'performed_by' => $userId,
                        'notes' => 'Asset location reverted from allocation create page.',
                    ]);

                    $asset->refresh()->load(['currentProject', 'currentDivision']);

                    return [
                        'allocated' => false,
                        'asset' => $this->assetLocationPayload($asset),
                        'message' => 'Asset moved back to previous location.',
                    ];
                }

                $fromLocation = $this->assetLocationLabel($asset);
                $fromProjectId = $asset->current_project_id;
                $fromDivisionId = $asset->current_division_id;
                $statusBefore = $asset->lifecycle_status;

                $asset->update([
                    'lifecycle_status' => 'in_use',
                    'current_project_id' => $project->id,
                    'current_division_id' => null,
                    'current_location' => $project->project_name,
                ]);

                FixedAssetMovement::create([
                    'fixed_asset_id' => $asset->id,
                    'movement_type' => 'allocation',
                    'movement_date' => now(),
                    'reference_type' => 'direct_asset_allocation',
                    'from_location' => $fromLocation,
                    'to_location' => $project->project_name,
                    'from_project_id' => $fromProjectId,
                    'to_project_id' => $project->id,
                    'from_division_id' => $fromDivisionId,
                    'to_division_id' => null,
                    'condition_before' => $asset->condition,
                    'condition_after' => $asset->condition,
                    'lifecycle_status_before' => $statusBefore,
                    'lifecycle_status_after' => 'in_use',
                    'performed_by' => $userId,
                    'notes' => 'Asset allocated from allocation create page.',
                ]);

                $asset->refresh()->load(['currentProject', 'currentDivision']);

                return [
                    'allocated' => true,
                    'asset' => $this->assetLocationPayload($asset),
                    'message' => 'Asset allocated to ' . $project->project_name . '.',
                ];
            });

            return response()->json(['success' => true] + $result);
        } catch (\Throwable $e) {
            \Log::error('FixedAssetAllocationController@toggleAssetLocation error: ' . $e->getMessage());
            return response()->json(['success' => false, 'message' => $e->getMessage()], 400);
        }
    }

    public function store(Request $request)
    {
        $request->validate([
            'destination_type' => 'required|in:project,repair_shop,warehouse',
            'project_id' => 'required_if:destination_type,project|nullable|exists:projects,id',
            'division_id' => 'nullable|exists:project_divisions,id',
            'allocation_date' => 'nullable|date',
            'purpose' => 'nullable|string|max:500',
            'expected_return_date' => 'nullable|date|after:today',
            'notes' => 'nullable|string|max:2000',
            'items' => 'required|array|min:1',
            'items.*.fixed_asset_id' => 'required|exists:fixed_assets,id',
            'items.*.condition_at_allocation' => 'nullable|string|max:50',
        ]);

        DB::beginTransaction();
        try {
            $allocation = FixedAssetAllocation::create([
                'allocation_date'  => $request->allocation_date ?: now()->toDateString(),
                'destination_type' => $request->destination_type,
                'project_id'       => $request->destination_type === 'project' ? $request->project_id : null,
                'division_id'      => $request->destination_type === 'project' ? $request->division_id : null,
                'purpose'          => $request->purpose,
                'notes'            => $request->notes,
                'status'           => 'pending_approval',
                'requested_by'     => Auth::id(),
            ]);

            foreach ($request->items as $itemData) {
                $allocation->items()->create([
                    'fixed_asset_id' => $itemData['fixed_asset_id'],
                    'condition_at_allocation' => $itemData['condition_at_allocation'] ?? null,
                ]);
            }

            DB::commit();

            if ($request->expectsJson() || $request->ajax()) {
                return response()->json([
                    'success' => true,
                    'data' => $allocation->load('items'),
                    'message' => 'Asset allocation ' . $allocation->allocation_number . ' created and waiting for allocation.'
                ], 201);
            }

            return redirect()
                ->route('warehouse.fixed-assets.allocations.show', $allocation->id)
                ->with('success', 'Asset allocation ' . $allocation->allocation_number . ' created and waiting for allocation.');
        } catch (\Exception $e) {
            DB::rollBack();
            \Log::error('FixedAssetAllocationController@store error: ' . $e->getMessage());

            if ($request->expectsJson() || $request->ajax()) {
                return response()->json(['success' => false, 'message' => $e->getMessage()], 400);
            }
            return back()->withInput()->with('error', 'Failed to create allocation: ' . $e->getMessage());
        }
    }

    public function show($id)
    {
        $allocation = FixedAssetAllocation::with([
            'project', 'division', 'requester', 'approvedBy', 'allocatedBy',
            'items.fixedAsset.category'
        ])->findOrFail($id);

        return view('warehouse.fixed-assets.allocations.show', compact('allocation'));
    }

    public function pendingApproval()
    {
        $allocations = FixedAssetAllocation::with(['project', 'requester', 'items'])
            ->where('status', 'pending_approval')
            ->latest()
            ->paginate(25);

        return view('warehouse.fixed-assets.allocations.pending-approval', compact('allocations'));
    }

    public function approve($id)
    {
        $allocation = FixedAssetAllocation::findOrFail($id);

        if ($allocation->status !== 'pending_approval') {
            if (request()->expectsJson()) {
                return response()->json(['success' => false, 'message' => 'Only pending approval allocations can be approved.'], 422);
            }
            return back()->with('error', 'Only pending approval allocations can be approved.');
        }

        try {
            $allocation->approve(Auth::id());

            if (request()->expectsJson()) {
                return response()->json(['success' => true, 'message' => 'Allocation approved successfully.']);
            }
            return redirect()->route('warehouse.fixed-assets.allocations.show', $allocation->id)
                ->with('success', 'Allocation approved successfully.');
        } catch (\Exception $e) {
            \Log::error('FixedAssetAllocationController@approve error: ' . $e->getMessage());
            if (request()->expectsJson()) {
                return response()->json(['success' => false, 'message' => $e->getMessage()], 400);
            }
            return back()->with('error', $e->getMessage());
        }
    }

    public function reject(Request $request, $id)
    {
        $allocation = FixedAssetAllocation::findOrFail($id);

        if ($allocation->status !== 'pending_approval') {
            if ($request->expectsJson()) {
                return response()->json(['success' => false, 'message' => 'Only pending approval allocations can be rejected.'], 422);
            }
            return back()->with('error', 'Only pending approval allocations can be rejected.');
        }

        $request->validate(['rejection_reason' => 'required|string|max:500']);

        try {
            $allocation->reject(Auth::id(), $request->rejection_reason);

            if ($request->expectsJson()) {
                return response()->json(['success' => true, 'message' => 'Allocation rejected.']);
            }
            return redirect()->route('warehouse.fixed-assets.allocations.show', $allocation->id)
                ->with('success', 'Allocation has been rejected.');
        } catch (\Exception $e) {
            \Log::error('FixedAssetAllocationController@reject error: ' . $e->getMessage());
            if ($request->expectsJson()) {
                return response()->json(['success' => false, 'message' => $e->getMessage()], 400);
            }
            return back()->with('error', $e->getMessage());
        }
    }

    public function allocate($id)
    {
        $allocation = FixedAssetAllocation::with('items')->findOrFail($id);

        if (! in_array($allocation->status, ['pending_approval', 'approved'], true)) {
            if (request()->expectsJson()) {
                return response()->json(['success' => false, 'message' => 'Only pending allocations can be allocated.'], 422);
            }
            return back()->with('error', 'Only pending allocations can be allocated.');
        }

        try {
            $allocation->allocate(Auth::id());

            if (request()->expectsJson()) {
                return response()->json(['success' => true, 'message' => 'Assets have been allocated successfully.']);
            }
            return redirect()->route('warehouse.fixed-assets.allocations.show', $allocation->id)
                ->with('success', 'Assets have been allocated to the project.');
        } catch (\Exception $e) {
            \Log::error('FixedAssetAllocationController@allocate error: ' . $e->getMessage());
            if (request()->expectsJson()) {
                return response()->json(['success' => false, 'message' => $e->getMessage()], 400);
            }
            return back()->with('error', $e->getMessage());
        }
    }

    public function allocateItem($id, $itemId)
    {
        $allocation = FixedAssetAllocation::with(['items', 'project'])->findOrFail($id);
        $item = $allocation->items()->with('fixedAsset')->findOrFail($itemId);

        if (! in_array($allocation->status, ['pending_approval', 'partially_allocated', 'approved'], true)) {
            return back()->with('error', 'Only pending allocations can be allocated.');
        }

        if ($item->is_allocated) {
            return back()->with('error', 'This asset is already allocated.');
        }

        try {
            DB::transaction(function () use ($allocation, $item) {
                $asset = $item->fixedAsset;

                if (! $asset) {
                    throw new \RuntimeException('Asset not found for this allocation item.');
                }

                $dest           = $allocation->resolveDestination();
                $fromLocation   = $asset->current_location ?: 'Warehouse';
                $fromProjectId  = $asset->current_project_id;
                $fromDivisionId = $asset->current_division_id;
                $statusBefore   = $asset->lifecycle_status;
                $conditionBefore = $asset->condition;

                $item->update([
                    'is_allocated' => true,
                    'allocated_at' => now(),
                ]);

                $asset->update([
                    'lifecycle_status'    => $dest['lifecycle_status'],
                    'current_project_id'  => $dest['project_id'],
                    'current_division_id' => $dest['division_id'],
                    'current_location'    => $dest['location'],
                ]);

                FixedAssetMovement::create([
                    'fixed_asset_id'          => $asset->id,
                    'movement_type'           => $dest['movement_type'],
                    'from_location'           => $fromLocation,
                    'to_location'             => $dest['location'],
                    'from_project_id'         => $fromProjectId,
                    'to_project_id'           => $dest['project_id'],
                    'from_division_id'        => $fromDivisionId,
                    'to_division_id'          => $dest['division_id'],
                    'condition_before'        => $conditionBefore,
                    'condition_after'         => $asset->condition,
                    'lifecycle_status_before' => $statusBefore,
                    'lifecycle_status_after'  => $dest['lifecycle_status'],
                    'movement_date'           => now(),
                    'reference_type'          => 'allocation_item',
                    'reference_id'            => $item->id,
                    'performed_by'            => Auth::id(),
                    'notes'                   => 'Asset allocated via ' . $allocation->allocation_number,
                ]);

                $totalItems = $allocation->items()->count();
                $allocatedItems = $allocation->items()->where('is_allocated', true)->count();

                $allocation->update([
                    'status' => $allocatedItems >= $totalItems ? 'allocated' : 'partially_allocated',
                    'allocated_by' => Auth::id(),
                    'allocated_at' => now(),
                ]);
            });

            return redirect()->route('warehouse.fixed-assets.allocations.show', $allocation->id)
                ->with('success', 'Asset allocated successfully.');
        } catch (\Exception $e) {
            \Log::error('FixedAssetAllocationController@allocateItem error: ' . $e->getMessage());
            return back()->with('error', $e->getMessage());
        }
    }

    public function print($id)
    {
        $allocation = FixedAssetAllocation::with([
            'project', 'division', 'requester', 'approvedBy', 'allocatedBy',
            'items.fixedAsset.category'
        ])->findOrFail($id);

        return view('warehouse.fixed-assets.allocations.print', compact('allocation'));
    }

    private function assetLocationPayload(FixedAsset $asset): array
    {
        return [
            'id' => $asset->id,
            'lifecycle_status' => $asset->lifecycle_status,
            'current_project_id' => $asset->current_project_id,
            'current_division_id' => $asset->current_division_id,
            'current_location' => $asset->current_location,
            'location_label' => $this->assetLocationLabel($asset),
        ];
    }

    private function assetLocationLabel(FixedAsset $asset): string
    {
        if ($asset->currentProject) {
            $location = $asset->currentProject->project_name;

            if ($asset->currentDivision) {
                $location .= ' / ' . ($asset->currentDivision->name ?? $asset->currentDivision->division_name ?? 'Division');
            }

            return $location;
        }

        return $asset->current_location ?: 'Warehouse';
    }
}
