<?php

namespace App\Cancellation\Controller;

use App\Appointment\Entity\Appointment;
use App\Appointment\Repository\AppointmentRepository;
use App\Auth\Entity\User;
use App\Branch\Service\BranchResolver;
use App\Cancellation\Entity\CancellationPolicy;
use App\Cancellation\Repository\CancellationPolicyRepository;
use App\Cancellation\Service\CancellationService;
use App\Cancellation\Repository\NoShowRecordRepository;
use App\Cancellation\Service\NoShowService;
use App\Cancellation\Service\PenaltyCalculator;
use App\ClinicService\Entity\ServiceItem;
use App\ClinicService\Repository\ServiceItemRepository;
use App\Patient\Repository\PatientRecordRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use App\Shared\Exception\AppException;
use App\Shared\Tenant\TenantOwnershipChecker;
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: 'Cancellation')]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class CancellationController extends BaseController
{
    public function __construct(
        private readonly CancellationPolicyRepository $policies,
        private readonly AppointmentRepository        $appointments,
        private readonly ServiceItemRepository        $items,
        private readonly PatientRecordRepository      $patients,
        private readonly PenaltyCalculator            $calculator,
        private readonly CancellationService          $cancellation,
        private readonly NoShowService                $noShow,
        private readonly NoShowRecordRepository       $noShowRecords,
        private readonly BranchResolver               $branches,
        private readonly TenantOwnershipChecker       $ownership,
    ) {}

    #[Route('/api/v1/cancellation-policy', name: 'cancellation_policy_show', methods: ['GET'])]
    public function show(#[CurrentUser] User $user): JsonResponse
    {
        [$entityType, $entityId] = $this->branches->pair($user);

        return $this->success([
            'default'   => $this->policies->findDefault($entityType, $entityId)?->toArray(),
            'overrides' => array_values(array_map(
                static fn (CancellationPolicy $p): array => $p->toArray(),
                array_filter(
                    $this->policies->findForPair($entityType, $entityId),
                    static fn (CancellationPolicy $p): bool => $p->getServiceItem() !== null,
                ),
            )),
        ]);
    }

    /** سیاست پیش‌فرض محیط — ساخته می‌شود اگر نبود. */
    #[Route('/api/v1/cancellation-policy', name: 'cancellation_policy_save', methods: ['PUT'])]
    public function save(#[CurrentUser] User $user, Request $request): JsonResponse
    {
        [$entityType, $entityId] = $this->branches->pair($user);

        $policy = $this->policies->findDefault($entityType, $entityId)
            ?? new CancellationPolicy($entityType, $entityId);

        return $this->applyAndSave($policy, $request);
    }

    #[Route('/api/v1/service-item/{uuid}/cancellation-policy', name: 'cancellation_policy_service', methods: ['PUT'])]
    public function saveForService(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
    {
        [$entityType, $entityId] = $this->branches->pair($user);
        $service = $this->requireItem($user, $uuid);

        $policy = $this->policies->findForService($entityType, $entityId, $service)
            ?? new CancellationPolicy($entityType, $entityId, $service);

        return $this->applyAndSave($policy, $request);
    }

    /**
     * جریمه و بازگشت **پیش از** لغو.
     *
     * همان محاسبه‌ای که خودِ لغو انجام می‌دهد؛ بیمار نباید عددی ببیند که با آنچه کسر
     * می‌شود فرق دارد.
     */
    #[Route('/api/v1/appointment/{uuid}/cancellation-preview', name: 'cancellation_preview', methods: ['GET'])]
    public function preview(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
    {
        $appointment = $this->requireAppointment($user, $uuid);

        $by = $request->query->get('by') === 'doctor'
            ? Appointment::STATUS_CANCELLED_BY_DOCTOR
            : Appointment::STATUS_CANCELLED_BY_USER;

        return $this->success(
            $this->calculator->calculate($appointment, $by)->toArray()
            + ['paid_rials' => $this->calculator->paidRials($appointment)],
        );
    }

    #[Route('/api/v1/appointment/{uuid}/cancel', name: 'cancellation_cancel', methods: ['POST'])]
    public function cancel(#[CurrentUser] User $user, string $uuid, Request $request): JsonResponse
    {
        $appointment = $this->requireAppointment($user, $uuid);
        $data        = json_decode($request->getContent(), true);

        $by = is_array($data) && ($data['by'] ?? null) === 'doctor'
            ? Appointment::STATUS_CANCELLED_BY_DOCTOR
            : Appointment::STATUS_CANCELLED_BY_USER;

        $reason = is_array($data) && is_string($data['reason'] ?? null) && trim($data['reason']) !== ''
            ? trim($data['reason'])
            : null;

        return $this->success($this->cancellation->cancel($appointment, $by, $user, null, $reason));
    }

    /** ثبت عدم حضور — برچسب پرریسک اگر آستانه رد شود. */
    #[Route('/api/v1/appointment/{uuid}/no-show', name: 'cancellation_no_show', methods: ['POST'])]
    public function markNoShow(#[CurrentUser] User $user, string $uuid): JsonResponse
    {
        $appointment = $this->requireAppointment($user, $uuid);
        [$entityType, $entityId] = $this->branches->pair($user);

        $patient = $this->patients->findOneBy([
            'user'       => $appointment->getUser(),
            'entityType' => $entityType,
            'entityId'   => $entityId,
        ]);

        if ($patient === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'پروندهٔ بیمار در این محیط یافت نشد', 404);
        }

        if ($appointment->getStatus() !== Appointment::STATUS_NO_SHOW) {
            $appointment->transitionTo(Appointment::STATUS_NO_SHOW);
            $this->appointments->save($appointment);
        }

        return $this->success($this->noShow->record($appointment, $patient, $user));
    }

    /**
     * خلاصهٔ عدم حضور یک بیمار — برای نشان دادن در پروندهٔ او.
     *
     * `at_risk` فقط یک **نشانه** است. مسدودسازی کارِ قانون `eligibility` تسک ۰۹ است؛
     * کلینیکی که می‌خواهد بیمار پرریسک را ببیند ولی بیعانه بگیرد، نباید مجبور شود این
     * شمارش را خاموش کند.
     */
    #[Route('/api/v1/patient/{uuid}/no-shows', name: 'patient_no_show_summary', methods: ['GET'])]
    public function noShowSummary(#[CurrentUser] User $user, string $uuid): JsonResponse
    {
        [$entityType, $entityId] = $this->branches->pair($user);

        $patient = $this->patients->findOneBy([
            'uuid'       => $uuid,
            'entityType' => $entityType,
            'entityId'   => $entityId,
        ]);

        if ($patient === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'پروندهٔ بیمار یافت نشد', 404);
        }

        $count     = $this->noShowRecords->countRecent($patient);
        $threshold = $this->policies->resolve($entityType, $entityId, null)?->getNoShowThreshold() ?? 3;

        return $this->success([
            'count'        => $count,
            'threshold'    => $threshold,
            'window_days'  => NoShowRecordRepository::WINDOW_DAYS,
            'at_risk'      => $count >= $threshold,
        ]);
    }

    private function applyAndSave(CancellationPolicy $policy, Request $request): JsonResponse
    {
        $data = json_decode($request->getContent(), true);

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

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

        if (isset($data['penalty_mode'])) {
            try {
                $policy->setPenalty(
                    (string) $data['penalty_mode'],
                    is_numeric($data['penalty_value'] ?? null) ? (int) $data['penalty_value'] : 0,
                );
            } catch (\InvalidArgumentException $e) {
                return $this->error(
                    ErrorCodes::ERR_VALIDATION_001,
                    str_contains($e->getMessage(), 'percentage')
                        ? 'درصد جریمه باید بین ۰ تا ۱۰۰ باشد'
                        : 'حالت جریمه نامعتبر است',
                    422,
                    'penalty_mode',
                );
            }
        }

        foreach (['deposit_refundable' => 'setDepositRefundable', 'credit_refundable' => 'setCreditRefundable', 'active' => 'setActive'] as $field => $setter) {
            if (isset($data[$field])) {
                $policy->{$setter}((bool) $data[$field]);
            }
        }

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

        if (array_key_exists('risk_tag_uuid', $data)) {
            $policy->setRiskTagUuid(is_string($data['risk_tag_uuid']) ? $data['risk_tag_uuid'] : null);
        }

        $this->policies->save($policy);

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

    private function requireItem(User $user, string $uuid): 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 requireAppointment(User $user, string $uuid): Appointment
    {
        $appointment = $this->appointments->findByUuid($uuid);
        [$entityType, $entityId] = $this->branches->pair($user);

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

        return $appointment;
    }
}
