<?php

namespace App\Appointment\Command;

use App\Appointment\Repository\WeeklyScheduleRepository;
use App\Clinic\Repository\ClinicRepository;
use App\Doctor\Repository\DoctorAddressRepository;
use App\Doctor\Repository\DoctorRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
 * Moves an existing weekly schedule from the personal context into a clinic.
 *
 * The clinic_id migration marks every pre-existing schedule as personal, because
 * nothing in the data says otherwise. A schedule whose sessions actually point at
 * a clinic address needs to be moved by hand — this command does that, and refuses
 * when the sessions do not agree with the target clinic.
 */
#[AsCommand(name: 'app:schedule:assign-clinic', description: 'Move a doctor\'s personal weekly schedule into a clinic context')]
class AssignScheduleClinicCommand extends Command
{
    public function __construct(
        private readonly WeeklyScheduleRepository $scheduleRepo,
        private readonly DoctorRepository         $doctorRepo,
        private readonly ClinicRepository         $clinicRepo,
        private readonly DoctorAddressRepository  $addressRepo,
        private readonly EntityManagerInterface   $em,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addArgument('doctor-uuid', InputArgument::REQUIRED, 'Doctor uuid')
            ->addArgument('clinic-uuid', InputArgument::REQUIRED, 'Target clinic uuid')
            ->addOption('force', null, InputOption::VALUE_NONE, 'Move even when some sessions use an address outside the clinic');
    }

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

        $doctor = $this->doctorRepo->findByUuid((string) $input->getArgument('doctor-uuid'));
        $clinic = $this->clinicRepo->findByUuid((string) $input->getArgument('clinic-uuid'));

        if ($doctor === null || $clinic === null) {
            $io->error('Doctor or clinic not found.');
            return Command::FAILURE;
        }

        if (!$clinic->hasDoctor($doctor)) {
            $io->error('This doctor is not a member of that clinic.');
            return Command::FAILURE;
        }

        $schedule = $this->scheduleRepo->findByDoctorAndClinic($doctor, null);
        if ($schedule === null) {
            $io->warning('This doctor has no personal schedule to move.');
            return Command::SUCCESS;
        }

        if ($this->scheduleRepo->findByDoctorAndClinic($doctor, $clinic) !== null) {
            $io->error('A schedule already exists for this doctor in that clinic; merge it manually.');
            return Command::FAILURE;
        }

        $foreign = $this->sessionsOutsideClinic($schedule->getDaySchedule(), $doctor, $clinic->getId());
        if ($foreign !== [] && !$input->getOption('force')) {
            $io->error(sprintf(
                'Sessions use address ids outside the clinic: %s. Re-run with --force to move anyway.',
                implode(', ', $foreign)
            ));
            return Command::FAILURE;
        }

        $schedule->setClinic($clinic);
        $this->em->flush();

        $io->success(sprintf('Schedule %s moved to clinic "%s".', $schedule->getUuid(), $clinic->getName()));

        return Command::SUCCESS;
    }

    /** @return int[] address ids referenced by the schedule that the clinic does not own */
    private function sessionsOutsideClinic(array $daySchedule, \App\Doctor\Entity\Doctor $doctor, int $clinicId): array
    {
        $owned = [];
        foreach ($this->addressRepo->findForContext($doctor, $clinicId) as $address) {
            $owned[(int) $address->getId()] = true;
        }

        $foreign = [];
        foreach ($daySchedule as $day) {
            foreach (($day['sessions'] ?? []) as $session) {
                $id = (int) ($session['location_id'] ?? 0);
                if ($id > 0 && !isset($owned[$id])) {
                    $foreign[$id] = true;
                }
            }
        }

        return array_keys($foreign);
    }
}
