<?php

namespace App\Insurance\Command;

use App\Auth\Entity\User;
use App\Billing\Entity\Claim;
use App\Billing\Service\ClaimService;
use App\Billing\Service\InvoiceService;
use App\Doctor\Repository\DoctorRepository;
use App\Insurance\Entity\TenantInsurance;
use App\Insurance\Enum\ServiceCategory;
use App\Insurance\Repository\InsuranceRepository;
use App\Insurance\Service\TenantInsuranceService;
use App\Insurance\Service\TenantServiceCategoryService;
use App\Patient\Entity\PatientRecord;
use App\Patient\Entity\PatientSession;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
 * سناریوی قابل‌اجرای بیمهٔ تکمیلی برای یک پزشک: چند قرارداد با درصد و فرانشیز
 * متفاوت، چند بیمار که از آن‌ها استفاده می‌کنند، و مطالبات در وضعیت‌های مختلف.
 *
 * صورتحساب‌ها از مسیر واقعی InvoiceService/ClaimService ساخته می‌شوند تا اعداد را
 * همان BillingCalculator تولید کند — نه INSERT دستی که با فرمول واگرا می‌شود.
 *
 * Marker پاک‌سازی: موبایل بیمارها با پیشوند 09129900 ساخته می‌شود.
 *
 *   ddev exec php bin/console app:seed-insurance-scenario --doctor-mobile=09389388131 --purge
 */
#[AsCommand(
    name: 'app:seed-insurance-scenario',
    description: 'Seed supplementary-insurance contracts, patients and claims for one doctor',
)]
class SeedInsuranceScenarioCommand extends Command
{
    private const PATIENT_MOBILE_PREFIX = '09129900';

    /** نام بیمه‌های کاتالوگ + درصدها؛ نام‌ها با seed کاتالوگ هم‌خوان‌اند. */
    private const CONTRACTS = [
        ['name' => 'تامین اجتماعی', 'kind' => 'basic',         'outpatient' => 30.0, 'inpatient' => 40.0, 'franchise' => 0.0,  'ceiling' => null],
        ['name' => 'بیمه ایران',    'kind' => 'supplementary', 'outpatient' => 90.0, 'inpatient' => 80.0, 'franchise' => 10.0, 'ceiling' => 50_000_000],
        ['name' => 'بیمه آسیا',     'kind' => 'supplementary', 'outpatient' => 70.0, 'inpatient' => 60.0, 'franchise' => 20.0, 'ceiling' => null],
        ['name' => 'بیمه دی',       'kind' => 'supplementary', 'outpatient' => 100.0, 'inpatient' => 50.0, 'franchise' => 0.0, 'ceiling' => 10_000_000],
    ];

    /**
     * بیمارهای سناریو. `base`/`supp` نام بیمه‌اند، `visit` قیمت ویزیت به ریال و
     * `claim` وضعیت نهاییِ مطالبه‌ها.
     */
    private const PATIENTS = [
        ['name' => 'زهرا رضایی',   'base' => 'تامین اجتماعی', 'supp' => null,          'visit' => 5_000_000,  'category' => 'outpatient', 'claim' => Claim::STATUS_PENDING],
        ['name' => 'علی محمدی',    'base' => 'تامین اجتماعی', 'supp' => 'بیمه ایران',  'visit' => 8_000_000,  'category' => 'outpatient', 'claim' => Claim::STATUS_SUBMITTED],
        ['name' => 'مریم کاظمی',   'base' => null,            'supp' => 'بیمه آسیا',   'visit' => 6_000_000,  'category' => 'inpatient',  'claim' => Claim::STATUS_APPROVED],
        ['name' => 'حسین نوروزی',  'base' => 'تامین اجتماعی', 'supp' => 'بیمه دی',     'visit' => 40_000_000, 'category' => 'inpatient',  'claim' => Claim::STATUS_PAID],
    ];

