<?php

namespace App\Secretary\Service;

use App\Auth\Entity\User;
use App\Auth\Repository\UserRepository;
use App\Clinic\Entity\Clinic;
use App\Doctor\Entity\Doctor;
use App\Doctor\Repository\DoctorRepository;
use App\Secretary\Entity\DoctorSecretary;
use App\Secretary\Repository\DoctorSecretaryRepository;
use App\Sms\Entity\SmsLog;
use App\Sms\Service\SmsService;
use App\Subscription\Service\SubscriptionService;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;

/**
 * Secretary assignment logic shared by single- and multi-doctor flows.
 *
 * A secretary is a User linked to one or more doctors via DoctorSecretary rows
 * (one row per doctor). Clinic-owned assignment lets a clinic manager attach the
 * same secretary to several of the clinic's doctors at once and later re-sync
 * that set; the secretary's access is scoped to exactly those rows.
 */
class SecretaryService
{
    public function __construct(
        private readonly DoctorSecretaryRepository   $secretaryRepo,
        private readonly DoctorRepository            $doctorRepo,
        private readonly UserRepository              $userRepo,
        private readonly UserPasswordHasherInterface $hasher,
        private readonly SubscriptionService         $subscriptionService,
        private readonly SmsService                  $smsService,
        private readonly string                      $appUrl,
    ) {}

    /** Find the secretary User by mobile or create it; ensure ROLE_SECRETARY, apply name/password. */
    public function resolveSecretaryUser(string $mobile, ?string $name = null, ?string $password = null): User
    {
        $user = $this->userRepo->findByMobile($mobile);
        if ($user === null) {
            $user = new User($mobile);
            if (!empty($password)) {
                $user->setPasswordHash($this->hasher->hashPassword($user, $password));
            }
        }
        if (!empty($name)) {
            $user->setRealName(trim($name));
        }

        $roles = $user->getRoles();
        if (!in_array('ROLE_SECRETARY', $roles, true)) {
            $roles[] = 'ROLE_SECRETARY';
            $user->setRoles(array_values(array_unique($roles)));
        }
        $this->userRepo->save($user);

        return $user;
    }

    public function doctorAtSecretaryLimit(Doctor $doctor): bool
    {
        $limit = $this->subscriptionService->getSecretaryLimit('doctor', $doctor->getId());

        return $this->secretaryRepo->countActiveByDoctor($doctor) >= $limit;
    }

    /**
     * Assign a secretary (by mobile) to several clinic doctors atomically.
     *
     * @param string[] $doctorUuids
     * @return array{secretary: User, created: DoctorSecretary[], skipped_duplicate: string[], skipped_limit: string[], skipped_not_in_clinic: string[]}
     */
    public function assignToClinicDoctors(Clinic $clinic, string $mobile, array $doctorUuids, array $meta = []): array
    {
        $secretary = $this->resolveSecretaryUser($mobile, $meta['name'] ?? null, $meta['password'] ?? null);

        $created = $skippedDup = $skippedLimit = $skippedNotInClinic = [];

        foreach (array_unique($doctorUuids) as $doctorUuid) {
            $doctor = $this->doctorRepo->findByUuid($doctorUuid);
            if ($doctor === null || !$this->secretaryRepo->isDoctorInClinic($doctor, $clinic)) {
                $skippedNotInClinic[] = $doctorUuid;
                continue;
            }

            $existing = $this->secretaryRepo->findOneBy([
                'doctor'    => $doctor,
                'secretary' => $secretary,
                'entityType' => DoctorSecretary::OWNER_CLINIC,
            ]);
            if ($existing !== null) {
                if ($existing->isActive()) {
                    $skippedDup[] = $doctorUuid;
                    continue;
                }
                $existing->setActive(true);
                $this->applyMeta($existing, $meta);
                $created[] = $existing;
                continue;
            }

            if ($this->doctorAtSecretaryLimit($doctor)) {
                $skippedLimit[] = $doctorUuid;
                continue;
            }
            $row = new DoctorSecretary($doctor, $secretary, $clinic);
            $this->applyMeta($row, $meta);
            $this->secretaryRepo->save($row, false);
            $created[] = $row;
        }

        $this->secretaryRepo->getEntityManager()->flush(); // flush the batch of new rows

        if (!empty($created)) {
            $this->sendWelcomeSms($mobile, $clinic->getName() ?? 'کلینیک');
        }

        return [
            'secretary'             => $secretary,
            'created'               => $created,
            'skipped_duplicate'     => $skippedDup,
            'skipped_limit'         => $skippedLimit,
            'skipped_not_in_clinic' => $skippedNotInClinic,
        ];
    }

