<?php

namespace App\Doctor\Command;

use Doctrine\DBAL\Connection;
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;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

/**
 * حذف پزشکانِ ایمپورت‌شدهٔ خزنده که هنوز مالکیتشان گرفته نشده (`owner_status='unclaimed'`).
 *
 * برخلاف {@see PurgeDoctorsCommand} که همهٔ پزشکان را پاک می‌کند، این کامند فقط
 * رکوردهای claim‌نشدهٔ یک منبع (پیش‌فرض `irimc`) و کاربران جانشینِ یتیم‌شان را حذف
 * می‌کند؛ پزشکان دستی و پروفایل‌های تصاحب‌شده (`claimed`/`pending_transfer`) دست‌نخورده
 * می‌مانند.
 *
 *   php bin/console app:doctors:purge-unclaimed            # فقط گزارش
 *   php bin/console app:doctors:purge-unclaimed --force    # حذف
 */
#[AsCommand(
    name: 'app:doctors:purge-unclaimed',
    description: 'Delete crawler-imported doctors that were never claimed (dry-run by default; --force to apply)',
)]
class PurgeUnclaimedDoctorsCommand extends Command
{
    /** جدول‌های فرزندِ کلیددارِ doctor_id. ترتیب برای خوانایی است؛ FK_CHECKS خاموش می‌شود. */
    private const CHILD_TABLES = [
        'doctor_claim_requests', 'doctor_secretaries', 'doctor_addresses', 'doctor_insurances',
        'doctor_provinces', 'doctor_cities', 'doctor_specialties', 'doctor_expertise',
        'clinic_doctors', 'clinic_doctor_invitations', 'weekly_schedules', 'date_overrides',
        'holidays', 'comments', 'rates', 'appointments',
    ];

    public function __construct(
        private readonly Connection $conn,
        #[Autowire('%kernel.environment%')]
        private readonly string $env,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addOption('force', null, InputOption::VALUE_NONE, 'Actually delete (otherwise dry-run report)')
            ->addOption('source', null, InputOption::VALUE_REQUIRED, "Only this source (default 'irimc')", 'irimc')
            ->addOption('all-sources', null, InputOption::VALUE_NONE, 'Any non-manual source, not just --source')
            ->addOption('i-know-this-is-prod', null, InputOption::VALUE_NONE, 'Required to run against APP_ENV=prod');
    }

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

        // محیط از kernel می‌آید (نه $_ENV) تا گاردِ prod با ست‌شدن APP_ENV از راهی
        // غیر از dotenv دور زده نشود.
        if ($this->env === 'prod' && !$input->getOption('i-know-this-is-prod')) {
            $io->error('روی prod بدون --i-know-this-is-prod اجرا نمی‌شود.');
            return Command::FAILURE;
        }

        // معیار مشترک همهٔ کوئری‌ها: claim‌نشده + فیلتر منبع.
        [$where, $params] = $this->buildCriteria((bool) $input->getOption('all-sources'), (string) $input->getOption('source'));

        $targetCount = (int) $this->conn->fetchOne("SELECT COUNT(*) FROM doctors d WHERE {$where}", $params);
        if ($targetCount === 0) {
            $io->success('هیچ پزشک claim‌نشده‌ای با این معیار نیست.');
            return Command::SUCCESS;
        }

        $io->section($force ? 'حذف پزشکان claim‌نشده' : 'گزارش (dry-run — چیزی حذف نمی‌شود)');
        $io->text(sprintf('پزشکان هدف: %d', $targetCount));

        $rows      = [];
        $appointmentRows = 0;
        foreach (self::CHILD_TABLES as $t) {
            $n = (int) $this->conn->fetchOne(
                "SELECT COUNT(*) FROM {$t} c JOIN doctors d ON d.id = c.doctor_id WHERE {$where}",
                $params,
            );
            if ($t === 'appointments') {
                $appointmentRows = $n;
            }
            $rows[] = [$t, $n];
        }
        $io->table(['جدول وابسته', 'رکورد'], $rows);

        // نوبت روی پزشک claim‌نشده یعنی بیمار واقعی رزرو کرده — حذفش دادهٔ واقعی می‌برد.
        if ($appointmentRows > 0) {
            $io->warning(sprintf('%d نوبت به این پزشکان وصل است و حذف خواهد شد.', $appointmentRows));
        }

        if (!$force) {
            $io->warning('برای حذف واقعی، دوباره با --force اجرا کن.');
            return Command::SUCCESS;
        }

        // کاربران جانشین را پیش از حذف پزشکان بگیر؛ بعدش دیگر قابل یافتن نیستند.
        $surrogateIds = $this->conn->fetchFirstColumn(
            "SELECT d.user_id FROM doctors d WHERE {$where}",
            $params,
        );

        $this->conn->executeStatement('SET FOREIGN_KEY_CHECKS=0');
        try {
            foreach (self::CHILD_TABLES as $t) {
                $n = $this->conn->executeStatement(
                    "DELETE c FROM {$t} c JOIN doctors d ON d.id = c.doctor_id WHERE {$where}",
                    $params,
                );
                $io->text(sprintf('%s: %d حذف شد', $t, $n));
            }

            $deleted = $this->conn->executeStatement("DELETE d FROM doctors d WHERE {$where}", $params);
            $io->text(sprintf('doctors: %d حذف شد', $deleted));

            $surrogates = $this->deleteSurrogates($surrogateIds);
            $io->text(sprintf('کاربران جانشین: %d حذف شد', $surrogates));
        } finally {
            $this->conn->executeStatement('SET FOREIGN_KEY_CHECKS=1');
        }

        $io->success('پزشکان claim‌نشده پاک شدند.');

        return Command::SUCCESS;
    }

    /** @return array{0:string,1:array<string,string>} */
    private function buildCriteria(bool $allSources, string $source): array
    {
        if ($allSources) {
            return ["d.owner_status = 'unclaimed' AND d.source <> 'manual'", []];
        }

        return ["d.owner_status = 'unclaimed' AND d.source = :source", ['source' => $source]];
    }

    /**
     * فقط کاربران جانشینِ ایمپورت را حذف کن — غیرفعال و با موبایلِ synthetic — تا اگر
     * پزشکی به‌اشتباه به کاربر واقعی وصل بود، آن کاربر پاک نشود.
     *
     * @param list<int|string> $ids
     */
    private function deleteSurrogates(array $ids): int
    {
        $deleted = 0;
        foreach (array_chunk($ids, 500) as $chunk) {
            $deleted += $this->conn->executeStatement(
                "DELETE FROM users WHERE id IN (:ids) AND status = 0 AND mobile_number LIKE 'imp\\_%'",
                ['ids' => $chunk],
                ['ids' => \Doctrine\DBAL\ArrayParameterType::INTEGER],
            );
        }

        return $deleted;
    }
}
