<?php

namespace Mautic\CoreBundle\Command;

use Doctrine\ORM\EntityManager;
use Mautic\CoreBundle\IpLookup\DoNotSellList\MaxMindDoNotSellList;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\LeadRepository;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * CLI Command to purge data from Mautic that appears on the
 * MaxMind Do Not Sell list.
 */
#[AsCommand(
    name: 'mautic:max-mind:purge',
    description: 'Purge data connected to MaxMind Do Not Sell list.'
)]
class MaxMindDoNotSellPurgeCommand extends Command
{
    public function __construct(
        private EntityManager $em,
        private LeadRepository $leadRepository,
        private MaxMindDoNotSellList $doNotSellList,
    ) {
        parent::__construct();
    }

    protected function configure()
    {
        $this
            ->addOption(
                'dry-run',
                'd',
                InputOption::VALUE_NONE,
                'Get a list of data that will be purged.'
            )
            ->setHelp(<<<'EOT'
The <info>%command.name%</info> command will purge all data from Mautic which is related to any IP found on the MaxMind Do Not Sell List.

<info>php %command.full_name% --dry-run</info>

Performs a dry-run which will not actually purge any data, but will produce a list of what would be purged.

<info>php %command.full_name% --batch-size</info>

Set the number of records to return in a batch when processing the Do Not Sell List. This option is ignored if IPs are passed as an argument.
EOT
            );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        try {
            $dryRun = $input->getOption('dry-run');

            $output->writeln('<info>Step 1: Searching for contacts with data from Do Not Sell List...</info>');

            $this->doNotSellList->loadList();
            $doNotSellListIPs = array_map(fn ($item): string =>
                // strip subnet mask characters
                $this->doNotSellList->stripCIDR($item['value']), $this->doNotSellList->getList());
            $doNotSellContacts = $this->findContactsFromIPs($doNotSellListIPs);

            if (0 == count($doNotSellContacts)) {
                $output->writeln('<info>No matches found.</info>');

                return Command::SUCCESS;
            }

            $output->writeln('Found '.count($doNotSellContacts)." contacts with an IP from the Do Not Sell list.\n");

            if ($dryRun) {
                $output->writeln('<info>Dry run; skipping purge.</info>');

                return Command::SUCCESS;
            }

            $output->writeln('<info>Step 2: Purging data...</info>');
            $purgeProgress = new ProgressBar($output, count($doNotSellContacts));

            foreach ($doNotSellContacts as $contact) {
                $this->purgeData($contact['id'], $contact['ip_address']);
                $purgeProgress->advance(1);
            }

            $purgeProgress->finish();
            $output->writeln("\n<info>Purge complete.</info>\n");

            return Command::SUCCESS;
        } catch (\Exception $e) {
            $output->writeln("\n<error>".$e->getMessage().'</error>');

            return Command::FAILURE;
        }
    }

    private function findContactsFromIPs(array $ips): array
    {
        $in  = "'".implode("','", $ips)."'";
        $sql =
            'SELECT x.lead_id AS id, ip.ip_address AS ip_address '.
             'FROM '.MAUTIC_TABLE_PREFIX.'lead_ips_xref x '.
             'JOIN '.MAUTIC_TABLE_PREFIX.'ip_addresses ip ON x.ip_id = ip.id '.
             'WHERE ip.ip_address IN ('.$in.')';

        $conn   = $this->em->getConnection();
        $stmt   = $conn->prepare($sql);
        $result = $stmt->executeQuery();

        return $result->fetchAllAssociative();
    }

    private function purgeData(string $contactId, string $ip): void
    {
        /** @var Lead $lead */
        $lead       = $this->leadRepository->findOneBy(['id' => $contactId]);
        $matchedIps = array_filter($lead->getIpAddresses()->getValues(), fn ($item): bool => $item->getIpAddress() == $ip);

        if (!$matchedIps) {
            return;
        }

        // We only purge data from the contact if it matches the data in the IP details
        if ($ipDetails = $matchedIps[0]->getIpDetails()) {
            return;
        }

        $changed = false;
        if (($ipDetails['city'] ?? '') == $lead->getCity()) {
            $lead->setCity(null);
            $changed = true;
        }
        if (($ipDetails['region'] ?? '') == $lead->getState()) {
            $lead->setState(null);
            $changed = true;
        }
        if (($ipDetails['country'] ?? '') == $lead->getCountry()) {
            $lead->setCountry(null);
            $changed = true;
        }
        if (($ipDetails['zipcode'] ?? '') == $lead->getZipcode()) {
            $lead->setZipcode(null);
            $changed = true;
        }

        if ($changed) {
            $this->leadRepository->saveEntity($lead);
        }
    }
}
