<?php

namespace App\Http\Controllers;

use App\Enums\RecordStatusEnum;
use App\Models\AliqoutMovement;
use App\Models\Aliquot;
use App\Models\AliquotTransaction;
use App\Models\BaseModel;
use App\Models\Box;
use App\Models\ContainerType;
use App\Models\Pathogen;
use App\Models\PathogenInventorySample;
use App\Models\Position;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;

class StoragePositionController extends Controller
{
    protected $baseOrganizationId;
    protected $base;

    public function __construct()
    {
        $this->baseOrganizationId = Session::get('base_organization_id');
        $this->base = Session::get('base_organization');
    }

    public function index(Box $box)
    {
        $box->load(['rack.equipment.room.building']);

        $positions = Position::with(['aliquot.sample.pathogen', 'aliquot.containerType.containerName'])
            ->where('box_id', $box->id)
            ->where(BaseModel::RECORD_STATUS_FIELD, RecordStatusEnum::ACTIVE)
            ->orderBy('row_label')
            ->orderByRaw('CAST(column_label AS UNSIGNED)')
            ->get();

        $samples = PathogenInventorySample::with(['pathogen'])
            ->where(BaseModel::RECORD_STATUS_FIELD, RecordStatusEnum::ACTIVE)
            ->where('organization_id', $this->baseOrganizationId)
            ->orderBy(BaseModel::CREATED_AT_FIELD, 'desc')
            ->limit(200)
            ->get();

        $pathogens = Pathogen::with(['riskGroup', 'bsl'])
            ->where(BaseModel::RECORD_STATUS_FIELD, RecordStatusEnum::ACTIVE)
            ->orderBy('scientific_name')
            ->get();

        $containerTypes = ContainerType::with(['containerName'])
            ->where(BaseModel::RECORD_STATUS_FIELD, RecordStatusEnum::ACTIVE)
            ->get();

        $emptyPositions = Position::with(['box.rack.equipment.room.building'])
            ->where(BaseModel::RECORD_STATUS_FIELD, RecordStatusEnum::ACTIVE)
            ->where('occupied', 0)
            ->orderByRaw('box_id = ? DESC', [$box->id])
            ->orderBy('box_id')
            ->orderBy('row_label')
            ->orderByRaw('CAST(column_label AS UNSIGNED)')
            ->get();

        $recentMovements = AliqoutMovement::with([
                'aliquot.sample.pathogen',
                'fromPosition.box.rack.equipment.room.building',
                'toPosition.box.rack.equipment.room.building',
            ])
            ->where(BaseModel::RECORD_STATUS_FIELD, RecordStatusEnum::ACTIVE)
            ->where(function ($query) use ($box) {
                $query->whereHas('fromPosition', function ($query) use ($box) {
                    $query->where('box_id', $box->id);
                })->orWhereHas('toPosition', function ($query) use ($box) {
                    $query->where('box_id', $box->id);
                });
            })
            ->orderBy(BaseModel::CREATED_AT_FIELD, 'desc')
            ->limit(10)
            ->get();

        return view('box_positions', [
            'box' => $box,
            'positions' => $positions,
            'samples' => $samples,
            'pathogens' => $pathogens,
            'containerTypes' => $containerTypes,
            'emptyPositions' => $emptyPositions,
            'recentMovements' => $recentMovements,
        ]);
    }

    public function generate(Request $request)
    {
        $request->validate(['box_id' => 'required']);

        try {
            $box = Box::query()->findOrFail($request->box_id);

            if ($box->rows < 1 || $box->columns < 1) {
                return response()->json(['success' => false, 'message' => 'Box rows and columns must be configured before generating positions.']);
            }

            DB::beginTransaction();

            for ($row = 1; $row <= $box->rows; $row++) {
                $rowLabel = $this->rowLabel($row);

                for ($column = 1; $column <= $box->columns; $column++) {
                    Position::query()->firstOrCreate(
                        [
                            'box_id' => $box->id,
                            'row_label' => $rowLabel,
                            'column_label' => (string) $column,
                        ],
                        [
                            'code' => $rowLabel . $column,
                            'occupied' => 0,
                            BaseModel::CREATED_AT_FIELD => date('Y-m-d H:i:s'),
                            BaseModel::CREATED_BY_FIELD => Auth::id(),
                            BaseModel::RECORD_STATUS_FIELD => RecordStatusEnum::ACTIVE,
                        ]
                    );
                }
            }

            DB::commit();

            return response()->json(['success' => true, 'message' => 'Box positions generated successfully.']);
        } catch (\Exception $e) {
            DB::rollBack();
            return response()->json(['success' => false, 'message' => 'Unable to generate box positions.', 'errors' => $e->getMessage()]);
        }
    }

