<?php

namespace App\Inventory\Controller;

use App\Auth\Entity\User;
use App\Auth\Repository\UserActiveContextRepository;
use App\Clinic\Repository\ClinicRepository;
use App\Doctor\Repository\DoctorRepository;
use App\Inventory\Entity\InventoryItem;
use App\Inventory\Entity\InventoryPackage;
use App\Inventory\Repository\InventoryItemRepository;
use App\Inventory\Repository\InventoryPackageRepository;
use App\Inventory\Service\InventoryService;
use App\Secretary\Security\SecretaryAccessChecker;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use App\Shared\Exception\AppException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use OpenApi\Attributes as OA;

/**
 * Per-tenant (doctor/clinic) inventory: consumable items and their packages.
 * Every row is scoped to the caller's resolved entity; a tenant can only see and
 * mutate its own inventory. Scoping mirrors {@see \App\Tag\Controller\TenantTagController}.
 */
#[OA\Tag(name: 'Inventory')]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class InventoryController extends BaseController
{
    public function __construct(
        private readonly InventoryItemRepository     $itemRepo,
        private readonly InventoryPackageRepository  $packageRepo,
        private readonly InventoryService            $service,
        private readonly DoctorRepository            $doctorRepo,
        private readonly ClinicRepository            $clinicRepo,
        private readonly UserActiveContextRepository $contextRepo,
        private readonly SecretaryAccessChecker      $secretaryAccess,
        private readonly \App\Clinic\Security\ClinicDoctorAccessChecker $clinicDoctorAccess,
    ) {}

    // ── Items ────────────────────────────────────────────────────────────────

    #[Route('/api/v1/inventory-items', methods: ['GET'])]
    public function listItems(#[CurrentUser] User $user): JsonResponse
    {
        $this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'view');
        $this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'view');
        [$type, $id] = $this->resolveEntity($user);
        if ($id === null) {
            return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
        }

        $items = $this->itemRepo->findByEntity($type, $id);

        return $this->success([
            'items' => array_map(fn(InventoryItem $i) => $i->toArray(), $items),
            'stats' => $this->service->stats($items),
        ]);
    }

    #[Route('/api/v1/inventory-categories', methods: ['GET'])]
    public function listCategories(#[CurrentUser] User $user): JsonResponse
    {
        $this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'view');
        $this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'view');
        [$type, $id] = $this->resolveEntity($user);
        if ($id === null) {
            return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
        }

        return $this->success($this->itemRepo->findCategories($type, $id));
    }

    #[Route('/api/v1/inventory-meta', methods: ['GET'])]
    public function meta(): JsonResponse
    {
        return $this->success([
            'units'      => InventoryItem::UNITS,
            'categories' => InventoryItem::CATEGORIES,
        ]);
    }

    #[Route('/api/v1/inventory-item', methods: ['POST'])]
    public function createItem(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'create');
        $this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'create');
        [$type, $id] = $this->resolveEntity($user);
        if ($id === null) {
            return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
        }

        $data = json_decode($request->getContent(), true) ?? [];
        $name = trim($data['name'] ?? '');
        if ($name === '') {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'نام کالا الزامی است', 422, 'name');
        }

        $item = new InventoryItem($type, $id, $name);
        $this->applyItemFields($item, $data);
        $this->itemRepo->save($item);

        return $this->success($item->toArray(), 201);
    }

    #[Route('/api/v1/inventory-item/{uuid}', methods: ['PATCH'])]
    public function updateItem(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'update');
        $this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'update');
        $item = $this->ownedItem($uuid, $user);
        if ($item === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'کالا یافت نشد', 404);
        }

        $data = json_decode($request->getContent(), true) ?? [];
        if (array_key_exists('name', $data)) {
            $name = trim($data['name']);
            if ($name === '') {
                return $this->error(ErrorCodes::ERR_VALIDATION_001, 'نام کالا الزامی است', 422, 'name');
            }
            $item->setName($name);
        }
        $this->applyItemFields($item, $data);
        $this->itemRepo->save($item);

        return $this->success($item->toArray());
    }

    #[Route('/api/v1/inventory-item/{uuid}', methods: ['DELETE'])]
    public function deleteItem(string $uuid, #[CurrentUser] User $user): JsonResponse
    {
        $this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'delete');
        $this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'delete');
        $item = $this->ownedItem($uuid, $user);
        if ($item === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'کالا یافت نشد', 404);
        }

        $this->itemRepo->remove($item);

        return $this->success(['message' => 'کالا حذف شد']);
    }

    // ── Packages ─────────────────────────────────────────────────────────────

    #[Route('/api/v1/inventory-packages', methods: ['GET'])]
    public function listPackages(#[CurrentUser] User $user): JsonResponse
    {
        $this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'view');
        $this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'view');
        [$type, $id] = $this->resolveEntity($user);
        if ($id === null) {
            return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
        }

        return $this->success(array_map(
            fn(InventoryPackage $p) => $this->service->packageToArray($p),
            $this->packageRepo->findByEntity($type, $id)
        ));
    }

    #[Route('/api/v1/inventory-package', methods: ['POST'])]
    public function createPackage(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'create');
        $this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'create');
        [$type, $id] = $this->resolveEntity($user);
        if ($id === null) {
            return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403);
        }

        $data  = json_decode($request->getContent(), true) ?? [];
        $title = trim($data['title'] ?? '');
        if ($title === '') {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'نام پکیج الزامی است', 422, 'title');
        }

        $package = new InventoryPackage($type, $id, $title);
        $this->service->syncPackageItems($package, $data['items'] ?? [], $type, $id);
        $this->packageRepo->save($package);

        return $this->success($this->service->packageToArray($package), 201);
    }

    #[Route('/api/v1/inventory-package/{uuid}', methods: ['PATCH'])]
    public function updatePackage(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'update');
        $this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'update');
        $package = $this->ownedPackage($uuid, $user);
        if ($package === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'پکیج یافت نشد', 404);
        }

        [$type, $id] = $this->resolveEntity($user);
        $data = json_decode($request->getContent(), true) ?? [];

        if (array_key_exists('title', $data)) {
            $title = trim($data['title']);
            if ($title === '') {
                return $this->error(ErrorCodes::ERR_VALIDATION_001, 'نام پکیج الزامی است', 422, 'title');
            }
            $package->setTitle($title);
        }
        if (array_key_exists('items', $data)) {
            $this->service->syncPackageItems($package, $data['items'], $type, (int) $id);
        }
        $this->packageRepo->save($package);

        return $this->success($this->service->packageToArray($package));
    }

    #[Route('/api/v1/inventory-package/{uuid}', methods: ['DELETE'])]
    public function deletePackage(string $uuid, #[CurrentUser] User $user): JsonResponse
    {
        $this->secretaryAccess->denyUnlessGranted($user, 'inventory', 'delete');
        $this->clinicDoctorAccess->denyUnlessGranted($user, 'inventory', 'delete');
        $package = $this->ownedPackage($uuid, $user);
        if ($package === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'پکیج یافت نشد', 404);
        }

        $this->packageRepo->remove($package);

        return $this->success(['message' => 'پکیج حذف شد']);
    }

    // ── Helpers ──────────────────────────────────────────────────────────────

    /** Apply optional mutable item fields present in the payload. */
    private function applyItemFields(InventoryItem $item, array $data): void
    {
        if (array_key_exists('consumable', $data)) {
            $c = trim((string) $data['consumable']);
            $item->setConsumable($c === '' ? null : $c);
        }
        if (array_key_exists('unit', $data)) {
            $unit = trim((string) $data['unit']);
            if ($unit !== '' && !in_array($unit, InventoryItem::UNITS, true)) {
                throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'واحد نامعتبر است', 422, 'unit');
            }
            $item->setUnit($unit === '' ? InventoryItem::DEFAULT_UNIT : $unit);
        }
        if (array_key_exists('category', $data)) {
            $category = trim((string) $data['category']);
            if ($category !== '' && !in_array($category, InventoryItem::CATEGORIES, true)) {
                throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'دسته‌بندی نامعتبر است', 422, 'category');
            }
            $item->setCategory($category === '' ? null : $category);
        }
        if (array_key_exists('price', $data)) {
            $item->setPrice((int) $data['price']);
        }
        if (array_key_exists('stock', $data)) {
            $item->setStock((int) $data['stock']);
        }
        if (array_key_exists('alertThreshold', $data)) {
            $item->setAlertThreshold((int) $data['alertThreshold']);
        }
    }

    /** The item only if it belongs to the caller's entity, else null. */
    private function ownedItem(string $uuid, User $user): ?InventoryItem
    {
        [$type, $id] = $this->resolveEntity($user);
        $item = $this->itemRepo->findByUuid($uuid);
        if ($item === null || $id === null || $item->getEntityType() !== $type || $item->getEntityId() !== $id) {
            return null;
        }
        return $item;
    }

    /** The package only if it belongs to the caller's entity, else null. */
    private function ownedPackage(string $uuid, User $user): ?InventoryPackage
    {
        [$type, $id] = $this->resolveEntity($user);
        $package = $this->packageRepo->findByUuid($uuid);
        if ($package === null || $id === null || $package->getEntityType() !== $type || $package->getEntityId() !== $id) {
            return null;
        }
        return $package;
    }

    /**
     * عمداً از EntityContextResolver استفاده نمی‌کند: اینجا نقشِ پزشک بی‌قید اول
     * بررسی می‌شود، پس پزشکِ عضو حتی در محیط کلینیک هم انبارِ شخصی خودش را می‌بیند.
     * رزولور مشترک محیط فعال را مقدم می‌داند و انتقال به آن، دادهٔ نمایش‌داده‌شده به
     * پزشکِ عضو را عوض می‌کند — تصمیمی محصولی که به فاز نشانه‌گذاری tenant موکول شد.
     *
     * @return array{0: string, 1: int|null} [entityType, entityId]
     */
    private function resolveEntity(User $user): array
    {
        if ($user->hasRole('ROLE_DOCTOR')) {
            $memberClinicId = $this->clinicDoctorAccess->memberClinicId($user);
            if ($memberClinicId !== null) {
                return ['clinic', $memberClinicId];
            }
            $doctor = $this->doctorRepo->findByUser($user);
            return ['doctor', $doctor?->getId()];
        }
        if ($user->hasRole('ROLE_CLINIC')) {
            $clinic = $this->clinicRepo->findByUser($user);
            return ['clinic', $clinic?->getId()];
        }
        if ($user->hasRole('ROLE_SECRETARY')) {
            $dbUuid = $this->contextRepo->findByUser($user)?->getDbUuid();
            if ($dbUuid !== null) {
                $clinic = $this->clinicRepo->findByUuid($dbUuid);
                if ($clinic !== null) {
                    return ['clinic', $clinic->getId()];
                }
                $doctor = $this->doctorRepo->findByUuid($dbUuid);
                if ($doctor !== null) {
                    return ['doctor', $doctor->getId()];
                }
            }
        }
        return ['unknown', null];
    }
}
