<?php

namespace App\Shared\Command;

use App\Config\Repository\SiteConfigRepository;
use App\Payment\Entity\Payment;
use App\Settlement\Service\CommissionService;
use Doctrine\DBAL\Connection;
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;
use Symfony\Component\Uid\Uuid;

/**
 * دیتای دمو production-like برای تست ماژول نمایندگان و نوبت‌دهی.
 *
 * Markerهای داده‌ی دمو (مبنای purge — با داده‌ی واقعی تداخل ندارند):
 *   نماینده‌ها:  موبایل کاربر 09124000xxx
 *   بیماران:     موبایل 09125xxxxxx
 *   پزشکان:      موبایل کاربر 09126xxxxxx + medical_system_code از 100000
 *   کلینیک‌ها:   موبایل کاربر 09127xxxxxx
 *   پرداخت‌ها:   metadata JSON دارای "demo":true + order_id با پیشوند DEMO-
 *
 * درج حجیم با DBAL bulk INSERT (chunkهای ۵۰۰تایی)؛ کمیسیونِ سناریوهای match با همان
 * فرمول CommissionService::settle مستقیم درج می‌شود و یک زیرمجموعه از مسیر واقعی
 * سرویس رد می‌شود تا parity فرمول تضمین بماند.
 *
 *   ddev exec php bin/console app:seed-demo-data --purge
 */
#[AsCommand(name: 'app:seed-demo-data', description: 'Seed production-like demo data (representations, doctors, clinics, users, appointments, commissions)')]
class SeedDemoDataCommand extends Command
{
    private const CHUNK = 500;

    private const GLOBAL_REP_DOMAINS = ['x-nobat.ir', 'global-doctor.ir', 'iran-doc.ir', 'salamat-nobat.ir', 'doc24.ir'];

    private const FIRST_NAMES = ['علی', 'محمد', 'حسین', 'رضا', 'مهدی', 'امیر', 'سعید', 'حسن', 'مجید', 'احمد', 'مریم', 'زهرا', 'فاطمه', 'سارا', 'نرگس', 'لیلا', 'مینا', 'شیما', 'الهام', 'نسرین', 'کامران', 'بهرام', 'فرهاد', 'پیمان', 'آرش'];
    private const LAST_NAMES  = ['احمدی', 'محمدی', 'رضایی', 'کریمی', 'حسینی', 'موسوی', 'جعفری', 'صادقی', 'رحیمی', 'نجفی', 'قاسمی', 'هاشمی', 'اکبری', 'امینی', 'شریفی', 'توکلی', 'زارعی', 'مرادی', 'عباسی', 'فلاحی', 'نوری', 'سلطانی', 'کاظمی', 'یوسفی', 'باقری'];

    private const SPECIALTY_NAMES = ['قلب', 'داخلی', 'ارتوپدی', 'پوست و مو', 'چشم', 'زنان', 'کودکان', 'دندانپزشکی', 'مغز و اعصاب', 'روانپزشکی'];

    /** @var array<int,int> user_id نماینده → موجودی جاری کیف‌پول (برای balance_after) */
    private array $walletBalance = [];

    public function __construct(
        private readonly Connection $db,
        private readonly EntityManagerInterface $em,
        private readonly SiteConfigRepository $configRepo,
        private readonly CommissionService $commissionService,
        private readonly string $environment,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addOption('doctors', null, InputOption::VALUE_REQUIRED, 'تعداد پزشک', '500')
            ->addOption('clinics', null, InputOption::VALUE_REQUIRED, 'تعداد کلینیک', '200')
            ->addOption('users', null, InputOption::VALUE_REQUIRED, 'تعداد بیمار', '10000')
            ->addOption('appointments', null, InputOption::VALUE_REQUIRED, 'تعداد نوبت', '20000')
            ->addOption('purge', null, InputOption::VALUE_NONE, 'حذف داده‌ی دموی قبلی پیش از seed')
            ->addOption('force', null, InputOption::VALUE_NONE, 'اجازه‌ی اجرا روی prod');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);

        if ($this->environment === 'prod' && !$input->getOption('force')) {
            $io->error('این seeder برای محیط تست است؛ روی prod فقط با --force اجرا می‌شود.');
            return Command::FAILURE;
        }

        $nDoctors      = max(1, (int) $input->getOption('doctors'));
        $nClinics      = max(1, (int) $input->getOption('clinics'));
        $nUsers        = max(1, (int) $input->getOption('users'));
        $nAppointments = max(1, (int) $input->getOption('appointments'));

        mt_srand(42); // اجراهای تکرارپذیر

        if ($input->getOption('purge')) {
            $this->purge($io);
        }

        $cities = $this->db->fetchAllAssociative('SELECT id, name, domain FROM cities WHERE domain IS NOT NULL ORDER BY id');
        if (count($cities) < 5) {
            $io->error('جدول cities خالی است — اول app:seed-categories را اجرا کنید.');
            return Command::FAILURE;
        }
        $specialtyIds = $this->resolveSpecialtyIds($io);

        // کمیسیون‌ها باید فعال باشند تا مسیر واقعی سرویس هم ثبت کند.
        $this->configRepo->set('appointment_commission_enabled', '1');
        $this->configRepo->set('upgrade_commission_enabled', '1');
        if ($this->configRepo->get('upgrade_commission_percent') === null) {
            $this->configRepo->set('upgrade_commission_percent', '20');
        }