    public function storeAliquot(Request $request)
    {
        try {
            $validator = \Validator::make($request->all(), [
                'storage_position_id' => 'required',
                'aliquot_number' => 'required',
                'initial_volume' => 'required|numeric|min:0',
                'remaining_volume' => 'required|numeric|min:0',
                'volume_unit' => 'required',
                'status' => 'required',
            ]);

            if ($validator->fails()) {
                return response()->json(['success' => false, 'message' => 'Unable to save aliquot.', 'errors' => $validator->errors()->all()]);
            }

            $position = Position::with(['aliquot'])->findOrFail($request->storage_position_id);
            if (!empty($position->aliquot)) {
                return response()->json(['success' => false, 'message' => 'This position is already occupied. Remove or transfer the current aliquot first.']);
            }

            DB::beginTransaction();

            $sampleId = $request->sample_id;
            if (empty($sampleId) && !empty($request->pathogen_id)) {
                $sampleId = PathogenInventorySample::query()->insertGetId([
                    'organization_id' => $this->baseOrganizationId,
                    'pathogen_id' => $request->pathogen_id,
                    'sample_code' => $request->sample_code ?: $request->aliquot_number,
                    'initial_volume' => $request->initial_volume,
                    'remaining_volume' => $request->remaining_volume,
                    'volume_unit' => $request->volume_unit,
                    'status' => 'Stored',
                    BaseModel::CREATED_AT_FIELD => date('Y-m-d H:i:s'),
                    BaseModel::CREATED_BY_FIELD => Auth::id(),
                    BaseModel::RECORD_STATUS_FIELD => RecordStatusEnum::ACTIVE,
                ]);
            }

            $aliquotId = Aliquot::query()->insertGetId([
                'sample_id' => $sampleId,
                'container_type_id' => $request->container_type_id,
                'aliquot_number' => $request->aliquot_number,
                'initial_volume' => $request->initial_volume,
                'remaining_volume' => $request->remaining_volume,
                'volume_unit' => $request->volume_unit,
                'concentration' => $request->concentration,
                'storage_position_id' => $request->storage_position_id,
                'expiry_date' => !empty($request->expiry_date) ? date('Y-m-d H:i:s', strtotime($request->expiry_date)) : null,
                'status' => $request->status,
                'last_accessed_at' => date('Y-m-d H:i:s'),
                'description' => $request->description,
                BaseModel::CREATED_AT_FIELD => date('Y-m-d H:i:s'),
                BaseModel::CREATED_BY_FIELD => Auth::id(),
                BaseModel::RECORD_STATUS_FIELD => RecordStatusEnum::ACTIVE,
            ]);

            $position->update([
                'occupied' => 1,
                BaseModel::UPDATED_AT_FIELD => date('Y-m-d H:i:s'),
                BaseModel::UPDATED_BY_FIELD => Auth::id(),
            ]);

            AliqoutMovement::query()->insert([
                'aliquot_id' => $aliquotId,
                'from_position_id' => null,
                'to_position_id' => $request->storage_position_id,
                'movement_reason' => 'Initial storage placement',
                'moved_by' => optional(Auth::user())->name ?? optional(Auth::user())->email ?? (string) Auth::id(),
                BaseModel::CREATED_AT_FIELD => date('Y-m-d H:i:s'),
                BaseModel::CREATED_BY_FIELD => Auth::id(),
                BaseModel::RECORD_STATUS_FIELD => RecordStatusEnum::ACTIVE,
            ]);

            DB::commit();

            return response()->json(['success' => true, 'message' => 'Aliquot placed in storage position.']);
        } catch (\Exception $e) {
            DB::rollBack();
            return response()->json(['success' => false, 'message' => 'Unable to save aliquot.', 'errors' => $e->getMessage()]);
        }
    }

