<?php

namespace App\Resource\Command;

use App\Appointment\Entity\WeeklySchedule;
use App\Branch\Entity\Room;
use App\Doctor\Entity\DoctorAddress;
use App\Resource\Entity\ResourceType;
use App\Resource\Repository\ClinicResourceRepository;
use App\Resource\Service\ResourceLinker;
use App\Staff\Entity\ClinicStaff;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
 * هر موجودیت قابل‌اشغالِ موجود را به یک `ClinicResource` پل می‌زند تا تسک‌های ۰۳ به بعد
 * روی داده‌ای واقعی بنشینند، نه روی جدول خالی.
 *
 * dry-run پیش‌فرض است و `--force` می‌نویسد. idempotent: تکیه‌گاهش وجود یا نبودِ منبعِ
 * متناظر است، نه یک پرچم جداگانه — پس اجرای دوباره چیزی دوباره نمی‌سازد.
 */
#[AsCommand(
    name: 'app:resource:backfill',
    description: 'Bridge existing doctors, staff and rooms to clinic resources',
)]
class BackfillResourceCommand extends Command
{
    public function __construct(
        private readonly EntityManagerInterface   $em,
        private readonly ResourceLinker           $linker,
        private readonly ClinicResourceRepository $resources,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this->addOption('force', null, InputOption::VALUE_NONE, 'Actually write; without it the command only reports');
        $this->addOption('pair', null, InputOption::VALUE_REQUIRED, 'Limit to one environment, e.g. clinic:12 or doctor:7');
    }

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

        if (!$force) {
            $io->note('Dry run — nothing will be written. Re-run with --force to apply.');
        }

        $created = ['room' => 0, 'staff' => 0, 'doctor' => 0];
        $skipped = [];
        /** @var list<array{0: string, 1: string, 2: string}> $rows */
        $rows = [];

        $only            = $input->getOption('pair');
        $addressesByPair = $this->addressesByPair();

        if (is_string($only) && $only !== '') {
            $addressesByPair = array_intersect_key($addressesByPair, [$only => true]);

            if ($addressesByPair === []) {
                $io->warning(sprintf('محیط «%s» هیچ شعبه‌ای ندارد.', $only));

                return Command::SUCCESS;
            }
        }

        foreach ($addressesByPair as $pairKey => $addresses) {
            [$entityType, $entityId] = explode(':', $pairKey);

            foreach (ResourceType::SYSTEM_CODES as $code => $_) {
                $this->linker->systemType($entityType, (int) $entityId, $code);
            }

            // ── اتاق‌ها: آدرسشان را خودشان دارند، پس بی‌ابهام‌اند ──────────────────
            foreach ($addresses as $address) {
                foreach ($this->em->getRepository(Room::class)->findForAddress($address) as $room) {
                    if ($this->resources->findForSubject($room) !== null) {
                        continue;
                    }

                    $rows[] = ['room', (string) $room->getName(), (string) ($address->getName() ?? '—')];
                    $created['room']++;

                    if ($force) {
                        $this->linker->link($room, $address, $room->getName());
                    }
                }
            }

            // ── پرسنل: هیچ ستونی آدرسش را نمی‌گوید ────────────────────────────────
            $staffMembers = $this->em->getRepository(ClinicStaff::class)
                ->findBy(['entityType' => $entityType, 'entityId' => (int) $entityId, 'active' => true]);

            foreach ($staffMembers as $staff) {
                // با بیش از یک آدرس، انتخاب یکی حدس است و پرسنل را در ساختمان اشتباه
                // می‌نشاند. گزارش می‌شود تا کاربر خودش تعیین کند، نه حدس بی‌صدا.
                if (count($addresses) !== 1) {
                    $skipped[] = sprintf(
                        'پرسنل «%s» — محیط %s:%s شعبهٔ یکتا ندارد (%d شعبه)',
                        $staff->getFullName(),
                        $entityType,
                        $entityId,
                        count($addresses),
                    );
                    continue;
                }

                $address = $addresses[array_key_first($addresses)];

                if ($this->resources->findForSubject($staff, $address) !== null) {
                    continue;
                }

                $rows[] = ['staff', (string) $staff->getFullName(), (string) ($address->getName() ?? '—')];
                $created['staff']++;

                if ($force) {
                    $this->linker->link($staff, $address, $staff->getFullName());
                }
            }

            // flush per محیط: یک ردیفِ خرابِ یک کلینیک نباید کل اجرای چندهزارمحیطی را
            // با EntityManagerClosed از پا بیندازد.
            if ($force) {
                $this->em->flush();
                $this->linker->forgetPendingTypes();
            }
        }