    public function __construct(
        private readonly EntityManagerInterface       $em,
        private readonly DoctorRepository             $doctorRepo,
        private readonly InsuranceRepository          $insuranceRepo,
        private readonly TenantInsuranceService       $tenantInsuranceService,
        private readonly TenantServiceCategoryService $serviceCategories,
        private readonly InvoiceService               $invoiceService,
        private readonly ClaimService                 $claimService,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addOption('doctor-mobile', null, InputOption::VALUE_REQUIRED, 'موبایل کاربرِ پزشک', '09389388131')
            ->addOption('purge', null, InputOption::VALUE_NONE, 'فقط پاک‌سازی داده‌ی سناریو، بدون ساخت دوباره');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io     = new SymfonyStyle($input, $output);
        $mobile = (string) $input->getOption('doctor-mobile');

        $doctor = $this->doctorRepo->createQueryBuilder('d')
            ->join('d.user', 'u')
            ->andWhere('u.mobileNumber = :mobile')
            ->setParameter('mobile', $mobile)
            ->getQuery()
            ->getOneOrNullResult();

        if ($doctor === null) {
            $io->error(sprintf('پزشکی با موبایل %s یافت نشد', $mobile));
            return Command::FAILURE;
        }

        $entityType = TenantInsurance::TYPE_DOCTOR;
        $entityId   = (int) $doctor->getId();
        $io->title(sprintf('سناریوی بیمه — %s (doctor #%d)', $doctor->getName(), $entityId));

        // پاک‌سازی همیشه انجام می‌شود تا اجرای دوباره داده‌ی تکراری نسازد؛ marker موبایل
        // مخصوص همین سناریوست و به داده‌ی واقعی نمی‌رسد.
        $purged = $this->purge($entityType, $entityId);
        $io->text(sprintf('پاک‌سازی: %d بیمار سناریو حذف شد', $purged));

        if ($input->getOption('purge')) {
            $io->success('فقط پاک‌سازی انجام شد.');
            return Command::SUCCESS;
        }

        $this->serviceCategories->save($entityType, $entityId, [
            ['key' => 'outpatient', 'enabled' => true],
            ['key' => 'inpatient',  'enabled' => true],
        ]);

        $contracts = $this->seedContracts($entityType, $entityId, $io);
        if ($contracts === []) {
            return Command::FAILURE;
        }

        $rows = [];
        foreach (self::PATIENTS as $i => $spec) {
            $rows[] = $this->seedPatient($entityType, $entityId, $i, $spec);
        }

        $io->section('قراردادهای بیمه');
        $io->table(
            ['بیمه', 'نوع', 'سرپایی', 'بستری', 'فرانشیز', 'سقف سالانه'],
            array_map(static fn(array $c) => [
                $c['name'],
                $c['kind'] === 'basic' ? 'پایه' : 'تکمیلی',
                $c['outpatient'] . '٪',
                $c['inpatient'] . '٪',
                $c['franchise'] . '٪',
                $c['ceiling'] === null ? 'نامحدود' : number_format($c['ceiling']),
            ], self::CONTRACTS),
        );

        $io->section('بیماران و مطالبات');
        $io->table(['بیمار', 'پایه', 'تکمیلی', 'کل', 'سهم بیمه', 'سهم بیمار', 'وضعیت'], $rows);
        $io->success('سناریو ساخته شد. ورود به پنل با ' . $mobile);

        return Command::SUCCESS;
    }

    /**
     * @return array<string, TenantInsurance> کلید = نام بیمه
     */
    private function seedContracts(string $entityType, int $entityId, SymfonyStyle $io): array
    {
        $contracts = [];
        foreach (self::CONTRACTS as $spec) {
            $insurance = $this->insuranceRepo->findOneBy(['name' => $spec['name']]);
            if ($insurance === null) {
                $io->error(sprintf('بیمهٔ «%s» در کاتالوگ نیست؛ اول کاتالوگ بیمه را seed کنید', $spec['name']));
                return [];
            }

            $contract = $this->tenantInsuranceService->activate(
                $entityType,
                $entityId,
                (int) $insurance->getId(),
                $spec['outpatient'],
                $spec['franchise'],
                $spec['ceiling'],
                null,
                null,
                $spec['kind'],
            );

            $this->tenantInsuranceService->setCategoryCoverages($contract, [
                ['key' => 'outpatient', 'coverage_percent' => $spec['outpatient']],
                ['key' => 'inpatient',  'coverage_percent' => $spec['inpatient']],
            ]);

            $contracts[$spec['name']] = $contract;
        }

        return $contracts;
    }

    /**
     * @param array{name: string, base: ?string, supp: ?string, visit: int, category: string, claim: string} $spec
     * @return list<string> ردیف جدول خلاصه
     */
    private function seedPatient(string $entityType, int $entityId, int $index, array $spec): array
    {
        $mobile = self::PATIENT_MOBILE_PREFIX . str_pad((string) ($index + 1), 3, '0', STR_PAD_LEFT);

        $user = $this->em->getRepository(User::class)->findOneBy(['mobileNumber' => $mobile]) ?? new User($mobile);
        $user->setRealName($spec['name'])->setRoles(['ROLE_USER'])->setStatus(1);
        $this->em->persist($user);
        $this->em->flush();

        $record = new PatientRecord($entityType, $entityId, $user, $entityType, $entityId);
        $this->em->persist($record);
        $this->em->flush();

        $session = new PatientSession($record);
        $session->setVisitPriceRials($spec['visit'])
            ->setInsuranceBaseId($this->insuranceIdOf($spec['base']))
            ->setInsuranceSupplementaryId($this->insuranceIdOf($spec['supp']))
            ->setInsuranceServiceCategory(ServiceCategory::from($spec['category']))
            ->setSessionAt(time());
        $this->em->persist($session);
        $this->em->flush();

        $invoice = $this->invoiceService->createFromSession($session, $entityType, $entityId);
        $this->invoiceService->finalize($invoice);

        $claims = $this->claimService->createFromInvoice($invoice);
        foreach ($claims as $claim) {
            $this->advanceClaim($claim, $spec['claim']);
        }

        $insuranceShare = $invoice->getTotalRials() - $invoice->getPatientRials();

        return [
            $spec['name'],
            $spec['base'] ?? '—',
            $spec['supp'] ?? '—',
            number_format($invoice->getTotalRials()),
            number_format($insuranceShare),
            number_format($invoice->getPatientRials()),
            $spec['claim'],
        ];
    }

