<?php

namespace App\Command;

use App\Service\AwsSesService;
use App\Service\DnsCheckService;
use App\Service\DnsInfraHelper;
use App\Service\MailcowService;
use App\Service\MailerService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Twig\Environment;

#[AsCommand(
    name: 'app:dns:check',
    description: 'Verifie la configuration DNS, AWS SES, Cloudflare et Mailcow pour les domaines E-Cosplay',
)]
class CheckDnsCommand extends Command
{
    private const MONITOR_EMAIL = 'notification@e-cosplay.fr';

    public function __construct(
        private DnsCheckService $dnsCheck,
        private AwsSesService $awsSes,
        private MailcowService $mailcow,
        private MailerService $mailer,
        private Environment $twig,
        private HttpClientInterface $httpClient,
        private DnsInfraHelper $helper,
        #[Autowire('%kernel.environment%')] private string $appEnv,
        #[Autowire(env: 'DISCORD_WEBHOOK')] private string $discordWebhook = '',
    ) {
        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $io->title('Verification DNS - CRM E-Cosplay');

        $errors = [];
        $warnings = [];
        $successes = [];
        $domainResults = [];

        $cfRecordsByDomain = $this->loadCloudflareRecords($io);

        foreach (DnsInfraHelper::DOMAINS as $domain) {
            $io->section('Domaine : '.$domain);
            $checks = [];
            $cfRecords = $cfRecordsByDomain[$domain] ?? [];

            $this->dnsCheck->checkSpf($domain, $checks, $errors, $warnings, $successes);
            $this->helper->enrichWithCloudflare($checks, $domain, 'SPF', 'TXT', $cfRecords);

            $this->dnsCheck->checkDmarc($domain, $checks, $errors, $successes);
            $this->helper->enrichWithCloudflare($checks, '_dmarc.'.$domain, 'DMARC', 'TXT', $cfRecords);

            $this->dnsCheck->checkMx($domain, DnsInfraHelper::EXPECTED_MX[$domain], $checks, $errors, $successes);
            $this->helper->enrichWithCloudflare($checks, $domain, 'MX', 'MX', $cfRecords);

            $this->dnsCheck->checkBounce($domain, $checks, $errors, $warnings, $successes);
            $this->helper->enrichWithCloudflare($checks, 'bounce.'.$domain, 'Bounce', 'MX', $cfRecords);

            $this->dnsCheck->checkWhois($domain, $checks, $errors, $warnings, $successes);

            $this->checkAwsSes($domain, $checks, $errors, $successes, $cfRecords);
            $this->checkMailcow($domain, $checks, $errors, $warnings, $successes, $cfRecords);

            foreach ($checks as $check) {
                $icon = match ($check['status']) {
                    'ok' => '<fg=green>OK</>',
                    'error' => '<fg=red>ERREUR</>',
                    default => '<fg=yellow>ATTENTION</>',
                };
                $io->text("  [{$check['type']}] $icon {$check['label']}");
            }

            $domainResults[] = ['domain' => $domain, 'checks' => $checks];
        }

        $io->newLine();
        $this->sendReport($errors, $warnings, $successes, $domainResults);

        if ([] !== $errors) {
            $io->error(\count($errors).' erreur(s). Rapport envoye a '.self::MONITOR_EMAIL);

            return Command::FAILURE;
        }

        if ([] !== $warnings) {
            $io->warning(\count($warnings).' avertissement(s). Rapport envoye a '.self::MONITOR_EMAIL);

            return Command::SUCCESS;
        }

        $io->success('Tous les DNS sont OK. Rapport envoye a '.self::MONITOR_EMAIL);

        return Command::SUCCESS;
    }

    /**
     * @return array<string, list<array<string, mixed>>>
     */
    private function loadCloudflareRecords(SymfonyStyle $io): array
    {
        $result = $this->helper->loadCloudflareRecords();

        if ([] === $result) {
            $io->text('  Cloudflare API non disponible, colonnes CF vides.');
        }

        return $result;
    }