        // پزشکان یک پاسِ جدا دارند: برنامهٔ هفتگی خودش محیط و آدرس‌هایش را می‌گوید،
        // پس یک بار روی همهٔ برنامه‌ها می‌رویم. حلقه‌زدن روی برنامه‌ها *به‌ازای هر محیط*
        // ضرب‌درِ تعداد محیط‌ها بود و روی دادهٔ واقعی هرگز تمام نمی‌شد.
        $created['doctor'] = $this->backfillDoctors($addressesByPair, $force, $rows);

        if ($force) {
            $this->em->flush();
            $this->linker->forgetPendingTypes();
        }

        if ($rows !== []) {
            $io->table(['نوع', 'نام', 'شعبه'], $rows);
        }

        foreach ($skipped as $reason) {
            $io->warning($reason);
        }

        $io->success(sprintf(
            '%s — اتاق: %d · پرسنل: %d · پزشک: %d',
            $force ? 'ساخته شد' : 'ساخته می‌شود',
            $created['room'],
            $created['staff'],
            $created['doctor'],
        ));

        return Command::SUCCESS;
    }

    /**
     * منبعِ پزشک از `location_id`های برنامهٔ هفتگی مشتق می‌شود: آنجا دقیقاً نوشته که
     * این پزشک در کدام آدرس‌ها شیفت دارد. «اولین شعبهٔ محیط» حدس می‌بود.
     *
     * یک پاس روی همهٔ برنامه‌ها، نه یک پاس به‌ازای هر محیط: محیطِ هر برنامه از خودش
     * خوانده می‌شود.
     *
     * @param array<string, array<int, DoctorAddress>> $addressesByPair
     * @param list<array{0: string, 1: string, 2: string}> $rows
     */
    private function backfillDoctors(array $addressesByPair, bool $force, array &$rows): int
    {
        $created = 0;

        foreach ($this->em->getRepository(WeeklySchedule::class)->findAll() as $schedule) {
            $clinic  = $schedule->getClinic();
            $doctor  = $schedule->getDoctor();
            $pairKey = $clinic !== null
                ? 'clinic:' . $clinic->getId()
                : 'doctor:' . $doctor->getId();

            $addresses = $addressesByPair[$pairKey] ?? null;

            if ($addresses === null) {
                continue;
            }

            foreach ($this->locationIdsOf($schedule) as $locationId) {
                $address = $addresses[$locationId] ?? null;

                if ($address === null || $this->resources->findForSubject($doctor, $address) !== null) {
                    continue;
                }

                $rows[] = ['doctor', (string) $doctor->getName(), (string) ($address->getName() ?? '—')];
                $created++;

                if ($force) {
                    $this->linker->link($doctor, $address, $doctor->getName());
                }
            }
        }

        return $created;
    }

    /** @return int[] شناسهٔ آدرس‌هایی که این برنامه شیفت فعالی رویشان دارد */
    private function locationIdsOf(WeeklySchedule $schedule): array
    {
        $ids = [];

        foreach ($schedule->getSetting() as $day) {
            foreach (($day['sessions'] ?? []) as $session) {
                $locationId = $session['location_id'] ?? null;

                if (($session['active'] ?? false) && is_numeric($locationId)) {
                    $ids[(int) $locationId] = true;
                }
            }
        }

        return array_keys($ids);
    }

    /** @return array<string, array<int, DoctorAddress>> "type:id" => [شناسهٔ آدرس => آدرس] */
    private function addressesByPair(): array
    {
        $grouped = [];

        foreach ($this->em->getRepository(DoctorAddress::class)->findAll() as $address) {
            try {
                $key = $address->tenantEntityType() . ':' . $address->tenantEntityId();
            } catch (\LogicException) {
                // آدرس بی‌مالک؛ نه منبعی می‌گیرد نه محیطی دارد.
                continue;
            }

            $grouped[$key][(int) $address->getId()] = $address;
        }

        return $grouped;
    }
}