    public function removeAliquot(Request $request)
    {
        try {
            $validator = \Validator::make($request->all(), [
                'aliquot_id' => 'required',
            ]);

            if ($validator->fails()) {
                return response()->json(['success' => false, 'message' => 'Unable to remove aliquot.', 'errors' => $validator->errors()->all()]);
            }

            $aliquot = Aliquot::query()->findOrFail($request->aliquot_id);

            DB::beginTransaction();

            $positionId = $aliquot->storage_position_id;
            $aliquot->update([
                'status' => 'Removed',
                BaseModel::DELETED_AT_FIELD => date('Y-m-d H:i:s'),
                BaseModel::DELETED_BY_FIELD => Auth::id(),
                BaseModel::RECORD_STATUS_FIELD => RecordStatusEnum::DELETE,
            ]);

            Position::query()->where('id', $positionId)->update([
                'occupied' => 0,
                BaseModel::UPDATED_AT_FIELD => date('Y-m-d H:i:s'),
                BaseModel::UPDATED_BY_FIELD => Auth::id(),
            ]);

            AliqoutMovement::query()->insert([
                'aliquot_id' => $aliquot->id,
                'from_position_id' => $positionId,
                'to_position_id' => null,
                'movement_reason' => $request->movement_reason ?: 'Removed from storage',
                'moved_by' => optional(Auth::user())->name ?? optional(Auth::user())->email ?? (string) Auth::id(),
                BaseModel::CREATED_AT_FIELD => date('Y-m-d H:i:s'),
                BaseModel::CREATED_BY_FIELD => Auth::id(),
                BaseModel::RECORD_STATUS_FIELD => RecordStatusEnum::ACTIVE,
            ]);

            DB::commit();

            return response()->json(['success' => true, 'message' => 'Aliquot removed from this position.']);
        } catch (\Exception $e) {
            DB::rollBack();
            return response()->json(['success' => false, 'message' => 'Unable to remove aliquot.', 'errors' => $e->getMessage()]);
        }
    }

    public function moveAliquot(Request $request)
    {
        try {
            $validator = \Validator::make($request->all(), [
                'aliquot_id' => 'required',
                'to_position_id' => 'required',
                'movement_reason' => 'required',
            ]);

            if ($validator->fails()) {
                return response()->json(['success' => false, 'message' => 'Unable to move aliquot.', 'errors' => $validator->errors()->all()]);
            }

            $aliquot = Aliquot::query()
                ->where(BaseModel::RECORD_STATUS_FIELD, RecordStatusEnum::ACTIVE)
                ->findOrFail($request->aliquot_id);

            $fromPositionId = $aliquot->storage_position_id;
            $toPosition = Position::with(['aliquot'])->findOrFail($request->to_position_id);

            if ((int) $fromPositionId === (int) $toPosition->id) {
                return response()->json(['success' => false, 'message' => 'The aliquot is already in that position.']);
            }

            if (!empty($toPosition->aliquot) || (int) $toPosition->occupied === 1) {
                return response()->json(['success' => false, 'message' => 'Target position is occupied. Choose an empty position.']);
            }

            DB::beginTransaction();

            Position::query()->where('id', $fromPositionId)->update([
                'occupied' => 0,
                BaseModel::UPDATED_AT_FIELD => date('Y-m-d H:i:s'),
                BaseModel::UPDATED_BY_FIELD => Auth::id(),
            ]);

            Position::query()->where('id', $toPosition->id)->update([
                'occupied' => 1,
                BaseModel::UPDATED_AT_FIELD => date('Y-m-d H:i:s'),
                BaseModel::UPDATED_BY_FIELD => Auth::id(),
            ]);

            $aliquot->update([
                'storage_position_id' => $toPosition->id,
                'last_accessed_at' => date('Y-m-d H:i:s'),
                BaseModel::UPDATED_AT_FIELD => date('Y-m-d H:i:s'),
                BaseModel::UPDATED_BY_FIELD => Auth::id(),
            ]);

            AliqoutMovement::query()->insert([
                'aliquot_id' => $aliquot->id,
                'from_position_id' => $fromPositionId,
                'to_position_id' => $toPosition->id,
                'movement_reason' => $request->movement_reason,
                'moved_by' => optional(Auth::user())->name ?? optional(Auth::user())->email ?? (string) Auth::id(),
                BaseModel::CREATED_AT_FIELD => date('Y-m-d H:i:s'),
                BaseModel::CREATED_BY_FIELD => Auth::id(),
                BaseModel::RECORD_STATUS_FIELD => RecordStatusEnum::ACTIVE,
            ]);

            DB::commit();

            return response()->json(['success' => true, 'message' => 'Aliquot moved successfully.']);
        } catch (\Exception $e) {
            DB::rollBack();
            return response()->json(['success' => false, 'message' => 'Unable to move aliquot.', 'errors' => $e->getMessage()]);
        }
    }