    /** مطالبه را تا وضعیت هدف جلو می‌برد؛ هر گام از همان transition واقعی رد می‌شود. */
    private function advanceClaim(Claim $claim, string $target): void
    {
        $path = match ($target) {
            Claim::STATUS_SUBMITTED => [Claim::STATUS_SUBMITTED],
            Claim::STATUS_APPROVED  => [Claim::STATUS_SUBMITTED, Claim::STATUS_APPROVED],
            Claim::STATUS_PAID      => [Claim::STATUS_SUBMITTED, Claim::STATUS_APPROVED, Claim::STATUS_PAID],
            Claim::STATUS_REJECTED  => [Claim::STATUS_SUBMITTED, Claim::STATUS_REJECTED],
            default                 => [],
        };

        foreach ($path as $step) {
            $this->claimService->transition($claim, $step, [
                'approved_rials'  => $step === Claim::STATUS_APPROVED ? $claim->getTotalClaimedRials() : null,
                'paid_rials'      => $step === Claim::STATUS_PAID ? $claim->getTotalApprovedRials() : null,
                'reason'          => $step === Claim::STATUS_REJECTED ? 'مدارک ناقص است' : '',
                'tracking_number' => $step === Claim::STATUS_SUBMITTED ? 'SC-' . $claim->getId() : null,
            ]);
        }
    }

    private function insuranceIdOf(?string $name): ?int
    {
        if ($name === null) {
            return null;
        }

        return $this->insuranceRepo->findOneBy(['name' => $name])?->getId();
    }

    /** حذف بیماران سناریو و هرچه از آن‌ها آویزان است. قراردادها با activate بازنویسی می‌شوند. */
    private function purge(string $entityType, int $entityId): int
    {
        $conn = $this->em->getConnection();
        $ids  = $conn->executeQuery(
            'SELECT id FROM users WHERE mobile_number LIKE :prefix',
            ['prefix' => self::PATIENT_MOBILE_PREFIX . '%'],
        )->fetchFirstColumn();

        if ($ids === []) {
            return 0;
        }

        $params = ['ids' => $ids, 'type' => $entityType, 'entity' => $entityId];
        $types  = ['ids' => \Doctrine\DBAL\ArrayParameterType::INTEGER];

        $conn->executeStatement(
            'DELETE ci FROM claim_items ci
             JOIN invoice_items ii ON ii.id = ci.invoice_item_id
             JOIN invoices inv ON inv.id = ii.invoice_id
             JOIN patient_records pr ON pr.id = inv.patient_record_id
             WHERE pr.user_id IN (:ids) AND pr.entity_type = :type AND pr.entity_id = :entity',
            $params,
            $types,
        );
        $conn->executeStatement(
            'DELETE csl FROM claim_status_logs csl
             JOIN claims c ON c.id = csl.claim_id
             LEFT JOIN claim_items ci ON ci.claim_id = c.id
             WHERE ci.id IS NULL AND c.entity_type = :type AND c.entity_id = :entity',
            ['type' => $entityType, 'entity' => $entityId],
        );
        $conn->executeStatement(
            'DELETE c FROM claims c
             LEFT JOIN claim_items ci ON ci.claim_id = c.id
             WHERE ci.id IS NULL AND c.entity_type = :type AND c.entity_id = :entity',
            ['type' => $entityType, 'entity' => $entityId],
        );
        $conn->executeStatement(
            'DELETE ii FROM invoice_items ii
             JOIN invoices inv ON inv.id = ii.invoice_id
             JOIN patient_records pr ON pr.id = inv.patient_record_id
             WHERE pr.user_id IN (:ids)',
            $params,
            $types,
        );
        $conn->executeStatement(
            'DELETE inv FROM invoices inv
             JOIN patient_records pr ON pr.id = inv.patient_record_id
             WHERE pr.user_id IN (:ids)',
            $params,
            $types,
        );
        $conn->executeStatement(
            'DELETE ps FROM patient_sessions ps
             JOIN patient_records pr ON pr.id = ps.record_id
             WHERE pr.user_id IN (:ids)',
            $params,
            $types,
        );
        $conn->executeStatement('DELETE FROM patient_records WHERE user_id IN (:ids)', $params, $types);
        $conn->executeStatement('DELETE FROM users WHERE id IN (:ids)', $params, $types);

        return count($ids);
    }
}