    /**
     * @param list<array>                $checks
     * @param array<int, string>         $errors
     * @param array<int, string>         $successes
     * @param list<array<string, mixed>> $cfRecords
     */
    private function checkAwsSes(string $domain, array &$checks, array &$errors, array &$successes, array $cfRecords = []): void
    {
        $ses = DnsInfraHelper::LABEL_AWS_SES;

        if (!$this->awsSes->isAvailable()) {
            $checks[] = DnsCheckService::check($ses, 'API', 'warning', 'Cles non configurees', 'Acces API SES', 'N/A');

            return;
        }

        try {
            $this->checkSesDomain($domain, $checks, $errors, $successes);
            $this->checkSesDkim($domain, $checks, $errors, $successes, $cfRecords);
            $this->checkSesMailFrom($domain, $checks, $errors, $successes, $cfRecords);
            $this->checkSesBounce($domain, $checks);
        } catch (\Throwable $e) {
            $errors[] = "[$domain] $ses : ".$e->getMessage();
            $checks[] = DnsCheckService::check($ses, 'API', 'error', $e->getMessage());
        }
    }

    /**
     * @param list<array>        $checks
     * @param array<int, string> $errors
     * @param array<int, string> $successes
     */
    private function checkSesDomain(string $domain, array &$checks, array &$errors, array &$successes): void
    {
        $ses = DnsInfraHelper::LABEL_AWS_SES;
        $verif = $this->awsSes->isDomainVerified($domain);
        $ok = 'Success' === $verif;

        $checks[] = DnsCheckService::check($ses, 'Domaine', $ok ? 'ok' : 'error', $verif ?? 'Non verifie', 'Success', $verif ?? 'Absent');
        $ok ? $successes[] = "[$domain] $ses : domaine verifie" : $errors[] = "[$domain] $ses : domaine non verifie ($verif)";
    }

    /**
     * @param list<array>                $checks
     * @param array<int, string>         $errors
     * @param array<int, string>         $successes
     * @param list<array<string, mixed>> $cfRecords
     */
    private function checkSesDkim(string $domain, array &$checks, array &$errors, array &$successes, array $cfRecords): void
    {
        $ses = DnsInfraHelper::LABEL_AWS_SES;
        $dkim = $this->awsSes->getDkimStatus($domain);
        $dkimOk = $dkim['enabled'] && $dkim['verified'];

        $checks[] = DnsCheckService::check(
            $ses, 'DKIM statut', $dkimOk ? 'ok' : 'error',
            $dkimOk ? 'Active et verifiee' : 'Non active ou non verifiee',
            'Enabled=oui, Verified=oui',
            'Enabled='.($dkim['enabled'] ? 'oui' : 'non').', Verified='.($dkim['verified'] ? 'oui' : 'non')
        );
        $dkimOk ? $successes[] = "[$domain] $ses DKIM : active et verifiee" : $errors[] = "[$domain] $ses DKIM : non active ou non verifiee";

        foreach ($dkim['tokens'] as $token) {
            $expectedCname = $token.'.dkim.amazonses.com';
            $dkimFqdn = $token.'._domainkey.'.$domain;
            $actualCname = $this->dnsCheck->getCnameRecord($dkimFqdn);
            $found = null !== $actualCname && str_contains($actualCname, 'dkim.amazonses.com');

            $checks[] = DnsCheckService::check($ses, 'DKIM CNAME '.$token, $found ? 'ok' : 'error', $found ? 'Present' : 'Absent', $expectedCname, $actualCname ?? DnsInfraHelper::NOT_FOUND);
            $this->helper->enrichLastCheck($checks, $dkimFqdn, 'CNAME', $cfRecords);
            $found ? $successes[] = "[$domain] $ses DKIM CNAME $token : OK" : $errors[] = "[$domain] $ses DKIM CNAME $token : absent (attendu: $dkimFqdn CNAME $expectedCname)";
        }
    }

