<?php

namespace App\Service;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

/**
 * @codeCoverageIgnore Wrapper base de donnees mail externe (connexion DBAL directe)
 */
class EsyMailService
{
    private const DATETIME_FORMAT = 'Y-m-d H:i:sP';

    private ?Connection $conn = null;

    public function __construct(
        private LoggerInterface $logger,
        #[Autowire(env: 'ESYMAIL_DATABASE_URL')] private string $databaseUrl = '',
        #[Autowire(env: 'ESYMAIL_HOSTNAME')] private string $mailHostname = '',
    ) {
    }

    public function isAvailable(): bool
    {
        return '' !== $this->databaseUrl;
    }

    public function getMailHostname(): string
    {
        return $this->mailHostname;
    }

    // ──── Domaines ────────────────────────────────────

    /**
     * @return list<array{id: int, name: string, max_mailboxes: int, default_quota_mb: int, is_active: bool, created_at: string, mailbox_count: int}>
     */
    public function listDomains(): array
    {
        if (!$this->isAvailable()) {
            return [];
        }

        return $this->getConnection()->fetchAllAssociative(
            'SELECT d.*, (SELECT COUNT(*) FROM mailbox m WHERE m.domain = d.name) AS mailbox_count FROM domain d ORDER BY d.name'
        );
    }

    /**
     * @return array<string, mixed>|null
     */
    public function getDomain(string $name): ?array
    {
        if (!$this->isAvailable()) {
            return null;
        }

        $result = $this->getConnection()->fetchAssociative('SELECT * FROM domain WHERE name = ?', [$name]);

        return false === $result ? null : $result;
    }

    public function createDomain(string $name, int $maxMailboxes = 50, int $defaultQuotaMb = 5120): bool
    {
        if (!$this->isAvailable()) {
            return false;
        }

        try {
            $this->getConnection()->insert('domain', [
                'name' => strtolower(trim($name)),
                'max_mailboxes' => $maxMailboxes,
                'default_quota_mb' => $defaultQuotaMb,
                'is_active' => true,
                'created_at' => (new \DateTimeImmutable())->format(self::DATETIME_FORMAT),
            ], ['is_active' => 'boolean']);

            $this->logger->info('EsyMail: domaine cree: '.$name);

            return true;
        } catch (\Throwable $e) {
            $this->logger->error('EsyMail: erreur creation domaine '.$name.': '.$e->getMessage());

            return false;
        }
    }

    public function updateDomain(string $name, int $maxMailboxes, int $defaultQuotaMb, bool $isActive): bool
    {
        if (!$this->isAvailable()) {
            return false;
        }

        try {
            $this->getConnection()->update('domain', [
                'max_mailboxes' => $maxMailboxes,
                'default_quota_mb' => $defaultQuotaMb,
                'is_active' => $isActive,
            ], ['name' => $name], ['is_active' => 'boolean']);

            return true;
        } catch (\Throwable $e) {
            $this->logger->error('EsyMail: erreur update domaine '.$name.': '.$e->getMessage());

            return false;
        }
    }

    public function deleteDomain(string $name): bool
    {
        if (!$this->isAvailable()) {
            return false;
        }

        try {
            $this->getConnection()->delete('domain', ['name' => $name]);
            $this->logger->info('EsyMail: domaine supprime: '.$name);

            return true;
        } catch (\Throwable $e) {
            $this->logger->error('EsyMail: erreur suppression domaine '.$name.': '.$e->getMessage());

            return false;
        }
    }

    public function domainExists(string $name): bool
    {
        if (!$this->isAvailable()) {
            return false;
        }

        return (int) $this->getConnection()->fetchOne('SELECT COUNT(*) FROM domain WHERE name = ?', [strtolower($name)]) > 0;
    }

    // ──── Boîtes mail ─────────────────────────────────

    /**
     * @return list<array<string, mixed>>
     */
    public function listMailboxes(?string $domain = null): array
    {
        if (!$this->isAvailable()) {
            return [];
        }

        if (null !== $domain) {
            return $this->getConnection()->fetchAllAssociative(
                'SELECT * FROM mailbox WHERE domain = ? ORDER BY email', [$domain]
            );
        }

        return $this->getConnection()->fetchAllAssociative('SELECT * FROM mailbox ORDER BY domain, email');
    }

    public function createMailbox(string $email, string $password, ?string $displayName = null, int $quotaMb = 5120): bool
    {
        $parts = explode('@', $email);
        $domain = 2 === \count($parts) ? strtolower($parts[1]) : null;

        if (!$this->isAvailable() || null === $domain || !$this->domainExists($domain)) {
            if (null !== $domain && $this->isAvailable()) {
                $this->logger->error('EsyMail: domaine '.$domain.' inexistant pour '.$email);
            }

            return false;
        }

        try {
            $this->getConnection()->insert('mailbox', [
                'email' => strtolower(trim($email)),
                'password' => password_hash($password, \PASSWORD_BCRYPT),
                'domain' => $domain,
                'display_name' => $displayName,
                'quota_mb' => $quotaMb,
                'is_active' => true,
                'created_at' => (new \DateTimeImmutable())->format(self::DATETIME_FORMAT),
            ], ['is_active' => 'boolean']);

            $this->logger->info('EsyMail: boite creee: '.$email);

            return true;
        } catch (\Throwable $e) {
            $this->logger->error('EsyMail: erreur creation boite '.$email.': '.$e->getMessage());

            return false;
        }
    }