    public function movementHistory(Request $request)
    {
        try {
            $validator = \Validator::make($request->all(), [
                'aliquot_id' => 'required',
            ]);

            if ($validator->fails()) {
                return response()->json(['success' => false, 'message' => 'Unable to retrieve movement history.', 'errors' => $validator->errors()->all()]);
            }

            $movements = AliqoutMovement::with([
                    'fromPosition.box.rack.equipment.room.building',
                    'toPosition.box.rack.equipment.room.building',
                ])
                ->where('aliquot_id', $request->aliquot_id)
                ->where(BaseModel::RECORD_STATUS_FIELD, RecordStatusEnum::ACTIVE)
                ->orderBy(BaseModel::CREATED_AT_FIELD, 'desc')
                ->get()
                ->map(function ($movement) {
                    return [
                        'from' => $this->positionLabel($movement->fromPosition),
                        'to' => $this->positionLabel($movement->toPosition),
                        'reason' => $movement->movement_reason,
                        'moved_by' => $movement->moved_by,
                        'moved_at' => $movement->created_at,
                    ];
                });

            return response()->json(['success' => true, 'message' => 'Movement history retrieved.', 'data' => $movements]);
        } catch (\Exception $e) {
            return response()->json(['success' => false, 'message' => 'Unable to retrieve movement history.', 'errors' => $e->getMessage()]);
        }
    }

    public function useVolume(Request $request)
    {
        try {
            $validator = \Validator::make($request->all(), [
                'aliquot_id' => 'required',
                'used_volume' => 'required|numeric|min:0.01',
                'use_reason' => 'required',
            ]);

            if ($validator->fails()) {
                return response()->json(['success' => false, 'message' => 'Unable to record aliquot use.', 'errors' => $validator->errors()->all()]);
            }

            $aliquot = Aliquot::query()
                ->where(BaseModel::RECORD_STATUS_FIELD, RecordStatusEnum::ACTIVE)
                ->findOrFail($request->aliquot_id);

            $sourceVolume = (float) $aliquot->remaining_volume;
            $usedVolume = (float) $request->used_volume;

            if ($usedVolume > $sourceVolume) {
                return response()->json(['success' => false, 'message' => 'Used volume cannot be greater than remaining volume.']);
            }

            $remainingVolume = $sourceVolume - $usedVolume;

            DB::beginTransaction();

            $aliquot->update([
                'remaining_volume' => $remainingVolume,
                'status' => $remainingVolume <= 0 ? 'Consumed' : $aliquot->status,
                'last_accessed_at' => date('Y-m-d H:i:s'),
                BaseModel::UPDATED_AT_FIELD => date('Y-m-d H:i:s'),
                BaseModel::UPDATED_BY_FIELD => Auth::id(),
            ]);

            AliquotTransaction::query()->insert([
                'aliquot_id' => $aliquot->id,
                'transaction_at' => date('Y-m-d H:i:s'),
                'performed_by' => optional(Auth::user())->name ?? optional(Auth::user())->email ?? (string) Auth::id(),
                'source_volumn' => $sourceVolume,
                'affected_volume' => $usedVolume,
                'remaining_volume' => $remainingVolume,
                'volume_unit' => $aliquot->volume_unit,
                'storage_position_id' => $aliquot->storage_position_id,
                'reference_number' => $request->reference_number,
                'description' => $request->use_reason,
                'state' => 'approved',
                BaseModel::CREATED_AT_FIELD => date('Y-m-d H:i:s'),
                BaseModel::CREATED_BY_FIELD => Auth::id(),
                BaseModel::RECORD_STATUS_FIELD => RecordStatusEnum::ACTIVE,
            ]);

            DB::commit();

            return response()->json(['success' => true, 'message' => 'Aliquot use recorded. Remaining volume: ' . $remainingVolume . ' ' . $aliquot->volume_unit]);
        } catch (\Exception $e) {
            DB::rollBack();
            return response()->json(['success' => false, 'message' => 'Unable to record aliquot use.', 'errors' => $e->getMessage()]);
        }
    }

    private function rowLabel(int $row): string
    {
        $label = '';

        while ($row > 0) {
            $row--;
            $label = chr(65 + ($row % 26)) . $label;
            $row = intdiv($row, 26);
        }

        return $label;
    }

    private function positionLabel($position): string
    {
        if (empty($position)) {
            return 'Outside storage';
        }

        $box = $position->box;
        $rack = optional($box)->rack;
        $equipment = optional($rack)->equipment;
        $room = optional($equipment)->room;
        $building = optional($room)->building;

        return trim(
            collect([
                optional($building)->name,
                optional($room)->name,
                optional($equipment)->name,
                optional($rack)->name,
                optional($box)->name,
                $position->code,
            ])->filter()->implode(' / ')
        );
    }
}