    /**
     * @param list<array>                $checks
     * @param array<int, string>         $errors
     * @param array<int, string>         $successes
     * @param list<array<string, mixed>> $cfRecords
     */
    private function checkSesMailFrom(string $domain, array &$checks, array &$errors, array &$successes, array $cfRecords): void
    {
        $ses = DnsInfraHelper::LABEL_AWS_SES;
        $mailFrom = $this->awsSes->getMailFromStatus($domain);
        $mfd = $mailFrom['mail_from_domain'];

        if (null === $mfd) {
            $checks[] = DnsCheckService::check($ses, 'MAIL FROM', 'warning', DnsInfraHelper::NOT_CONFIGURED, 'bounce.'.$domain, 'N/A');

            return;
        }

        $status = $mailFrom['mail_from_status'];
        $statusOk = 'Success' === $status;
        $checks[] = DnsCheckService::check($ses, 'MAIL FROM', $statusOk ? 'ok' : 'error', $status ?? 'Inconnu', $mfd.' (statut: Success)', $mfd.' (statut: '.($status ?? '?').')');
        $statusOk ? $successes[] = "[$domain] $ses MAIL FROM : $mfd verifie" : $errors[] = "[$domain] $ses MAIL FROM : $mfd statut $status";

        $this->checkSesMailFromMx($domain, $mfd, $mailFrom, $checks, $errors, $successes, $cfRecords);
        $this->checkSesMailFromTxt($domain, $mfd, $mailFrom, $checks, $errors, $successes, $cfRecords);
    }

    /**
     * @param array<string, mixed>       $mailFrom
     * @param list<array>                $checks
     * @param array<int, string>         $errors
     * @param array<int, string>         $successes
     * @param list<array<string, mixed>> $cfRecords
     */
    private function checkSesMailFromMx(string $domain, string $mfd, array $mailFrom, array &$checks, array &$errors, array &$successes, array $cfRecords): void
    {
        if (null === $mailFrom['mx_expected']) {
            return;
        }

        $ses = DnsInfraHelper::LABEL_AWS_SES;
        $mxFound = $this->helper->checkMxExists($mfd, $mailFrom['mx_expected']);
        $actualMx = $this->helper->getMxValues($mfd);
        $checks[] = DnsCheckService::check($ses, 'MAIL FROM MX', $mxFound ? 'ok' : 'error', $mxFound ? 'Present' : 'Absent', "$mfd MX {$mailFrom['mx_expected']}", $actualMx ?: DnsInfraHelper::NOT_FOUND);
        $this->helper->enrichLastCheck($checks, $mfd, 'MX', $cfRecords);
        $mxFound ? $successes[] = "[$domain] $ses MAIL FROM MX : OK" : $errors[] = "[$domain] $ses MAIL FROM MX absent (attendu: $mfd MX {$mailFrom['mx_expected']})";
    }

    /**
     * @param array<string, mixed>       $mailFrom
     * @param list<array>                $checks
     * @param array<int, string>         $errors
     * @param array<int, string>         $successes
     * @param list<array<string, mixed>> $cfRecords
     */
    private function checkSesMailFromTxt(string $domain, string $mfd, array $mailFrom, array &$checks, array &$errors, array &$successes, array $cfRecords): void
    {
        if (null === $mailFrom['txt_expected']) {
            return;
        }

        $ses = DnsInfraHelper::LABEL_AWS_SES;
        $txtFound = $this->helper->checkTxtContains($mfd, 'v=spf1');
        $actualTxt = $this->helper->getTxtSpfValue($mfd);
        $checks[] = DnsCheckService::check($ses, 'MAIL FROM TXT', $txtFound ? 'ok' : 'error', $txtFound ? 'Present' : 'Absent', "$mfd TXT {$mailFrom['txt_expected']}", $actualTxt ?: DnsInfraHelper::NOT_FOUND);
        $this->helper->enrichLastCheck($checks, $mfd, 'TXT', $cfRecords);
        $txtFound ? $successes[] = "[$domain] $ses MAIL FROM SPF : OK" : $errors[] = "[$domain] $ses MAIL FROM SPF absent (attendu: $mfd TXT {$mailFrom['txt_expected']})";
    }

