<?php

namespace App\Policy\Controller;

use App\Auth\Entity\User;
use App\Branch\Service\BranchResolver;
use App\ClinicService\Repository\CatalogCategoryRepository;
use App\ClinicService\Repository\ServiceItemRepository;
use App\Policy\Entity\Policy;
use App\Policy\Entity\PolicyVersionLog;
use App\Policy\Repository\PolicyRepository;
use App\Policy\Repository\PolicySimulationRunRepository;
use App\Policy\Repository\PolicyVersionLogRepository;
use App\Policy\Template\PolicyTemplateRegistry;
use App\Policy\Service\ConditionEvaluator;
use App\Policy\Service\PolicySchema;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use App\Shared\Exception\AppException;
use App\Shared\Tenant\TenantOwnershipChecker;
use Doctrine\ORM\EntityManagerInterface;
use OpenApi\Attributes as OA;
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;

#[OA\Tag(name: 'Policy')]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class PolicyController extends BaseController
{
    public function __construct(
        private readonly PolicyRepository           $policies,
        private readonly PolicyVersionLogRepository $versions,
        private readonly PolicySimulationRunRepository $simulations,
        private readonly PolicyTemplateRegistry     $templates,
        private readonly ConditionEvaluator         $evaluator,
        private readonly PolicySchema               $schema,
        private readonly ServiceItemRepository      $items,
        private readonly CatalogCategoryRepository  $categories,
        private readonly BranchResolver             $branches,
        private readonly TenantOwnershipChecker     $ownership,
        private readonly EntityManagerInterface     $em,
    ) {}

    /**
     * فهرست بستهٔ فیلدها، عملگرها و اثرها.
     *
     * فرم ساخت قانون در پنل از **همین** ساخته می‌شود، نه از فهرستی که در فرانت دوباره
     * نوشته شده باشد — دو فهرست یعنی دو حقیقت و یکی از آن‌ها همیشه قدیمی است.
     */
    #[Route('/api/v1/policy-schema', name: 'policy_schema', methods: ['GET'])]
    public function schema(): JsonResponse
    {
        return $this->success($this->schema->describe());
    }

    #[Route('/api/v1/policies', name: 'policy_index', methods: ['GET'])]
    public function index(#[CurrentUser] User $user, Request $request): JsonResponse
    {
        [$entityType, $entityId] = $this->branches->pair($user);

        $category = $request->query->get('category');

        if (is_string($category) && $category !== '' && !in_array($category, Policy::CATEGORIES, true)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'دستهٔ قانون نامعتبر است', 422, 'category');
        }

        return $this->success(array_map(
            static fn (Policy $p): array => $p->toArray(),
            $this->policies->findForPair($entityType, $entityId, is_string($category) && $category !== '' ? $category : null),
        ));
    }

    #[Route('/api/v1/policy', name: 'policy_create', methods: ['POST'])]
    public function create(#[CurrentUser] User $user, Request $request): JsonResponse
    {
        $data = json_decode($request->getContent(), true);

        if (!is_array($data)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'بدنهٔ درخواست نامعتبر است', 422);
        }

        // الگو فقط `category`/`condition`/`effects` را از پیش پر می‌کند؛ اعتبارسنجی
        // بعد از آن همان مسیر عادی است، پس الگو نمی‌تواند قانونِ نامعتبر بسازد.
        if (is_string($data['template'] ?? null)) {
            $data = array_merge($data, $this->templates->build($data['template'], $data['values'] ?? []));
        }

        if (!is_string($data['category'] ?? null) || !in_array($data['category'], Policy::CATEGORIES, true)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'دستهٔ قانون نامعتبر است', 422, 'category');
        }

        if (!is_string($data['name'] ?? null) || trim($data['name']) === '') {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'نام قانون الزامی است', 422, 'name');
        }

        [$entityType, $entityId] = $this->branches->pair($user);

        $policy = new Policy($entityType, $entityId, $data['category'], trim($data['name']));
        $this->apply($user, $policy, $data);

        $this->em->persist($policy);
        $this->em->flush();

        $this->em->persist(new PolicyVersionLog($policy, $policy->getVersion(), $policy->toArray()));
        $this->em->flush();

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

    #[Route('/api/v1/policy/{uuid}', name: 'policy_show', methods: ['GET'])]
    public function show(#[CurrentUser] User $user, string $uuid): JsonResponse
    {
        $policy = $this->requirePolicy($user, $uuid);

        return $this->success($policy->toArray() + [
            'versions' => array_map(
                static fn (PolicyVersionLog $l): array => $l->toArray(),
                $this->versions->findForPolicy($policy),
            ),
        ]);
    }

    /**
     * نسخهٔ جدید — قانون **ویرایش نمی‌شود**.
     *
     * نوبتی که دیروز ثبت شده نسخهٔ قبلی را در فاکتورش نگه داشته؛ بازنویسی درجا یعنی
     * آن ارجاع به متنی اشاره کند که هرگز روی آن نوبت اعمال نشده بود.
     */
    #[Route('/api/v1/policy/{uuid}/version', name: 'policy_version', methods: ['POST'])]
    public function version(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
    {
        $data   = json_decode($request->getContent(), true);
        $policy = $this->requirePolicy($user, $uuid);

        if (!is_array($data)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'بدنهٔ درخواست نامعتبر است', 422);
        }

        // شروعِ اعتبارِ عقب‌رونده روی نسخهٔ تازه یعنی قانونی که ادعا می‌کند از دیروز برقرار
        // بوده، در حالی که نوبت‌های دیروز با متن قبلی حساب شده‌اند و ردپای قیمتشان به این
        // نسخه اشاره می‌کند. روی نسخهٔ نخست آزاد است — هنوز چیزی بر اساسش تصمیم نگرفته‌ایم.
        if (is_numeric($data['valid_from'] ?? null) && (int) $data['valid_from'] < time()) {
            return $this->error(
                ErrorCodes::ERR_VALIDATION_001,
                'شروع اعتبار نسخهٔ تازه نمی‌تواند در گذشته باشد؛ نوبت‌های گذشته با متن قبلی حساب شده‌اند',
                422,
                'valid_from',
            );
        }

        $this->apply($user, $policy, $data);
        $policy->bumpVersion();

        $this->em->persist(new PolicyVersionLog($policy, $policy->getVersion(), $policy->toArray()));
        $this->em->flush();

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

    /**
     * فعال‌سازی — فقط بعد از یک اجرای آزمایشیِ **همین نسخه**.
     *
     * آزمایش نسخهٔ ۱ اجازهٔ فعال‌سازی نسخهٔ ۲ را نمی‌دهد: کاربر متن قانون را عوض کرده و
     * گزارشی که دیده دیگر توصیف این قانون نیست.
     */
    #[Route('/api/v1/policy/{uuid}/activate', name: 'policy_activate', methods: ['POST'])]
    public function activate(#[CurrentUser] User $user, string $uuid): JsonResponse
    {
        $policy = $this->requirePolicy($user, $uuid);
        $run    = $this->simulations->latestFor($policy);

        if ($run === null || $run->getPolicyVersion() !== $policy->getVersion()) {
            return $this->error(
                ErrorCodes::ERR_VALIDATION_001,
                'ابتدا قانون را آزمایش کنید و نتیجه را ببینید',
                422,
                'simulation',
            );
        }

        $policy->setActive(true);
        $this->em->flush();

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

    #[Route('/api/v1/policy/{uuid}/deactivate', name: 'policy_deactivate', methods: ['POST'])]
    public function deactivate(#[CurrentUser] User $user, string $uuid): JsonResponse
    {
        $policy = $this->requirePolicy($user, $uuid)->setActive(false);
        $this->em->flush();

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

    /** @param array<string, mixed> $data */
    private function apply(User $user, Policy $policy, array $data): void
    {
        if (is_string($data['name'] ?? null) && trim($data['name']) !== '') {
            $policy->setName(trim($data['name']));
        }

        if (is_array($data['condition'] ?? null)) {
            // اعتبارسنجی در **زمان ساخت**: قانونی که موقع رزرو بیمار بترکد، بدترین
            // جای ممکن برای شکستن است.
            $this->evaluator->assertValid($policy->getCategory(), $data['condition']);
            $policy->setCondition($data['condition']);
        }

        if (is_array($data['effects'] ?? null)) {
            $this->evaluator->assertEffectsValid($policy->getCategory(), $data['effects']);
            $policy->setEffects(array_values($data['effects']));
        }

        if (is_numeric($data['priority'] ?? null)) {
            $policy->setPriority((int) $data['priority']);
        }

        if (array_key_exists('valid_from', $data) || array_key_exists('valid_to', $data)) {
            $validFrom = is_numeric($data['valid_from'] ?? null) ? (int) $data['valid_from'] : null;

            try {
                $policy->setValidity(
                    $validFrom,
                    is_numeric($data['valid_to'] ?? null) ? (int) $data['valid_to'] : null,
                );
            } catch (\InvalidArgumentException) {
                throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'پایان اعتبار باید بعد از شروع آن باشد', 422, 'valid_to');
            }
        }

        if (array_key_exists('address_uuid', $data)) {
            $policy->setAddress(is_string($data['address_uuid']) ? $this->branches->resolve($user, $data['address_uuid']) : null);
        }

        if (array_key_exists('service_uuid', $data)) {
            $policy->setServiceItem(is_string($data['service_uuid']) ? $this->requireItem($user, $data['service_uuid']) : null);
        }

        if (array_key_exists('catalog_category_uuid', $data)) {
            $policy->setCatalogCategory(
                is_string($data['catalog_category_uuid']) ? $this->requireCategory($user, $data['catalog_category_uuid']) : null,
            );
        }
    }

    private function requirePolicy(User $user, string $uuid): Policy
    {
        $policy = $this->policies->findByUuid($uuid);
        [$entityType, $entityId] = $this->branches->pair($user);

        if ($policy === null || !$this->ownership->belongsToPair($entityType, $entityId, $policy)) {
            throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'قانون یافت نشد', 404);
        }

        return $policy;
    }

    private function requireItem(User $user, string $uuid): \App\ClinicService\Entity\ServiceItem
    {
        $item = $this->items->findByUuid($uuid);
        [$entityType, $entityId] = $this->branches->pair($user);

        if ($item === null
            || $item->getSection()->getEntityType() !== $entityType
            || $item->getSection()->getEntityId() !== $entityId
        ) {
            throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'سرویس یافت نشد', 404);
        }

        return $item;
    }

    private function requireCategory(User $user, string $uuid): \App\ClinicService\Entity\CatalogCategory
    {
        $category = $this->categories->findByUuid($uuid);
        [$entityType, $entityId] = $this->branches->pair($user);

        if ($category === null || !$this->ownership->belongsToPair($entityType, $entityId, $category)) {
            throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, 'دسته یافت نشد', 404);
        }

        return $category;
    }
}