        $now = time();

        $io->section('۱) نمایندگان');
        $reps = $this->seedRepresentations($cities, $now);
        $io->text(sprintf('%d شهری + %d سراسری', 20, 5));

        $io->section('۲) بیماران');
        $patientIds = $this->seedPatients($nUsers, $now, $io);

        $io->section('۳) پزشکان + آدرس + برنامه هفتگی');
        $doctors = $this->seedDoctors($nDoctors, $reps, $now, $specialtyIds, $io);

        $io->section('۴) کلینیک‌ها');
        $this->seedClinics($nClinics, $reps, $doctors, $now, $io);

        $io->section('۵) نوبت‌ها + پرداخت‌ها + کمیسیون');
        $scenarioCounts = $this->seedAppointments($nAppointments, $reps, $doctors, $patientIds, $now, $io);

        $io->section('۶) اشتراک‌ها');
        $subCounts = $this->seedSubscriptions($reps, $doctors, $now, $io);

        $io->section('۷) parity با CommissionService واقعی');
        $parityOk = $this->verifyParity($reps, $doctors, $patientIds, $now, $io);

        $this->report($io, $scenarioCounts, $subCounts, $parityOk);

        return $parityOk ? Command::SUCCESS : Command::FAILURE;
    }

    // ── purge ────────────────────────────────────────────────────────────────

    private function purge(SymfonyStyle $io): void
    {
        $io->section('purge داده‌ی دموی قبلی');

        // پرداخت‌های parity از مسیر واقعی سرویس order_id استاندارد ORD- می‌گیرند → marker اصلی metadata است.
        $demoPayments = 'SELECT id FROM payments WHERE order_id LIKE "DEMO-%" OR metadata LIKE \'%"demo":true%\'';
        $demoDoctors  = 'SELECT id FROM doctors WHERE medical_system_code >= 100000 AND medical_system_code < 101000';
        $demoUsers    = "SELECT id FROM users WHERE mobile_number LIKE '09124000%' OR mobile_number LIKE '09125%' OR mobile_number LIKE '09126%' OR mobile_number LIKE '09127%'";

        $steps = [
            "DELETE FROM financial_breakdowns WHERE payment_id IN ($demoPayments)",
            "DELETE FROM wallet_transactions WHERE payment_id IN ($demoPayments)",
            "DELETE FROM clinic_subscriptions WHERE payment_id IN ($demoPayments)",
            "DELETE FROM appointments WHERE doctor_id IN ($demoDoctors)",
            "DELETE FROM payments WHERE order_id LIKE 'DEMO-%' OR metadata LIKE '%\"demo\":true%'",
            "DELETE FROM weekly_schedules WHERE doctor_id IN ($demoDoctors)",
            "DELETE FROM clinic_doctors WHERE doctor_id IN ($demoDoctors)",
            "DELETE FROM doctor_specialties WHERE doctor_id IN ($demoDoctors)",
            "DELETE FROM doctor_addresses WHERE doctor_id IN ($demoDoctors)",
            "DELETE FROM clinics WHERE user_id IN (SELECT id FROM users WHERE mobile_number LIKE '09127%')",
            "DELETE FROM doctors WHERE medical_system_code >= 100000 AND medical_system_code < 101000",
            "DELETE FROM representations WHERE user_id IN (SELECT id FROM users WHERE mobile_number LIKE '09124000%')",
            "DELETE FROM users WHERE id IN ($demoUsers)",
        ];

        foreach ($steps as $sql) {
            // MariaDB روی DELETE با subquery از همان جدول خطا می‌دهد → از جدول موقتِ derived استفاده کن.
            $sql = preg_replace('/IN \((SELECT id FROM users[^)]*)\)$/', 'IN (SELECT id FROM (\1) AS t)', $sql) ?? $sql;
            $this->db->executeStatement($sql);
        }

        $io->text('purge انجام شد.');
    }

    // ── سازنده‌های پایه ──────────────────────────────────────────────────────

    /** درج bulk با chunk؛ rows = لیست ردیف‌های associative با کلیدهای یکسان. */
    private function bulkInsert(string $table, array $rows): void
    {
        if ($rows === []) return;
        $cols = array_keys($rows[0]);
        $colSql = implode(', ', array_map(fn($c) => "`$c`", $cols));

        foreach (array_chunk($rows, self::CHUNK) as $chunk) {
            $placeholders = [];
            $params = [];
            foreach ($chunk as $row) {
                $placeholders[] = '(' . implode(', ', array_fill(0, count($cols), '?')) . ')';
                foreach ($cols as $c) $params[] = $row[$c];
            }
            $this->db->executeStatement(
                "INSERT INTO `$table` ($colSql) VALUES " . implode(', ', $placeholders),
                $params,
            );
        }
    }

    private function insertUser(string $mobile, string $name, array $roles, int $now): int
    {
        $this->db->insert('users', [
            'uuid'          => Uuid::v4()->toRfc4122(),
            'mobile_number' => $mobile,
            'real_name'     => $name,
            'roles'         => json_encode($roles),
            'national_code_verified' => 0,
            'status'        => 1,
            'created_at'    => $now,
            'updated_at'    => $now,
        ]);
        return (int) $this->db->lastInsertId();
    }

    private function persianName(int $i): string
    {
        return self::FIRST_NAMES[$i % 25] . ' ' . self::LAST_NAMES[intdiv($i, 25) % 25];
    }

    private function resolveSpecialtyIds(SymfonyStyle $io): array
    {
        $rows = $this->db->fetchAllAssociative('SELECT id, name FROM specialties');
        $byName = array_column($rows, 'id', 'name');
        $ids = [];
        foreach (self::SPECIALTY_NAMES as $name) {
            foreach ($byName as $n => $id) {
                if (str_contains((string) $n, $name)) { $ids[] = (int) $id; continue 2; }
            }
        }
        if ($ids === []) {
            $ids = array_map('intval', array_slice(array_column($rows, 'id'), 0, 10));
            $io->warning('تخصص‌های خواسته‌شده در جدول نبود — از ۱۰ تخصص اول استفاده شد.');
        }
        return $ids;
    }

    // ── نمایندگان ────────────────────────────────────────────────────────────

    /** @return array{city: array<int,array>, global: array<int,array>} */
    private function seedRepresentations(array $cities, int $now): array
    {
        $cityReps = [];
        $cityPool = $cities;

        for ($i = 0; $i < 20; $i++) {
            $name   = 'نماینده ' . $this->persianName($i);
            $userId = $this->insertUser(sprintf('09124000%03d', $i + 1), $name, ['ROLE_USER', 'ROLE_REPRESENTATION'], $now);

            // ۱ تا ۳ شهر؛ شهر اصلی = دامنه‌ی نماینده (مدل واقعی نماینده‌ی شهری).
            $count  = 1 + ($i % 3);
            $chosen = [];
            for ($k = 0; $k < $count && $cityPool !== []; $k++) {
                $idx = ($i * 3 + $k) % count($cityPool);
                $chosen[] = $cityPool[$idx];
                array_splice($cityPool, $idx, 1);
            }
            // استخر شهرِ اختصاص‌نیافته تمام شد → عضویت از لیست کامل، دامنه مصنوعی یکتا
            // (دامنه‌ی شهری فقط وقتی که آن شهر هنوز نماینده‌ی دامنه‌دار ندارد).
            $domain = $chosen !== [] ? $chosen[0]['domain'] : sprintf('demo-rep%02d.ir', $i + 1);
            if ($chosen === []) $chosen[] = $cities[$i % count($cities)];

            $this->db->insert('representations', [
                'uuid'               => Uuid::v4()->toRfc4122(),
                'user_id'            => $userId,
                'full_name'          => $name,
                'mobile_number'      => sprintf('09124000%03d', $i + 1),
                'city_id'            => $chosen[0]['id'],
                'domain'             => $domain,
                'is_global'          => 0,
                'commission_percent' => (string) (8 + ($i % 8)),
                'active'             => 1,
                'created_at'         => $now,
                'updated_at'         => $now,
            ]);
            $repId = (int) $this->db->lastInsertId();
            foreach ($chosen as $c) {
                $this->db->insert('representation_cities', ['representation_id' => $repId, 'city_id' => $c['id']]);
            }
            $this->walletBalance[$userId] = 0;
            $cityReps[] = ['id' => $repId, 'user_id' => $userId, 'domain' => $domain, 'percent' => 8 + ($i % 8), 'cities' => array_column($chosen, 'id')];
        }

        $globalReps = [];
        foreach (self::GLOBAL_REP_DOMAINS as $g => $domain) {
            $name   = 'نماینده سراسری ' . $this->persianName(20 + $g);
            $userId = $this->insertUser(sprintf('09124000%03d', 100 + $g), $name, ['ROLE_USER', 'ROLE_REPRESENTATION'], $now);
            $this->db->insert('representations', [
                'uuid'               => Uuid::v4()->toRfc4122(),
                'user_id'            => $userId,
                'full_name'          => $name,
                'mobile_number'      => sprintf('09124000%03d', 100 + $g),
                'city_id'            => null,
                'domain'             => $domain,
                'is_global'          => 1,
                'commission_percent' => (string) (10 + $g),
                'active'             => 1,
                'created_at'         => $now,
                'updated_at'         => $now,
            ]);
            $this->walletBalance[$userId] = 0;
            $globalReps[] = ['id' => (int) $this->db->lastInsertId(), 'user_id' => $userId, 'domain' => $domain, 'percent' => 10 + $g, 'cities' => [$cities[$g]['id']]];
        }

        return ['city' => $cityReps, 'global' => $globalReps];
    }

    // ── بیماران ──────────────────────────────────────────────────────────────

    /** @return int[] */
    private function seedPatients(int $count, int $now, SymfonyStyle $io): array
    {
        $rows = [];
        for ($i = 0; $i < $count; $i++) {
            $rows[] = [
                'uuid'          => Uuid::v4()->toRfc4122(),
                'mobile_number' => sprintf('09125%06d', $i),
                'real_name'     => $this->persianName($i),
                'roles'         => json_encode(['ROLE_USER']),
                'national_code_verified' => 0,
                'status'        => 1,
                'created_at'    => $now - mt_rand(0, 180 * 86400),
                'updated_at'    => $now,
            ];
        }
        $this->bulkInsert('users', $rows);
        $ids = $this->db->fetchFirstColumn("SELECT id FROM users WHERE mobile_number LIKE '09125%' ORDER BY id");
        $io->text(count($ids) . ' بیمار');
        return array_map('intval', $ids);
    }

    // ── پزشکان ───────────────────────────────────────────────────────────────

    /** @return array<int,array{id:int,rep:?array,address_id:int,duration:int,user_id:int}> */
    private function seedDoctors(int $count, array $reps, int $now, array $specialtyIds, SymfonyStyle $io): array
    {
        $allReps = array_merge($reps['city'], $reps['global']);

        // توزیع مالکیت: ~۱۰٪ بدون نماینده؛ سراسری‌ها هر کدام سهم ثابت؛ بقیه بین شهری‌ها.
        $userRows = [];
        for ($i = 0; $i < $count; $i++) {
            $userRows[] = [
                'uuid'          => Uuid::v4()->toRfc4122(),
                'mobile_number' => sprintf('09126%06d', $i),
                'real_name'     => 'دکتر ' . $this->persianName($i),
                'roles'         => json_encode(['ROLE_USER', 'ROLE_DOCTOR']),
                'national_code_verified' => 0,
                'status'        => 1,
                'created_at'    => $now,
                'updated_at'    => $now,
            ];
        }
        $this->bulkInsert('users', $userRows);
        $doctorUserIds = array_map('intval', $this->db->fetchFirstColumn("SELECT id FROM users WHERE mobile_number LIKE '09126%' ORDER BY id"));

        $doctorRows = [];
        $ownership  = [];
        foreach ($doctorUserIds as $i => $userId) {
            if ($i % 10 === 9) {
                $rep = null;                                        // بدون نماینده
            } elseif ($i % 10 >= 7) {
                $rep = $reps['global'][intdiv($i, 10) % 5];         // ~۲۰٪ سراسری، چرخشی بین هر ۵
            } else {
                $rep = $reps['city'][$i % 20];                      // بقیه شهری
            }
            $ownership[$i] = $rep;

            $doctorRows[] = [
                'uuid'                       => Uuid::v4()->toRfc4122(),
                'user_id'                    => $userId,
                'name'                       => 'دکتر ' . $this->persianName($i),
                'gender'                     => $i % 3 === 0 ? 'woman' : 'man',
                'medical_system_code'        => (string) (100000 + $i),
                'mobile_number'              => sprintf('09126%06d', $i),
                'degree'                     => ['specialist', 'general', 'subspecialist'][$i % 3],
                'active_doctor_appointment'  => $i % 7 === 6 ? 0 : 1,
                'representation_id'          => $rep['id'] ?? null,
                'doctor_rate'                => mt_rand(30, 50) / 10,
                'doctor_rate_percentage'     => mt_rand(50, 100),
                'created_at'                 => $now,
                'updated_at'                 => $now,
            ];
        }
        $this->bulkInsert('doctors', $doctorRows);
        $doctorIds = array_map('intval', $this->db->fetchFirstColumn('SELECT id FROM doctors WHERE medical_system_code >= 100000 AND medical_system_code < 101000 ORDER BY id'));

        // تخصص + آدرس + برنامه هفتگی
        $specRows = $addrRows = [];
        foreach ($doctorIds as $i => $docId) {
            $specRows[] = ['doctor_id' => $docId, 'specialty_id' => $specialtyIds[$i % count($specialtyIds)]];
            $rep        = $ownership[$i];
            $cityId     = $rep !== null ? $rep['cities'][$i % count($rep['cities'])] : 108; // بدون نماینده → تهران
            $addrRows[] = [
                'uuid'       => Uuid::v4()->toRfc4122(),
                'doctor_id'  => $docId,
                'name'       => 'مطب',
                'address'    => 'خیابان اصلی، پلاک ' . ($i + 1),
                'telephone'  => sprintf('0219998%04d', $i),
                'city_id'    => $cityId,
                'type'       => 'personal',
                'created_at' => $now,
                'updated_at' => $now,
            ];
        }
        $this->bulkInsert('doctor_specialties', $specRows);
        $this->bulkInsert('doctor_addresses', $addrRows);
        $addrIds = array_map('intval', $this->db->fetchFirstColumn("SELECT id FROM doctor_addresses WHERE telephone LIKE '0219998%' ORDER BY doctor_id"));

        $scheduleRows = [];
        $doctors = [];
        foreach ($doctorIds as $i => $docId) {
            $duration = [15, 20, 30][$i % 3];
            $days     = 3 + ($i % 4); // ۳ تا ۶ روز کاری
            $setting  = [];
            for ($d = 0; $d < 7; $d++) {
                if ($d >= $days) continue;
                $sessions = [$this->session('08:00', '14:00', $duration, $addrIds[$i])];
                if ($i % 2 === 0) $sessions[] = $this->session('16:00', '21:00', $duration, $addrIds[$i]);
                $setting[(string) $d] = ['sessions' => $sessions];
            }
            $scheduleRows[] = [
                'uuid'       => Uuid::v4()->toRfc4122(),
                'doctor_id'  => $docId,
                'setting'    => json_encode($setting, JSON_UNESCAPED_UNICODE),
                'created_at' => $now,
                'updated_at' => $now,
            ];
            $doctors[] = ['id' => $docId, 'rep' => $ownership[$i], 'address_id' => $addrIds[$i], 'duration' => $duration, 'user_id' => $doctorUserIds[$i]];
        }
        $this->bulkInsert('weekly_schedules', $scheduleRows);

        $io->text(count($doctors) . ' پزشک + آدرس + برنامه هفتگی');
        return $doctors;
    }

    private function session(string $start, string $end, int $duration, int $locationId): array
    {
        return [
            'active' => true, 'location_id' => $locationId,
            'start_time' => $start, 'end_time' => $end,
            'duration_per_patient' => $duration,
            'has_rest' => false, 'rest_interval' => 60, 'time_to_rest' => 10, 'patient_limit' => null,
        ];
    }

    // ── کلینیک‌ها ────────────────────────────────────────────────────────────

    private function seedClinics(int $count, array $reps, array $doctors, int $now, SymfonyStyle $io): void
    {
        $allReps = array_merge($reps['city'], $reps['global']);

        $userRows = [];
        for ($i = 0; $i < $count; $i++) {
            $userRows[] = [
                'uuid'          => Uuid::v4()->toRfc4122(),
                'mobile_number' => sprintf('09127%06d', $i),
                'real_name'     => 'مدیر کلینیک ' . ($i + 1),
                'roles'         => json_encode(['ROLE_USER', 'ROLE_CLINIC']),
                'national_code_verified' => 0,
                'status'        => 1,
                'created_at'    => $now,
                'updated_at'    => $now,
            ];
        }
        $this->bulkInsert('users', $userRows);
        $clinicUserIds = array_map('intval', $this->db->fetchFirstColumn("SELECT id FROM users WHERE mobile_number LIKE '09127%' ORDER BY id"));

        $clinicRows = [];
        foreach ($clinicUserIds as $i => $userId) {
            $rep = $allReps[$i % count($allReps)];
            $clinicRows[] = [
                'uuid'              => Uuid::v4()->toRfc4122(),
                'user_id'           => $userId,
                'name'              => 'کلینیک ' . self::LAST_NAMES[$i % 25] . ' ' . ($i + 1),
                'address'           => 'بلوار مرکزی، ساختمان ' . ($i + 1),
                'telephone'         => sprintf('0219997%04d', $i),
                'city_id'           => $rep['cities'][0],
                'representation_id' => $rep['id'],
                'is_active'         => 1,
                'is_24_7'           => 0,
                'created_at'        => $now,
                'updated_at'        => $now,
            ];
        }
        $this->bulkInsert('clinics', $clinicRows);
        $clinicIds = array_map('intval', $this->db->fetchFirstColumn("SELECT id FROM clinics WHERE telephone LIKE '0219997%' ORDER BY id"));

        // اتصال ۱–۵ پزشکِ همان نماینده به هر کلینیک.
        $byRep = [];
        foreach ($doctors as $d) {
            if ($d['rep'] !== null) $byRep[$d['rep']['id']][] = $d['id'];
        }
        $linkRows = [];
        foreach ($clinicIds as $i => $clinicId) {
            $rep  = $allReps[$i % count($allReps)];
            $pool = $byRep[$rep['id']] ?? [];
            foreach (array_slice($pool, 0, 1 + ($i % 5)) as $docId) {
                $linkRows[] = ['clinic_id' => $clinicId, 'doctor_id' => $docId];
            }
        }
        // حذف تکراری‌ها (PK مرکب)
        $seen = [];
        $linkRows = array_values(array_filter($linkRows, function ($r) use (&$seen) {
            $k = $r['clinic_id'] . '-' . $r['doctor_id'];
            if (isset($seen[$k])) return false;
            return $seen[$k] = true;
        }));
        $this->bulkInsert('clinic_doctors', $linkRows);

        $io->text(count($clinicIds) . ' کلینیک + ' . count($linkRows) . ' اتصال پزشک');
    }

    // ── نوبت‌ها + پرداخت + کمیسیون ───────────────────────────────────────────

    /** @return array<string,int> */
    private function seedAppointments(int $count, array $reps, array $doctors, array $patientIds, int $now, SymfonyStyle $io): array
    {
        $allReps  = array_merge($reps['city'], $reps['global']);
        $fee      = max(100000, (int) ($this->configRepo->get('appointment_fee_rials') ?: 1500000));
        $smsFee   = (int) ($this->configRepo->get('sms_panel_fee_rials') ?: 0);
        $taxOn    = $this->configRepo->get('tax_enabled') === '1';
        $taxPct   = $taxOn ? (float) $this->configRepo->get('tax_percent') : 0.0;
        $today    = strtotime('today');

        $counts = ['match' => 0, 'mismatch' => 0, 'cancelled' => 0, 'pending' => 0];
        $apptRows = $payRows = $bdRows = $wtRows = [];
        $io->progressStart($count);

        for ($i = 0; $i < $count; $i++) {
            $doc = $doctors[$i % count($doctors)];
            $dur = $doc['duration'] * 60;
            $dayOffset = mt_rand(-60, 14);
            $base = $today + $dayOffset * 86400 + 8 * 3600;
            $slotStart = $base + mt_rand(0, intdiv(6 * 3600, $dur) - 1) * $dur;
            $slotEnd   = $slotStart + $dur;
            $patient   = $patientIds[$i % count($patientIds)];
            $apptUuid  = Uuid::v4()->toRfc4122();

            $r = $i % 20;
            if ($r < 12 && $doc['rep'] !== null)      $scenario = 'match';      // 60%
            elseif ($r < 15 && $doc['rep'] !== null)  $scenario = 'mismatch';   // 15%
            elseif ($r < 18)                          $scenario = 'cancelled';  // 15%
            else                                      $scenario = 'pending';    // 10%
            if ($doc['rep'] === null && in_array($scenario, ['match', 'mismatch'], true)) $scenario = 'pending';
            $counts[$scenario]++;

            $status = match ($scenario) {
                'match', 'mismatch' => $slotStart < $now ? 'completed' : 'confirmed',
                'cancelled'         => 'cancelled_by_user',
                'pending'           => 'pending',
            };

            $bookingRepId = null;
            $domain = null;
            if ($scenario === 'match') {
                $domain = $doc['rep']['domain'];
                $bookingRepId = $doc['rep']['id'];
            } elseif ($scenario === 'mismatch') {
                $other = $allReps[($i + 7) % count($allReps)];
                if ($other['id'] === $doc['rep']['id']) $other = $allReps[($i + 8) % count($allReps)];
                $domain = $other['domain'];
                $bookingRepId = $other['id'];
            }

            $apptRows[] = [
                'uuid' => $apptUuid, 'version' => 1,
                'slot_start' => $slotStart, 'slot_end' => $slotEnd, 'status' => $status,
                'doctor_id' => $doc['id'], 'user_id' => $patient,
                'patient_name' => $this->persianName($i), 'patient_mobile' => sprintf('09125%06d', $i % count($patientIds)),
                'patient_national_code' => sprintf('%010d', 1000000000 + $i), 'patient_gender' => $i % 2 ? 'man' : 'woman',
                'address_id' => $doc['address_id'], 'booking_representation_id' => $bookingRepId,
                'created_at' => min($slotStart, $now) - 3600, 'updated_at' => $now,
            ];

            if ($scenario === 'match' || $scenario === 'mismatch') {
                $payRows[] = [
                    'uuid' => Uuid::v4()->toRfc4122(),
                    'order_id' => 'DEMO-A' . $i,
                    'amount_rials' => $fee, 'status' => 'success', 'gateway' => 'mock', 'type' => 'appointment',
                    'reference_id' => 'DEMOREF-A' . $i,
                    'frontend_address' => 'https://' . $domain . '/payment/result',
                    'user_id' => $patient,
                    'metadata' => json_encode(['demo' => true, 'scenario' => $scenario, 'appt_uuid' => $apptUuid]),
                    'created_at' => min($slotStart, $now) - 3000, 'updated_at' => $now,
                ];

                if ($scenario === 'match') {
                    $rep = $doc['rep'];
                    [$bd, $wt] = $this->breakdownRows('appointment', $fee, $smsFee, $taxOn, $taxPct, (float) $rep['percent'], $rep, $doc['id'], null, $patient, $now, 'DEMO-A' . $i);
                    $bdRows[] = $bd;
                    if ($wt !== null) $wtRows[] = $wt;
                }
            }
            $io->progressAdvance();
        }
        $io->progressFinish();

        $this->bulkInsert('appointments', $apptRows);
        $this->bulkInsert('payments', $payRows);
        // payment_id واقعی را به breakdownها وصل کن (بر اساس order_id).
        $payIdByOrder = [];
        foreach ($this->db->fetchAllAssociative("SELECT id, order_id FROM payments WHERE order_id LIKE 'DEMO-A%'") as $p) {
            $payIdByOrder[$p['order_id']] = (int) $p['id'];
        }
        foreach ($bdRows as &$bd) { $bd['payment_id'] = $payIdByOrder[$bd['payment_id']]; }
        unset($bd);
        foreach ($wtRows as &$wt) { $wt['payment_id'] = $payIdByOrder[$wt['payment_id']]; }
        unset($wt);
        $this->bulkInsert('financial_breakdowns', $bdRows);
        $this->bulkInsert('wallet_transactions', $wtRows);

        $io->text(sprintf('نوبت: %d | پرداخت: %d | کمیسیون match: %d', count($apptRows), count($payRows), count($bdRows)));
        return $counts;
    }

    /**
     * همان فرمول CommissionService::settle — parity در verifyParity() چک می‌شود.
     * payment_id موقتاً order_id است و بعد از درج پرداخت‌ها resolve می‌شود.
     * @return array{0: array, 1: ?array}
     */
    private function breakdownRows(string $source, int $gross, int $smsFee, bool $taxOn, float $taxPct, float $percent, array $rep, ?int $doctorId, ?int $clinicId, int $userId, int $now, string $orderRef): array
    {
        $afterSms    = max(0, $gross - $smsFee);
        $taxRials    = ($taxOn && $taxPct > 0) ? (int) round($afterSms * $taxPct / (100 + $taxPct)) : 0;
        $netAfterTax = $afterSms - $taxRials;
        $repShare    = (int) round($netAfterTax * $percent / 100);

        $bd = [
            'uuid' => Uuid::v4()->toRfc4122(), 'source' => $source,
            'gross_rials' => $gross, 'sms_fee_rials' => $smsFee,
            'tax_percent' => number_format($taxPct, 2, '.', ''), 'tax_rials' => $taxRials,
            'net_after_tax_rials' => $netAfterTax,
            'commission_percent' => number_format($percent, 2, '.', ''),
            'representation_share_rials' => $repShare,
            'system_share_rials' => $gross - $smsFee - $taxRials - $repShare,
            'representation_id' => $rep['id'], 'doctor_id' => $doctorId, 'clinic_id' => $clinicId,
            'user_id' => $userId, 'payment_id' => $orderRef, 'created_at' => $now,
        ];

        $wt = null;
        if ($repShare > 0) {
            $this->walletBalance[$rep['user_id']] += $repShare;
            $wt = [
                'uuid' => Uuid::v4()->toRfc4122(),
                'amount_rials' => $repShare, 'type' => 'credit',
                'description' => 'پورسانت ' . $source . ' ' . $orderRef,
                'balance_after' => $this->walletBalance[$rep['user_id']],
                'user_id' => $rep['user_id'], 'payment_id' => $orderRef, 'created_at' => $now,
            ];
        }

        return [$bd, $wt];
    }

    // ── اشتراک‌ها ────────────────────────────────────────────────────────────

    /** @return array<string,int> */
    private function seedSubscriptions(array $reps, array $doctors, int $now, SymfonyStyle $io): array
    {
        $period = $this->db->fetchAssociative('SELECT id, plan_id FROM subscription_periods ORDER BY id LIMIT 1');
        if ($period === false) {
            $io->warning('subscription_periods خالی است — اشتراک seed نشد.');
            return ['sub_match' => 0, 'sub_mismatch' => 0];
        }

        $allReps = array_merge($reps['city'], $reps['global']);
        $owned   = array_values(array_filter($doctors, fn($d) => $d['rep'] !== null));
        $smsFee  = (int) ($this->configRepo->get('sms_panel_fee_rials') ?: 0);
        $taxOn   = $this->configRepo->get('tax_enabled') === '1';
        $taxPct  = $taxOn ? (float) $this->configRepo->get('tax_percent') : 0.0;
        $upPct   = (float) $this->configRepo->get('upgrade_commission_percent');
        $amount  = 5000000;

        $payRows = $subRows = $bdRows = $wtRows = [];
        $counts  = ['sub_match' => 0, 'sub_mismatch' => 0];

        for ($i = 0; $i < 100 && $i < count($owned); $i++) {
            $doc   = $owned[$i * 3 % count($owned)];
            $match = $i % 2 === 0;
            $rep   = $doc['rep'];
            $domain = $match ? $rep['domain'] : $allReps[($i + 9) % count($allReps)]['domain'];
            if (!$match && $domain === $rep['domain']) $domain = $allReps[($i + 10) % count($allReps)]['domain'];
            $counts[$match ? 'sub_match' : 'sub_mismatch']++;

            $payRows[] = [
                'uuid' => Uuid::v4()->toRfc4122(),
                'order_id' => 'DEMO-S' . $i,
                'amount_rials' => $amount, 'status' => 'success', 'gateway' => 'mock', 'type' => 'subscription',
                'reference_id' => 'DEMOREF-S' . $i,
                'frontend_address' => 'https://' . $domain . '/panel',
                'user_id' => $doc['user_id'],
                'metadata' => json_encode(['demo' => true, 'scenario' => $match ? 'sub_match' : 'sub_mismatch']),
                'created_at' => $now - mt_rand(0, 30 * 86400), 'updated_at' => $now,
            ];
            $subRows[] = [
                'uuid' => Uuid::v4()->toRfc4122(),
                'entity_type' => 'doctor', 'entity_id' => $doc['id'], 'is_trial' => 0,
                'starts_at' => $now - 86400, 'expires_at' => $now + 30 * 86400,
                'plan_id' => (int) $period['plan_id'], 'period_id' => (int) $period['id'],
                'payment_id' => 'DEMO-S' . $i, 'created_at' => $now,
            ];
            if ($match) {
                [$bd, $wt] = $this->breakdownRows('subscription', $amount, $smsFee, $taxOn, $taxPct, $upPct, $rep, $doc['id'], null, $doc['user_id'], $now, 'DEMO-S' . $i);
                $bdRows[] = $bd;
                if ($wt !== null) $wtRows[] = $wt;
            }
        }

        $this->bulkInsert('payments', $payRows);
        $payIdByOrder = [];
        foreach ($this->db->fetchAllAssociative("SELECT id, order_id FROM payments WHERE order_id LIKE 'DEMO-S%'") as $p) {
            $payIdByOrder[$p['order_id']] = (int) $p['id'];
        }
        foreach ($subRows as &$s) { $s['payment_id'] = $payIdByOrder[$s['payment_id']]; }
        unset($s);
        foreach ($bdRows as &$bd) { $bd['payment_id'] = $payIdByOrder[$bd['payment_id']]; }
        unset($bd);
        foreach ($wtRows as &$wt) { $wt['payment_id'] = $payIdByOrder[$wt['payment_id']]; }
        unset($wt);
        $this->bulkInsert('clinic_subscriptions', $subRows);
        $this->bulkInsert('financial_breakdowns', $bdRows);
        $this->bulkInsert('wallet_transactions', $wtRows);

        $io->text(sprintf('اشتراک: %d (match: %d، mismatch: %d)', count($subRows), $counts['sub_match'], $counts['sub_mismatch']));
        return $counts;
    }

    // ── parity با سرویس واقعی ────────────────────────────────────────────────

    private function verifyParity(array $reps, array $doctors, array $patientIds, int $now, SymfonyStyle $io): bool
    {
        $fee = max(100000, (int) ($this->configRepo->get('appointment_fee_rials') ?: 1500000));
        $ok  = true;

        $sample = array_values(array_filter($doctors, fn($d) => $d['rep'] !== null));
        for ($i = 0; $i < 100 && $i < count($sample); $i++) {
            $doc = $sample[$i];
            $rep = $doc['rep'];

            $user = $this->em->getReference(\App\Auth\Entity\User::class, $patientIds[$i]);
            $payment = new Payment($user, $fee, 'mock', Payment::TYPE_APPOINTMENT, 'https://' . $rep['domain'] . '/payment/result');
            $payment->setMetadata(['demo' => true, 'scenario' => 'match-service']);
            $this->em->persist($payment);
            $this->em->flush();

            $this->commissionService->processAppointment($payment, $rep['id'], $rep['id'], $doc['id']);

            $share = $this->db->fetchOne('SELECT representation_share_rials FROM financial_breakdowns WHERE payment_id = ?', [$payment->getId()]);
            $expected = $this->expectedShare($fee, (float) $rep['percent']);
            if ((int) $share !== $expected) {
                $io->error(sprintf('parity شکست: سرویس=%s، فرمول bulk=%d (rep %d)', var_export($share, true), $expected, $rep['id']));
                $ok = false;
                break;
            }
            $this->em->clear();
        }

        if ($ok) $io->text('parity تایید شد — فرمول bulk == CommissionService (۱۰۰ نمونه).');
        return $ok;
    }

    private function expectedShare(int $gross, float $percent): int
    {
        $smsFee   = (int) ($this->configRepo->get('sms_panel_fee_rials') ?: 0);
        $afterSms = max(0, $gross - $smsFee);
        $taxOn    = $this->configRepo->get('tax_enabled') === '1';
        $taxPct   = $taxOn ? (float) $this->configRepo->get('tax_percent') : 0.0;
        $taxRials = ($taxOn && $taxPct > 0) ? (int) round($afterSms * $taxPct / (100 + $taxPct)) : 0;
        return (int) round(($afterSms - $taxRials) * $percent / 100);
    }

    // ── گزارش ────────────────────────────────────────────────────────────────

    private function report(SymfonyStyle $io, array $scenarioCounts, array $subCounts, bool $parityOk): void
    {
        $io->section('گزارش نهایی');
        $tables = ['representations', 'representation_cities', 'doctors', 'clinics', 'clinic_doctors', 'weekly_schedules', 'users', 'appointments', 'payments', 'clinic_subscriptions', 'financial_breakdowns', 'wallet_transactions'];
        $rows = [];
        foreach ($tables as $t) {
            $rows[] = [$t, number_format((int) $this->db->fetchOne("SELECT COUNT(*) FROM `$t`"))];
        }
        $io->table(['جدول', 'تعداد کل'], $rows);
        $io->table(['سناریو', 'تعداد'], array_map(fn($k, $v) => [$k, $v], array_keys($scenarioCounts + $subCounts), array_values($scenarioCounts + $subCounts)));

        $mismatchLeak = (int) $this->db->fetchOne(
            "SELECT COUNT(*) FROM financial_breakdowns b JOIN payments p ON p.id = b.payment_id
             WHERE p.metadata LIKE '%\"scenario\":\"mismatch\"%' OR p.metadata LIKE '%\"scenario\":\"sub_mismatch\"%'"
        );
        $walletParity = $this->db->fetchOne(
            'SELECT (SELECT COALESCE(SUM(representation_share_rials),0) FROM financial_breakdowns)
                  = (SELECT COALESCE(SUM(amount_rials),0) FROM wallet_transactions WHERE type = "credit")'
        );

        $io->listing([
            'کمیسیون نشت‌کرده به سناریوهای mismatch: ' . $mismatchLeak . ' (باید 0 باشد)',
            'برابری مجموع سهم نماینده و کیف‌پول: ' . ($walletParity ? 'OK' : 'FAIL'),
            'parity فرمول با CommissionService: ' . ($parityOk ? 'OK' : 'FAIL'),
        ]);

        $io->section('سناریوهای تست دستی');
        $io->listing([
            'GET /api/v1/doctors?domain=x-nobat.ir → فقط پزشکان نماینده سراسری اول',
            'GET /api/v1/doctors?domain=<دامنه شهر نماینده اول> → رفتار شهری عادی',
            'GET /api/v1/site-context?domain=x-nobat.ir → type=representation و is_global=true',
            'ورود با موبایل 09124000001 (نماینده ۱) → داشبورد نماینده: درآمد > 0',
            'پنل ادمین → نمایندگان → badge «سراسری» روی ۵ نماینده و ستون شهرهای چندتایی',
        ]);
    }
}
