<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Command;

use Doctrine\ORM\EntityManagerInterface;
use Ferienpass\CoreBundle\Entity\Tenant;
use Ferienpass\CoreBundle\Entity\TenantDomain;
use Ferienpass\CoreBundle\Repository\TenantRepository;
use Ferienpass\CoreBundle\Tenant\Surface;
use Ferienpass\CoreBundle\Tenant\TenantTables;
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;
use Symfony\Component\Validator\Validator\ValidatorInterface;

/**
 * Creates a tenant from the command line.
 *
 * This exists because the admin UI cannot bootstrap itself: with multi-tenancy
 * enabled and no tenants yet, every host 404s, including the one you would use
 * to add the first tenant.
 */
#[AsCommand(name: 'ferienpass:tenant:create', description: 'Creates a tenant')]
class TenantCreateCommand extends Command
{
    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly TenantRepository $tenants,
        private readonly ValidatorInterface $validator,
        private readonly TenantTables $tenantTables,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addArgument('slug', InputArgument::REQUIRED, 'Machine name, e.g. "nordhorn"')
            ->addArgument('name', InputArgument::REQUIRED, 'Display name, e.g. "Ferienpass Nordhorn"')
            ->addOption('host', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A host that serves this tenant\'s public site — and its admin under /admin, unless --admin-host is given (repeatable)')
            ->addOption('admin-host', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A host that serves this tenant\'s admin at its root, with no /admin segment. Given one, --host domains stop serving the admin (repeatable)')
            ->addOption('adopt-orphans', null, InputOption::VALUE_NONE, 'Assign all existing rows that have no tenant yet to this tenant. Use once, when converting a single-tenant installation.')
        ;
    }

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

        $slug = (string) $input->getArgument('slug');

        if (null !== $this->tenants->findOneBySlug($slug)) {
            $io->error(\sprintf('A tenant with the slug "%s" already exists.', $slug));

            return Command::FAILURE;
        }

        $tenant = new Tenant();
        $tenant->setSlug($slug);
        $tenant->setName((string) $input->getArgument('name'));

        foreach ($input->getOption('host') as $host) {
            $tenant->addDomain(new TenantDomain($tenant, $host, Surface::Frontend));
        }

        foreach ($input->getOption('admin-host') as $host) {
            $tenant->addDomain(new TenantDomain($tenant, $host, Surface::Admin));
        }

        $violations = $this->validator->validate($tenant);
        if ($violations->count() > 0) {
            $io->error(array_map(static fn ($v): string => \sprintf('%s: %s', $v->getPropertyPath(), $v->getMessage()), iterator_to_array($violations)));

            return Command::FAILURE;
        }

        $this->entityManager->persist($tenant);
        $this->entityManager->flush();

        $io->success(\sprintf('Created the tenant "%s".', $tenant->getSlug()));

        if ($input->getOption('adopt-orphans')) {
            if (!$io->confirm(\sprintf('Assign every row that has no tenant yet to "%s"? This cannot be undone automatically.', $tenant->getSlug()), false)) {
                return Command::SUCCESS;
            }

            $adopted = $this->tenantTables->adoptOrphans($tenant);

            $io->table(['Table', 'Rows adopted'], array_map(null, array_keys($adopted), array_values($adopted)));
            $io->success(\sprintf('Adopted %d rows.', array_sum($adopted)));
        }

        if (!$tenant->hasFrontend()) {
            // No public site means no page tree to point at, so the root-page
            // step below does not apply to this one.
            $io->note('This tenant has no public site: it serves the admin only, and every frontend route is unreachable for it.');

            return Command::SUCCESS;
        }

        // Contao has no idea tenants exist, so the page tree has to be told
        // where this one's website begins — and it is not something a console
        // command can guess.
        $io->note(\sprintf('Next: create a Contao root page for "%s" and select the tenant on it. Its domain must be one of the tenant\'s public-site hosts, and no other root page may claim the tenant.', $tenant->getSlug()));

        return Command::SUCCESS;
    }
}