    /** @param list<array> $checks */
    private function checkSesBounce(string $domain, array &$checks): void
    {
        $ses = DnsInfraHelper::LABEL_AWS_SES;
        $notif = $this->awsSes->getNotificationStatus($domain);
        $bounceOk = $notif['forwarding'] || null !== $notif['bounce_topic'];
        $detail = $notif['forwarding'] ? 'Forwarding actif' : ($notif['bounce_topic'] ?? DnsInfraHelper::NOT_CONFIGURED);
        $checks[] = DnsCheckService::check($ses, 'Bounce notif', $bounceOk ? 'ok' : 'warning', $detail, 'Forwarding ou SNS topic', $detail);
    }

    /**
     * @param list<array>                $checks
     * @param array<int, string>         $errors
     * @param array<int, string>         $warnings
     * @param array<int, string>         $successes
     * @param list<array<string, mixed>> $cfRecords
     */
    private function checkMailcow(string $domain, array &$checks, array &$errors, array &$warnings, array &$successes, array $cfRecords = []): void
    {
        $mc = DnsInfraHelper::LABEL_MAILCOW;

        if (!$this->mailcow->isAvailable()) {
            $checks[] = DnsCheckService::check($mc, 'API', 'warning', 'Non disponible', 'Acces API Mailcow', 'N/A');

            return;
        }

        try {
            $info = $this->mailcow->getDomainStatus($domain);
            if (null === $info) {
                $warnings[] = "[$domain] $mc : domaine non configure";
                $checks[] = DnsCheckService::check($mc, 'Domaine', 'warning', DnsInfraHelper::NOT_CONFIGURED, 'Domaine actif', 'Absent');

                return;
            }

            $this->checkMailcowDomain($domain, $info, $checks, $errors, $successes);
            $this->checkMailcowDnsRecords($domain, $checks, $errors, $warnings, $successes, $cfRecords);
        } catch (\Throwable $e) {
            $errors[] = "[$domain] $mc : ".$e->getMessage();
            $checks[] = DnsCheckService::check($mc, 'API', 'error', $e->getMessage());
        }
    }

    /**
     * @param array<string, mixed> $info
     * @param list<array>          $checks
     * @param array<int, string>   $errors
     * @param array<int, string>   $successes
     */
    private function checkMailcowDomain(string $domain, array $info, array &$checks, array &$errors, array &$successes): void
    {
        $mc = DnsInfraHelper::LABEL_MAILCOW;
        $active = $info['active'];
        $checks[] = DnsCheckService::check(
            $mc, 'Domaine', $active ? 'ok' : 'error',
            $active ? "Actif, {$info['mailboxes']} boite(s)" : 'Desactive',
            'Actif', $active ? "Actif ({$info['mailboxes']} boites)" : 'Desactive'
        );
        $active ? $successes[] = "[$domain] $mc : actif, {$info['mailboxes']} boite(s)" : $errors[] = "[$domain] $mc : desactive";
    }

    /**
     * @param list<array>                $checks
     * @param array<int, string>         $errors
     * @param array<int, string>         $warnings
     * @param array<int, string>         $successes
     * @param list<array<string, mixed>> $cfRecords
     */
    private function checkMailcowDnsRecords(string $domain, array &$checks, array &$errors, array &$warnings, array &$successes, array $cfRecords): void
    {
        $mcDns = DnsInfraHelper::LABEL_MAILCOW_DNS;

        foreach ($this->mailcow->getExpectedDnsRecords($domain) as $expected) {
            $found = $this->helper->checkDnsRecordExists($expected['type'], $expected['name'], $expected['content']);
            $isOptional = $expected['optional'];
            $label = $expected['type'].' '.$expected['name'];
            $digValue = $this->helper->getActualDnsValue($expected['type'], $expected['name']);
            $status = $found ? 'ok' : ($isOptional ? 'warning' : 'error');
            $detail = $found ? 'Present' : ($isOptional ? 'Absent (optionnel)' : 'Absent');

            $checks[] = DnsCheckService::check($mcDns, $label, $status, $detail, $expected['content'], $digValue ?: DnsInfraHelper::NOT_FOUND);
            $this->helper->enrichLastCheck($checks, $expected['name'], $expected['type'], $cfRecords);

            if ($found) {
                $successes[] = "[$domain] $mcDns : $label OK";
            } elseif ($isOptional) {
                $warnings[] = "[$domain] $mcDns : $label absent (optionnel)";
            } else {
                $errors[] = "[$domain] $mcDns : $label absent";
            }
        }
    }