    public function updateMailbox(string $email, ?string $displayName, int $quotaMb, bool $isActive): bool
    {
        if (!$this->isAvailable()) {
            return false;
        }

        try {
            $this->getConnection()->update('mailbox', [
                'display_name' => $displayName,
                'quota_mb' => $quotaMb,
                'is_active' => $isActive,
                'updated_at' => (new \DateTimeImmutable())->format(self::DATETIME_FORMAT),
            ], ['email' => $email], ['is_active' => 'boolean']);

            return true;
        } catch (\Throwable $e) {
            $this->logger->error('EsyMail: erreur update boite '.$email.': '.$e->getMessage());

            return false;
        }
    }

    public function changePassword(string $email, string $newPassword): bool
    {
        if (!$this->isAvailable()) {
            return false;
        }

        try {
            $this->getConnection()->update('mailbox', [
                'password' => password_hash($newPassword, \PASSWORD_BCRYPT),
                'updated_at' => (new \DateTimeImmutable())->format(self::DATETIME_FORMAT),
            ], ['email' => $email]);

            $this->logger->info('EsyMail: password modifie pour '.$email);

            return true;
        } catch (\Throwable $e) {
            $this->logger->error('EsyMail: erreur changement password '.$email.': '.$e->getMessage());

            return false;
        }
    }

    public function deleteMailbox(string $email): bool
    {
        if (!$this->isAvailable()) {
            return false;
        }

        try {
            $this->getConnection()->delete('mailbox', ['email' => $email]);
            $this->logger->info('EsyMail: boite supprimee: '.$email);

            return true;
        } catch (\Throwable $e) {
            $this->logger->error('EsyMail: erreur suppression boite '.$email.': '.$e->getMessage());

            return false;
        }
    }

    public function mailboxExists(string $email): bool
    {
        if (!$this->isAvailable()) {
            return false;
        }

        return (int) $this->getConnection()->fetchOne('SELECT COUNT(*) FROM mailbox WHERE email = ?', [strtolower($email)]) > 0;
    }

    // ──── Alias ───────────────────────────────────────

    /**
     * @return list<array<string, mixed>>
     */
    public function listAliases(?string $domain = null): array
    {
        if (!$this->isAvailable()) {
            return [];
        }

        if (null !== $domain) {
            return $this->getConnection()->fetchAllAssociative(
                'SELECT * FROM alias WHERE domain = ? ORDER BY source', [$domain]
            );
        }

        return $this->getConnection()->fetchAllAssociative('SELECT * FROM alias ORDER BY domain, source');
    }

    public function createAlias(string $source, string $destination, string $domain): bool
    {
        if (!$this->isAvailable()) {
            return false;
        }

        try {
            $this->getConnection()->insert('alias', [
                'source' => strtolower(trim($source)),
                'destination' => strtolower(trim($destination)),
                'domain' => strtolower(trim($domain)),
                'is_active' => true,
                'created_at' => (new \DateTimeImmutable())->format(self::DATETIME_FORMAT),
            ], ['is_active' => 'boolean']);

            $this->logger->info('EsyMail: alias cree: '.$source.' -> '.$destination);

            return true;
        } catch (\Throwable $e) {
            $this->logger->error('EsyMail: erreur creation alias '.$source.': '.$e->getMessage());

            return false;
        }
    }

    public function deleteAlias(int $id): bool
    {
        if (!$this->isAvailable()) {
            return false;
        }

        try {
            $this->getConnection()->delete('alias', ['id' => $id]);

            return true;
        } catch (\Throwable $e) {
            $this->logger->error('EsyMail: erreur suppression alias #'.$id.': '.$e->getMessage());

            return false;
        }
    }

    // ──── Stats ───────────────────────────────────────

    /**
     * @return array{domains: int, mailboxes: int, aliases: int, active_mailboxes: int}
     */
    public function getStats(): array
    {
        if (!$this->isAvailable()) {
            return ['domains' => 0, 'mailboxes' => 0, 'aliases' => 0, 'active_mailboxes' => 0];
        }

        $conn = $this->getConnection();

        return [
            'domains' => (int) $conn->fetchOne('SELECT COUNT(*) FROM domain'),
            'mailboxes' => (int) $conn->fetchOne('SELECT COUNT(*) FROM mailbox'),
            'aliases' => (int) $conn->fetchOne('SELECT COUNT(*) FROM alias'),
            'active_mailboxes' => (int) $conn->fetchOne('SELECT COUNT(*) FROM mailbox WHERE is_active = true'),
        ];
    }

    // ──── Connexion ───────────────────────────────────

    private function getConnection(): Connection
    {
        if (null === $this->conn) {
            $this->conn = DriverManager::getConnection(['url' => $this->databaseUrl]);
        }

        return $this->conn;
    }
}