    /**
     * Re-sync a clinic secretary's assigned doctors to exactly $doctorUuids:
     * activate/create the wanted set, deactivate the rest.
     *
     * @param string[] $doctorUuids
     * @return array{added: DoctorSecretary[], removed: DoctorSecretary[], skipped_limit: string[], skipped_not_in_clinic: string[]}
     */
    public function syncClinicDoctors(Clinic $clinic, User $secretary, array $doctorUuids): array
    {
        $wanted   = array_values(array_unique($doctorUuids));
        $existing  = $this->secretaryRepo->findByClinicAndSecretary($clinic, $secretary);
        $byDoctorUuid = [];
        foreach ($existing as $row) {
            $byDoctorUuid[$row->getDoctor()->getUuid()] = $row;
        }

        $added = $removed = $skippedLimit = $skippedNotInClinic = [];
        // New links inherit the person's existing profile/permissions so that a
        // multi-doctor secretary stays a single consistent record.
        $template = $existing[0] ?? null;

        foreach ($wanted as $doctorUuid) {
            $doctor = $this->doctorRepo->findByUuid($doctorUuid);
            if ($doctor === null || !$this->secretaryRepo->isDoctorInClinic($doctor, $clinic)) {
                $skippedNotInClinic[] = $doctorUuid;
                continue;
            }
            $current = $byDoctorUuid[$doctorUuid] ?? null;
            if ($current !== null) {
                if (!$current->isActive()) {
                    $current->setActive(true);
                    $added[] = $current;
                }
                continue;
            }
            if ($this->doctorAtSecretaryLimit($doctor)) {
                $skippedLimit[] = $doctorUuid;
                continue;
            }
            $row = new DoctorSecretary($doctor, $secretary, $clinic);
            if ($template !== null) {
                $row->setNationalCode($template->getNationalCode())
                    ->setAddress($template->getAddress())
                    ->setPermissions($template->getPermissions());
            }
            $this->secretaryRepo->save($row, false);
            $added[] = $row;
        }

        foreach ($existing as $row) {
            if ($row->isActive() && !in_array($row->getDoctor()->getUuid(), $wanted, true)) {
                $row->setActive(false);
                $removed[] = $row;
            }
        }

        $this->secretaryRepo->getEntityManager()->flush();

        return [
            'added'                 => $added,
            'removed'               => $removed,
            'skipped_limit'         => $skippedLimit,
            'skipped_not_in_clinic' => $skippedNotInClinic,
        ];
    }

    private function applyMeta(DoctorSecretary $row, array $meta): void
    {
        if (array_key_exists('national_code', $meta)) {
            $row->setNationalCode($meta['national_code'] !== null ? trim((string) $meta['national_code']) : null);
        }
        if (array_key_exists('address', $meta)) {
            $row->setAddress($meta['address'] !== null ? trim((string) $meta['address']) : null);
        }
        if (!empty($meta['permissions'])) {
            $row->mergePermissions($meta['permissions']);
        }
    }

    private function sendWelcomeSms(string $mobile, string $ownerName): void
    {
        $this->smsService->dispatchTemplate(SmsLog::TAG_SECRETARY, $mobile, [
            'owner'    => $ownerName,
            'username' => $mobile,
            'link'     => rtrim($this->appUrl, '/') . '/login',
        ]);
    }
}
