<?php

namespace App\Services\Inventory;

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

class InventoryItemService
{
    private array $types = ['classroom', 'book', 'office', 'kitchen'];

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

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

        $builder = InventoryItem::query()->where('type', $type);
        if ($schoolYear !== '' && strtolower($schoolYear) !== 'all') {
            $builder->where('school_year', $schoolYear);
        }
        if ($semester !== '') {
            $builder->where('semester', $semester);
        }
        if ($q !== '') {
            $builder->where('name', 'like', '%' . $q . '%');
        }

        $items = $builder->orderBy('name')->get()->toArray();
        $categories = InventoryCategory::optionsForType($type);

        $userIds = array_values(array_unique(array_map(static fn ($i) => (int) ($i['updated_by'] ?? 0), $items)));
        $userNames = $this->userNamesByIds(array_values(array_filter($userIds)));

        return [
            'type' => $type,
            'items' => $items,
            'categories' => $categories,
            'conditionOptions' => [
                'good' => 'Good condition',
                'needs_repair' => 'Needs repair',
                'need_replace' => 'Need replace',
                'cannot_find' => 'Cannot find',
            ],
            'schoolYears' => $this->getSchoolYearsForType($type),
            'selectedYear' => $schoolYear,
            'currentYear' => $this->context->schoolYear(),
            'semester' => $semester,
            'userNames' => $userNames,
        ];
    }

    public function find(int $id): ?array
    {
        $item = InventoryItem::query()->find($id);
        return $item ? $item->toArray() : null;
    }

    public function create(array $data, ?int $userId = null): array
    {
        $payload = $this->filterItemData($data, null, $userId);

        try {
            return DB::transaction(function () use ($payload) {
                $item = InventoryItem::query()->create($payload);
                $initialQty = (int) ($payload['quantity'] ?? 0);
                if ($initialQty !== 0) {
                    $this->movementService->recordMovement((int) $item->id, $initialQty, 'initial', 'Initial stock');
                } else {
                    $this->movementService->recalcQuantity((int) $item->id);
                }
                return ['ok' => true, 'item' => $item->toArray()];
            });
        } catch (\Throwable $e) {
            Log::error('Inventory item create failed: ' . $e->getMessage());
            return ['ok' => false, 'message' => 'Failed to create item.'];
        }
    }

    public function update(int $id, array $data, ?int $userId = null): array
    {
        $item = InventoryItem::query()->find($id);
        if (!$item) {
            return ['ok' => false, 'message' => 'Item not found.'];
        }

        $payload = $this->filterItemData($data, (string) $item->type, $userId);
        unset($payload['id']);

        try {
            $item->update($payload);
        } catch (\Throwable $e) {
            Log::error('Inventory item update failed: ' . $e->getMessage());
            return ['ok' => false, 'message' => 'Failed to update item.'];
        }

        return ['ok' => true, 'item' => $item->toArray()];
    }

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

        try {
            $item->delete();
        } catch (\Throwable $e) {
            Log::error('Inventory item delete failed: ' . $e->getMessage());
            return ['ok' => false, 'message' => 'Failed to delete item.'];
        }

        return ['ok' => true];
    }

    public function auditClassroom(int $itemId, array $data, ?int $userId = null): array
    {
        $item = InventoryItem::query()->find($itemId);
        if (!$item || $item->type !== 'classroom') {
            return ['ok' => false, 'message' => 'Invalid classroom item.'];
        }

        $prevLoss = (int) ($item->need_replace_qty ?? 0);
        $prevMiss = (int) ($item->cannot_find_qty ?? 0);

        $newRepair = max(0, (int) ($data['needs_repair_qty'] ?? 0));
        $newLoss = max(0, (int) ($data['need_replace_qty'] ?? 0));
        $newMiss = max(0, (int) ($data['cannot_find_qty'] ?? 0));

        $onHandNow = (int) ($item->quantity ?? 0);
        $newGood = max(0, $onHandNow - $newRepair);

        $deltaLoss = $newLoss - $prevLoss;
        $deltaMiss = $newMiss - $prevMiss;

        $totalOut = 0;
        if ($deltaLoss > 0) {
            $totalOut += $deltaLoss;
        }
        if ($deltaMiss > 0) {
            $totalOut += $deltaMiss;
        }

        $totalIn = 0;
        if ($deltaLoss < 0) {
            $totalIn += -$deltaLoss;
        }
        if ($deltaMiss < 0) {
            $totalIn += -$deltaMiss;
        }

        if ($totalOut > 0 && !$this->movementService->recordMovement($itemId, -$totalOut, 'out', 'Audit: loss/missing increased')) {
            return ['ok' => false, 'message' => 'Not enough stock to apply audit.'];
        }
        if ($totalIn > 0) {
            $this->movementService->recordMovement($itemId, $totalIn, 'in', 'Audit: loss/missing decreased');
        }

        $this->movementService->recalcQuantity($itemId);

        $item->update([
            'good_qty' => $newGood,
            'needs_repair_qty' => $newRepair,
            'need_replace_qty' => $newLoss,
            'cannot_find_qty' => $newMiss,
            'updated_by' => $userId,
        ]);

        return ['ok' => true, 'item' => $item->toArray()];
    }

    public function adjustStock(int $itemId, array $data, ?int $userId = null): array
    {
        $item = InventoryItem::query()->find($itemId);
        if (!$item) {
            return ['ok' => false, 'message' => 'Item not found.'];
        }

        $mode = (string) ($data['mode'] ?? '');
        $qty = (int) ($data['quantity'] ?? 0);
        $reason = (string) ($data['reason'] ?? '');
        $note = (string) ($data['note'] ?? '');

        if ($qty <= 0) {
            return ['ok' => false, 'message' => 'Quantity must be > 0.'];
        }

        $delta = ($mode === 'out') ? -abs($qty) : (($mode === 'in') ? abs($qty) : $qty);
        $type = ($mode === 'out') ? 'out' : (($mode === 'in') ? 'in' : 'adjust');

        $ok = $this->movementService->recordMovement($itemId, $delta, $type, $reason, $note, null, null, $userId);
        if (!$ok) {
            return ['ok' => false, 'message' => 'Not enough stock to apply adjustment.'];
        }

        return ['ok' => true];
    }

    private function filterItemData(array $data, ?string $lockedType = null, ?int $userId = null): array
    {
        $type = $lockedType ?? $this->normalizeType($data['type'] ?? 'classroom');
        $qtyRaw = $data['quantity'] ?? 0;
        $quantity = is_numeric($qtyRaw) ? (int) $qtyRaw : 0;

        $base = [
            'type' => $type,
            'category_id' => isset($data['category_id']) && $data['category_id'] !== '' ? (int) $data['category_id'] : null,
            'name' => (string) ($data['name'] ?? ''),
            'description' => $data['description'] ?? null,
            'quantity' => $quantity,
            'unit' => $data['unit'] ?? null,
            'sku' => $data['sku'] ?? null,
            'notes' => $data['notes'] ?? null,
            'semester' => $this->context->semester(),
            'school_year' => $this->context->schoolYear(),
            'updated_by' => $userId,
        ];

        $base['condition'] = ($type === 'classroom') ? ($data['condition'] ?? null) : null;

        if ($type === 'book') {
            $base['isbn'] = $data['isbn'] ?? null;
            $base['edition'] = $data['edition'] ?? null;
        } else {
            $base['isbn'] = null;
            $base['edition'] = null;
        }

        return $base;
    }

    private function normalizeType(?string $type): string
    {
        $type = strtolower((string) $type);
        return in_array($type, $this->types, true) ? $type : 'classroom';
    }

    private function userNamesByIds(array $ids): array
    {
        $ids = array_values(array_unique(array_filter($ids)));
        if (empty($ids)) {
            return [];
        }

        $cols = DB::getSchemaBuilder()->getColumnListing('users');
        $hasFirst = in_array('firstname', $cols, true);
        $hasLast = in_array('lastname', $cols, true);
        $nameCol = in_array('name', $cols, true) ? 'name' : null;
        $emailCol = in_array('email', $cols, true) ? 'email' : null;

        if ($hasFirst || $hasLast) {
            $select = DB::raw("id, TRIM(CONCAT(IFNULL(firstname,''),' ',IFNULL(lastname,''))) AS display_name");
        } elseif ($nameCol) {
            $select = DB::raw("id, {$nameCol} AS display_name");
        } elseif ($emailCol) {
            $select = DB::raw("id, {$emailCol} AS display_name");
        } else {
            $out = [];
            foreach ($ids as $id) {
                $out[$id] = 'User #' . $id;
            }
            return $out;
        }

        $rows = DB::table('users')->select($select)->whereIn('id', $ids)->get();
        $map = [];
        foreach ($rows as $r) {
            $disp = trim((string) ($r->display_name ?? ''));
            $map[(int) $r->id] = $disp !== '' ? $disp : ('User #' . $r->id);
        }
        return $map;
    }

    private function getSchoolYearsForType(string $type): array
    {
        $years = InventoryItem::query()
            ->select('school_year')
            ->where('type', $type)
            ->whereNotNull('school_year')
            ->groupBy('school_year')
            ->orderByDesc('school_year')
            ->pluck('school_year')
            ->map(fn ($v) => trim((string) $v))
            ->filter()
            ->unique()
            ->values()
            ->all();

        $currentYear = $this->context->schoolYear();
        if ($currentYear && !in_array($currentYear, $years, true)) {
            array_unshift($years, $currentYear);
        }

        return $years;
    }
}
