<?php

namespace App\Services\Inventory;

use App\Models\InventoryItem;
use App\Models\InventoryMovement;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class InventoryMovementService
{
    public function __construct(private InventoryContextService $context)
    {
    }

    public function list(array $filters = []): array
    {
        $selectedYear = trim((string) ($filters['school_year'] ?? $this->context->schoolYear()));
        $selectedSem = trim((string) ($filters['semester'] ?? $this->context->semester()));

        $builder = DB::table('inventory_movements as m')
            ->select([
                'm.*',
                'ii.name as item_name',
                DB::raw("CONCAT(pb.firstname, ' ', pb.lastname) AS performed_by_name"),
                DB::raw("CONCAT(t.firstname, ' ', t.lastname) AS teacher_name"),
                DB::raw("CONCAT(s.firstname, ' ', s.lastname) AS student_name"),
                'cs.class_section_name',
            ])
            ->leftJoin('inventory_items as ii', 'ii.id', '=', 'm.item_id')
            ->leftJoin('users as pb', 'pb.id', '=', 'm.performed_by')
            ->leftJoin('users as t', 't.id', '=', 'm.teacher_id')
            ->leftJoin('students as s', 's.id', '=', 'm.student_id')
            ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'm.class_section_id')
            ->orderBy('m.id', 'DESC');

        if ($selectedYear !== '') {
            $builder->where('m.school_year', $selectedYear);
        }
        if ($selectedSem !== '') {
            $builder->where('m.semester', $selectedSem);
        }

        return $builder->get()->map(fn ($r) => (array) $r)->all();
    }

    public function create(array $data, int $performedBy = 0): array
    {
        $itemId = (int) ($data['item_id'] ?? 0);
        $movementType = (string) ($data['movement_type'] ?? '');
        $qtyChange = (int) ($data['qty_change'] ?? 0);

        $item = InventoryItem::query()->find($itemId);
        if (!$item) {
            return ['ok' => false, 'message' => 'Selected item not found.'];
        }

        $qtyChange = $this->normalizeMovementQty($movementType, $qtyChange);
        if ($this->wouldGoNegative($itemId, $qtyChange)) {
            return ['ok' => false, 'message' => 'Not enough stock to apply this movement.'];
        }

        $payload = [
            'item_id' => $itemId,
            'qty_change' => $qtyChange,
            'movement_type' => $movementType,
            'reason' => trim((string) ($data['reason'] ?? '')),
            'note' => trim((string) ($data['note'] ?? '')),
            'semester' => $data['semester'] ?? $this->context->semester(),
            'school_year' => $data['school_year'] ?? $this->context->schoolYear(),
            'performed_by' => $performedBy ?: null,
            'teacher_id' => $this->nullableInt($data['teacher_id'] ?? null),
            'student_id' => $this->nullableInt($data['student_id'] ?? null),
            'class_section_id' => $this->nullableInt($data['class_section_id'] ?? null),
        ];

        try {
            InventoryMovement::query()->create($payload);
            $this->recalcQuantity($itemId);
        } catch (\Throwable $e) {
            Log::error('Inventory movement create failed: ' . $e->getMessage());
            return ['ok' => false, 'message' => 'Could not save movement.'];
        }

        return ['ok' => true];
    }

    public function update(int $id, array $data): array
    {
        $movement = InventoryMovement::query()->find($id);
        if (!$movement) {
            return ['ok' => false, 'message' => 'Movement not found.'];
        }

        $movementType = (string) ($data['movement_type'] ?? $movement->movement_type);
        $qtyChange = (int) ($data['qty_change'] ?? $movement->qty_change);
        $qtyChange = $this->normalizeMovementQty($movementType, $qtyChange);

        $itemId = (int) $movement->item_id;
        $delta = $qtyChange - (int) $movement->qty_change;
        if ($delta !== 0 && $this->wouldGoNegative($itemId, $delta)) {
            return ['ok' => false, 'message' => 'Not enough stock to apply this movement.'];
        }

        $payload = [
            'qty_change' => $qtyChange,
            'movement_type' => $movementType,
            'reason' => trim((string) ($data['reason'] ?? '')),
            'note' => trim((string) ($data['note'] ?? '')),
            'semester' => $data['semester'] ?? $this->context->semester(),
            'school_year' => $data['school_year'] ?? $this->context->schoolYear(),
            'teacher_id' => $this->nullableInt($data['teacher_id'] ?? null),
            'student_id' => $this->nullableInt($data['student_id'] ?? null),
            'class_section_id' => $this->nullableInt($data['class_section_id'] ?? null),
        ];

        try {
            $movement->update($payload);
            $this->recalcQuantity($itemId);
        } catch (\Throwable $e) {
            Log::error('Inventory movement update failed: ' . $e->getMessage());
            return ['ok' => false, 'message' => 'Could not update movement.'];
        }

        return ['ok' => true];
    }

    public function delete(int $id): array
    {
        $movement = InventoryMovement::query()->find($id);
        if (!$movement) {
            return ['ok' => false, 'message' => 'Movement not found.'];
        }

        $itemId = (int) $movement->item_id;
        try {
            $movement->delete();
            if ($itemId > 0) {
                $this->recalcQuantity($itemId);
            }
        } catch (\Throwable $e) {
            Log::error('Inventory movement delete failed: ' . $e->getMessage());
            return ['ok' => false, 'message' => 'Could not delete movement.'];
        }

        return ['ok' => true];
    }

    public function bulkDelete(array $ids): array
    {
        $ids = array_values(array_filter(array_map('intval', $ids), static fn ($v) => $v > 0));
        if (empty($ids)) {
            return ['ok' => false, 'message' => 'No movements selected.'];
        }

        $itemIds = DB::table('inventory_movements')
            ->select('item_id')
            ->whereIn('id', $ids)
            ->get()
            ->map(fn ($r) => (int) ($r->item_id ?? 0))
            ->filter(fn ($v) => $v > 0)
            ->unique()
            ->values()
            ->all();

        try {
            InventoryMovement::query()->whereIn('id', $ids)->delete();
            foreach ($itemIds as $itemId) {
                $this->recalcQuantity($itemId);
            }
        } catch (\Throwable $e) {
            Log::error('Inventory movement bulk delete failed: ' . $e->getMessage());
            return ['ok' => false, 'message' => 'Bulk delete failed.'];
        }

        return ['ok' => true, 'deleted' => count($ids)];
    }

    public function recordMovement(
        int $itemId,
        int $qtyChange,
        string $type,
        ?string $reason = null,
        ?string $note = null,
        ?int $classSectionId = null,
        ?int $studentId = null,
        ?int $performedBy = null
    ): bool {
        $isInitial = ($type === 'initial');

        if (!$isInitial) {
            $this->ensureInitialMovement($itemId);
            $this->ensureInitialMovementForYear($itemId, $this->context->schoolYear());
            $this->recalcQuantity($itemId);
        }

        if (in_array($type, ['out', 'distribution'], true) && $qtyChange > 0) {
            $qtyChange = -abs($qtyChange);
        }

        if (in_array($type, ['out', 'distribution'], true) && $this->wouldGoNegative($itemId, $qtyChange)) {
            return false;
        }

        InventoryMovement::query()->create([
            'item_id' => $itemId,
            'qty_change' => $qtyChange,
            'movement_type' => $type,
            'reason' => $reason,
            'note' => $note,
            'semester' => $this->context->semester(),
            'school_year' => $this->context->schoolYear(),
            'performed_by' => $performedBy,
            'teacher_id' => $performedBy,
            'student_id' => $studentId,
            'class_section_id' => $classSectionId,
        ]);

        $this->recalcQuantity($itemId);
        return true;
    }

    public function recalcQuantity(int $itemId): void
    {
        $sumRow = InventoryMovement::query()
            ->selectRaw('SUM(qty_change) as qty_change')
            ->where('item_id', $itemId)
            ->first();

        $sum = (int) ($sumRow?->qty_change ?? 0);
        if ($sum < 0) {
            $sum = 0;
        }

        $item = InventoryItem::query()->select('id', 'type', 'quantity', 'needs_repair_qty', 'good_qty')->find($itemId);
        if (!$item) {
            return;
        }

        $updates = [];
        if ((int) $item->quantity !== $sum) {
            $updates['quantity'] = $sum;
        }

        if ($item->type === 'classroom') {
            $needsRepair = (int) ($item->needs_repair_qty ?? 0);
            $goodCalc = max(0, $sum - $needsRepair);
            if ((int) ($item->good_qty ?? 0) !== $goodCalc) {
                $updates['good_qty'] = $goodCalc;
            }
        }

        if (!empty($updates)) {
            $item->update($updates);
        }
    }

    private function ensureInitialMovement(int $itemId): void
    {
        $hasMov = InventoryMovement::query()->where('item_id', $itemId)->exists();
        if ($hasMov) {
            return;
        }

        $item = InventoryItem::query()->select('quantity')->find($itemId);
        $initialQty = (int) ($item?->quantity ?? 0);

        InventoryMovement::query()->create([
            'item_id' => $itemId,
            'qty_change' => $initialQty,
            'movement_type' => 'initial',
            'semester' => $this->context->semester(),
            'school_year' => $this->context->schoolYear(),
        ]);
    }

    private function ensureInitialMovementForYear(int $itemId, string $schoolYear): void
    {
        $hasAnyThisYear = InventoryMovement::query()
            ->where('item_id', $itemId)
            ->where('school_year', $schoolYear)
            ->exists();

        if (!$hasAnyThisYear) {
            $onHand = (int) (InventoryItem::query()->where('id', $itemId)->value('quantity') ?? 0);
            InventoryMovement::query()->create([
                'item_id' => $itemId,
                'qty_change' => $onHand,
                'movement_type' => 'initial',
                'school_year' => $schoolYear,
                'semester' => $this->context->semester() ?: null,
            ]);
            return;
        }

        $hasInitial = InventoryMovement::query()
            ->where('item_id', $itemId)
            ->where('school_year', $schoolYear)
            ->where('movement_type', 'initial')
            ->exists();

        if (!$hasInitial) {
            $onHand = (int) (InventoryItem::query()->where('id', $itemId)->value('quantity') ?? 0);
            $netYear = (int) (InventoryMovement::query()
                ->where('item_id', $itemId)
                ->where('school_year', $schoolYear)
                ->sum('qty_change') ?? 0);
            $opening = $onHand - $netYear;

            InventoryMovement::query()->create([
                'item_id' => $itemId,
                'qty_change' => $opening,
                'movement_type' => 'initial',
                'school_year' => $schoolYear,
                'semester' => $this->context->semester() ?: null,
            ]);
        }
    }

    private function normalizeMovementQty(string $movementType, int $qtyChange): int
    {
        if (in_array($movementType, ['out', 'distribution'], true) && $qtyChange > 0) {
            return -abs($qtyChange);
        }
        return $qtyChange;
    }

    private function wouldGoNegative(int $itemId, int $delta): bool
    {
        $onHand = (int) (InventoryItem::query()->where('id', $itemId)->value('quantity') ?? 0);
        return $onHand + $delta < 0;
    }

    private function nullableInt($val): ?int
    {
        if ($val === null || $val === '' || $val === '0') {
            return null;
        }
        return (int) $val;
    }
}