    /**
     * @param array<int, string>                               $errors
     * @param array<int, string>                               $warnings
     * @param array<int, string>                               $successes
     * @param list<array{domain: string, checks: list<array>}> $domainResults
     */
    private function sendReport(array $errors, array $warnings, array $successes, array $domainResults): void
    {
        $hasErrors = [] !== $errors;
        $hasWarnings = [] !== $warnings;

        if ($hasErrors) {
            $subject = 'CRM E-Cosplay - Alerte DNS : '.\count($errors).' erreur(s)';
            $statusColor = '#dc2626';
            $statusText = \count($errors).' ERREUR(S) DETECTEE(S)';
        } elseif ($hasWarnings) {
            $subject = 'CRM E-Cosplay - DNS : '.\count($warnings).' avertissement(s)';
            $statusColor = '#f59e0b';
            $statusText = \count($warnings).' AVERTISSEMENT(S)';
        } else {
            $subject = 'CRM E-Cosplay - DNS : configuration OK';
            $statusColor = '#16a34a';
            $statusText = 'TOUTES LES VERIFICATIONS OK';
        }

        $html = $this->twig->render('emails/dns_report.html.twig', [
            'statusColor' => $statusColor,
            'statusText' => $statusText,
            'date' => new \DateTimeImmutable(),
            'domains' => DnsInfraHelper::DOMAINS,
            'domainResults' => $domainResults,
            'reportUrl' => '__DNS_REPORT_URL__',
        ]);

        $this->mailer->sendEmail(
            self::MONITOR_EMAIL,
            $subject,
            $html,
            null,
            null,
            false,
            null,
            1, // Priority HIGH
        );

        // Notification Discord uniquement en prod avec webhook configure
        if ('prod' === $this->appEnv && '' !== $this->discordWebhook) {
            $this->sendDiscordNotification($errors, $warnings, $successes);
        }
    }

    /**
     * @param array<int, string> $errors
     * @param array<int, string> $warnings
     * @param array<int, string> $successes
     */
    private function sendDiscordNotification(array $errors, array $warnings, array $successes): void
    {
        $hasErrors = [] !== $errors;
        $hasWarnings = [] !== $warnings;

        if ($hasErrors) {
            $color = 0xDC2626;
            $title = 'ALERTE DNS - '.\count($errors).' erreur(s)';
        } elseif ($hasWarnings) {
            $color = 0xF59E0B;
            $title = 'DNS - '.\count($warnings).' avertissement(s)';
        } else {
            $color = 0x16A34A;
            $title = 'DNS - Configuration OK';
        }

        $description = 'Domaines: **'.implode(', ', DnsInfraHelper::DOMAINS)."**\n\n";
        $description .= '**'.\count($successes)."** verification(s) OK\n";
        if ($hasErrors) {
            $description .= '**'.\count($errors)."** erreur(s)\n";
            foreach (array_slice($errors, 0, 5) as $e) {
                $description .= "> $e\n";
            }
            if (\count($errors) > 5) {
                $description .= '> ... et '.(\count($errors) - 5)." autres\n";
            }
        }
        if ($hasWarnings) {
            $description .= '**'.\count($warnings)."** avertissement(s)\n";
        }

        try {
            $this->httpClient->request('POST', $this->discordWebhook, [
                'json' => [
                    'embeds' => [[
                        'title' => 'E-Infra : '.$title,
                        'description' => $description,
                        'color' => $color,
                        'footer' => ['text' => 'E-Infra - Monitoring DNS'],
                        'timestamp' => (new \DateTimeImmutable())->format('c'),
                    ]],
                ],
            ]);
        } catch (\Throwable) {
        }
    }
}
