<?php

declare(strict_types=1);

namespace App\Core;

/**
 * Mail Helper — PHPMailer Wrapper for Gmind Institute
 *
 * Uses PHPMailer library to send emails via SMTP.
 * Place PHPMailer in: /phpmailer/ directory (at project root)
 * or install via Composer.
 *
 * Supports cPanel mail servers with self-signed certificates
 * by automatically bypassing SSL verification.
 */
class Mail
{
    private ?\PDO $db = null;
    private array $config = [];
    private ?string $foundPath = null;
    private string $lastError = '';

    public function __construct()
    {
        $this->loadConfig();
    }

    /**
     * Load SMTP configuration from database settings table.
     *
     * Reads from BOTH column pairs (setting_key/setting_value AND key_name/value)
     * and merges them. The key_name/value pair is used as fallback if
     * setting_key/setting_value is empty.
     */
    private function loadConfig(): void
    {
        // Try to get DB connection if available
        try {
            $this->db = getDb();
        } catch (\Throwable $e) {
            // DB not available, will use env defaults
        }

        $rawSettings = $this->readAllSmtpSettings();

        $this->config = [
            'host'       => $rawSettings['smtp_host'] ?? '',
            'port'       => $rawSettings['smtp_port'] ?? '465',
            'username'   => $rawSettings['smtp_username'] ?? '',
            'password'   => $rawSettings['smtp_password'] ?? '',
            'encryption' => $rawSettings['smtp_encryption'] ?? 'ssl',
            'from_email' => $rawSettings['smtp_from_email'] ?? '',
            'from_name'  => $rawSettings['smtp_from_name'] ?? 'Gmind Institute',
        ];
    }

    /**
     * Read all SMTP settings from DB — merge BOTH column pairs.
     *
     * This is the KEY fix: previously we only read from one column pair
     * and returned early. Now we read from BOTH and merge, so if
     * setting_value is empty but value has data, we still get the value.
     */
    private function readAllSmtpSettings(): array
    {
        $keys = ['smtp_host', 'smtp_port', 'smtp_username', 'smtp_password', 'smtp_encryption', 'smtp_from_email', 'smtp_from_name'];
        $result = [];

        if (!$this->db) {
            return $result;
        }

        // Method 1: Read from setting_key / setting_value
        try {
            $placeholders = implode(',', array_fill(0, count($keys), '?'));
            $stmt = $this->db->prepare(
                "SELECT `setting_key`, `setting_value` FROM `settings` WHERE `setting_key` IN ({$placeholders})"
            );
            $stmt->execute($keys);
            $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
            foreach ($rows as $row) {
                $k = $row['setting_key'] ?? '';
                if (in_array($k, $keys, true)) {
                    $result[$k] = $row['setting_value'] ?? '';
                }
            }
        } catch (\Throwable $e) {}

        // Method 2: Read from key_name / value — fill any missing or empty values
        try {
            $placeholders = implode(',', array_fill(0, count($keys), '?'));
            $stmt = $this->db->prepare(
                "SELECT `key_name`, `value` FROM `settings` WHERE `key_name` IN ({$placeholders})"
            );
            $stmt->execute($keys);
            $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
            foreach ($rows as $row) {
                $k = $row['key_name'] ?? '';
                $v = $row['value'] ?? '';
                if (in_array($k, $keys, true)) {
                    // Use key_name/value only if setting_key value is missing or empty
                    if (!isset($result[$k]) || $result[$k] === '') {
                        $result[$k] = $v;
                    }
                }
            }
        } catch (\Throwable $e) {}

        return $result;
    }

    /**
     * Get current SMTP config (for admin form prefill).
     */
    public function getConfig(): array
    {
        return $this->config;
    }

    /**
     * Set SMTP config directly (bypasses database reading).
     *
     * Used by AdminSmtpController to inject settings read directly
     * from the database, avoiding issues where Mail's own DB
     * connection might not be available in the same request.
     *
     * @param array $settings Key-value pairs with smtp_ prefixed keys
     *                       e.g. ['smtp_host' => 'mail.example.com', ...]
     */
    public function setConfig(array $settings): void
    {
        // Map smtp_* keys to Mail's internal config keys
        $this->config['host']       = $settings['smtp_host'] ?? $this->config['host'];
        $this->config['port']       = $settings['smtp_port'] ?? $this->config['port'];
        $this->config['username']   = $settings['smtp_username'] ?? $this->config['username'];
        $this->config['password']   = $settings['smtp_password'] ?? $this->config['password'];
        $this->config['encryption'] = $settings['smtp_encryption'] ?? $this->config['encryption'];
        $this->config['from_email'] = $settings['smtp_from_email'] ?? $this->config['from_email'];
        $this->config['from_name']  = $settings['smtp_from_name'] ?? $this->config['from_name'];
    }

    /**
     * Get the last error message from send/test operations.
     */
    public function getLastError(): string
    {
        return $this->lastError;
    }

    /**
     * Get the PHPMailer path that was found (for admin info display).
     */
    public function getPHPMailerPathFound(): ?string
    {
        if ($this->foundPath === null) {
            $this->foundPath = $this->findPHPMailerPath();
        }
        return $this->foundPath;
    }

    /**
     * Configure PHPMailer instance with common SMTP settings.
     *
     * Includes SSL certificate bypass for cPanel self-signed certs.
     */
    private function configureMailer(\PHPMailer\PHPMailer\PHPMailer $mail): void
    {
        $mail->isSMTP();
        $mail->Host       = $this->config['host'];
        $mail->Port       = (int) $this->config['port'];
        $mail->SMTPAuth   = !empty($this->config['username']);
        $mail->Username   = $this->config['username'];
        $mail->Password   = $this->config['password'];

        // Set encryption
        if ($this->config['encryption'] === 'ssl') {
            $mail->SMTPSecure = \PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_SMTPS;
        } elseif ($this->config['encryption'] === 'tls') {
            $mail->SMTPSecure = \PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS;
        } else {
            $mail->SMTPSecure = '';
            $mail->SMTPAutoTLS = false;
        }

        // For TLS: allow automatic upgrade from non-encrypted to TLS
        if ($this->config['encryption'] === 'tls') {
            $mail->SMTPAutoTLS = true;
        }

        $mail->CharSet = 'UTF-8';

        // Timeout settings (15 sec connect, 20 sec read/write)
        $mail->Timeout = 20;
        $GLOBALS['smtp_connect_timeout'] = 15;

        // SSL certificate bypass — REQUIRED for cPanel self-signed certificates
        // Most shared hosting (cPanel, Hostinger, GoDaddy) use self-signed mail certs
        // Without this, SMTP connection fails with "Could not connect to SMTP host"
        $mail->SMTPOptions = [
            'ssl' => [
                'verify_peer'      => false,
                'verify_peer_name' => false,
                'allow_self_signed' => true,
            ],
        ];
    }

    /**
     * Send an email to a single recipient.
     */
    public function send(string $toEmail, string $toName, string $subject, string $bodyHtml, ?string $bodyText = null): bool
    {
        $this->lastError = '';

        $phpMailerPath = $this->getPHPMailerPath();
        if (!$phpMailerPath) {
            $this->lastError = 'PHPMailer library not found. Please install it first.';
            error_log('Mail::send — ' . $this->lastError);
            return false;
        }

        require_once $phpMailerPath . '/PHPMailer.php';
        require_once $phpMailerPath . '/SMTP.php';
        require_once $phpMailerPath . '/Exception.php';

        $mail = new \PHPMailer\PHPMailer\PHPMailer(true);

        try {
            $this->configureMailer($mail);

            // From
            $mail->setFrom($this->config['from_email'], $this->config['from_name']);

            // To
            $mail->addAddress($toEmail, $toName);

            // Content
            $mail->isHTML(true);
            $mail->Subject = $subject;
            $mail->Body    = $bodyHtml;
            $mail->AltBody = $bodyText ?? strip_tags($bodyHtml);

            $mail->send();
            return true;
        } catch (\PHPMailer\PHPMailer\Exception $e) {
            $this->lastError = $e->getMessage();
            error_log('Mail::send failed — ' . $e->getMessage());
            return false;
        } catch (\Throwable $e) {
            $this->lastError = $e->getMessage();
            error_log('Mail::send failed — ' . $e->getMessage());
            return false;
        }
    }

    /**
     * Send newsletter to multiple recipients.
     * Returns: ['sent' => int, 'failed' => int, 'errors' => array]
     */
    public function sendBulk(array $recipients, string $subject, string $bodyHtml, ?string $bodyText = null, callable $onProgress = null): array
    {
        $phpMailerPath = $this->getPHPMailerPath();
        if (!$phpMailerPath) {
            return ['sent' => 0, 'failed' => count($recipients), 'errors' => ['PHPMailer not found']];
        }

        require_once $phpMailerPath . '/PHPMailer.php';
        require_once $phpMailerPath . '/SMTP.php';
        require_once $phpMailerPath . '/Exception.php';

        $sent   = 0;
        $failed = 0;
        $errors = [];

        foreach ($recipients as $index => $recipient) {
            $mail = new \PHPMailer\PHPMailer\PHPMailer(true);

            try {
                $this->configureMailer($mail);

                // From
                $mail->setFrom($this->config['from_email'], $this->config['from_name']);

                // To
                $email = $recipient['email'];
                $name  = $recipient['name'] ?? '';
                $mail->addAddress($email, $name);

                // Content — replace {{name}} and {{email}} placeholders
                $personalizedHtml = str_replace(
                    ['{{name}}', '{{email}}'],
                    [$name, $email],
                    $bodyHtml
                );
                $personalizedText = $bodyText
                    ? str_replace(['{{name}}', '{{email}}'], [$name, $email], $bodyText)
                    : null;

                $mail->isHTML(true);
                $mail->Subject = $subject;
                $mail->Body    = $personalizedHtml;
                $mail->AltBody = $personalizedText ?? strip_tags($personalizedHtml);

                $mail->send();
                $sent++;

                // Small delay to avoid SMTP rate limiting
                if ($sent % 10 === 0) {
                    usleep(200000); // 0.2 seconds
                }
            } catch (\Throwable $e) {
                $failed++;
                $errors[] = "Failed for {$email}: " . $e->getMessage();
                error_log("Mail::sendBulk failed for {$email}: " . $e->getMessage());
            }

            // Progress callback
            if ($onProgress) {
                $onProgress($index + 1, count($recipients), $sent, $failed);
            }
        }

        return ['sent' => $sent, 'failed' => $failed, 'errors' => $errors];
    }

    /**
     * Test SMTP connection.
     *
     * Returns detailed error information for troubleshooting.
     */
    public function testConnection(): array
    {
        $phpMailerPath = $this->getPHPMailerPath();
        if (!$phpMailerPath) {
            return ['success' => false, 'message' => 'PHPMailer library not found. Please install it first.'];
        }

        if (empty($this->config['host'])) {
            return ['success' => false, 'message' => 'SMTP Host is not configured. Please set it first.'];
        }

        if (empty($this->config['from_email'])) {
            return ['success' => false, 'message' => 'From Email is not configured. Please set it first.'];
        }

        require_once $phpMailerPath . '/PHPMailer.php';
        require_once $phpMailerPath . '/SMTP.php';
        require_once $phpMailerPath . '/Exception.php';

        $mail = new \PHPMailer\PHPMailer\PHPMailer(true);

        try {
            $this->configureMailer($mail);

            // Capture debug output for error reporting
            $mail->SMTPDebug = \PHPMailer\PHPMailer\SMTP::DEBUG_CONNECTION;
            ob_start();
            $mail->Debugoutput = function ($str, $level) {
                // Just let ob_start capture it
            };

            $result = $mail->smtpConnect();

            if ($result) {
                $mail->getSMTPInstance()->quit();
                ob_end_clean();
                return [
                    'success' => true,
                    'message' => 'Connected to ' . $this->config['host'] . ':' . $this->config['port'] . ' successfully!',
                ];
            }

            $debug = ob_get_clean();
            return [
                'success' => false,
                'message' => 'Could not connect to SMTP server. Debug: ' . $this->cleanDebug($debug),
            ];
        } catch (\PHPMailer\PHPMailer\Exception $e) {
            ob_end_clean();
            return [
                'success' => false,
                'message' => 'SMTP Error: ' . $e->getMessage(),
            ];
        } catch (\Throwable $e) {
            ob_end_clean();
            return [
                'success' => false,
                'message' => 'Connection Error: ' . $e->getMessage(),
            ];
        }
    }

    /**
     * Clean SMTP debug output to a readable summary.
     */
    private function cleanDebug(string $debug): string
    {
        // Take last 300 chars of debug — most relevant error is at the end
        $debug = preg_replace('/\s+/', ' ', $debug);
        if (strlen($debug) > 300) {
            return '...' . substr($debug, -300);
        }
        return $debug ?: '(no debug output)';
    }

    /**
     * Find PHPMailer library path.
     */
    private function getPHPMailerPath(): ?string
    {
        if ($this->foundPath !== null) {
            return $this->foundPath;
        }

        $this->foundPath = $this->findPHPMailerPath();
        return $this->foundPath;
    }

    /**
     * Actually search for PHPMailer.
     */
    private function findPHPMailerPath(): ?string
    {
        $basePath = defined('BASE_PATH') ? BASE_PATH : '';
        $docRoot  = rtrim($_SERVER['DOCUMENT_ROOT'] ?? '', '/');
        $relRoot  = dirname(__DIR__, 3); // 3 levels up from app/Core/Mail.php = project root

        // Possible locations (in order of priority)
        $paths = [
            // 1. lowercase phpmailer at project root (most common user upload)
            $docRoot . $basePath . '/phpmailer/src',
            $docRoot . '/phpmailer/src',
            $relRoot . '/phpmailer/src',

            // 2. Uppercase PHPMailer at project root
            $docRoot . $basePath . '/PHPMailer/src',
            $docRoot . '/PHPMailer/src',
            $relRoot . '/PHPMailer/src',

            // 3. Inside /lib/ subfolder
            $docRoot . $basePath . '/lib/PHPMailer/src',
            $docRoot . '/lib/PHPMailer/src',
            $relRoot . '/lib/PHPMailer/src',

            // 4. Inside /lib/ lowercase
            $docRoot . $basePath . '/lib/phpmailer/src',
            $docRoot . '/lib/phpmailer/src',
            $relRoot . '/lib/phpmailer/src',

            // 5. Composer autoload
            $docRoot . $basePath . '/vendor/phpmailer/phpmailer/src',
            $docRoot . '/vendor/phpmailer/phpmailer/src',
            $relRoot . '/vendor/phpmailer/phpmailer/src',

            // 6. Flat structure (no /src/ subfolder) — lowercase phpmailer
            $docRoot . $basePath . '/phpmailer',
            $docRoot . '/phpmailer',
            $relRoot . '/phpmailer',

            // 7. Flat structure — uppercase PHPMailer
            $docRoot . $basePath . '/PHPMailer',
            $docRoot . '/PHPMailer',
            $relRoot . '/PHPMailer',
        ];

        foreach ($paths as $path) {
            $realPath = realpath($path);
            if ($realPath && is_dir($realPath) && file_exists($realPath . '/PHPMailer.php')) {
                return $realPath;
            }
        }

        // Last resort: scan project root for any folder containing PHPMailer.php
        $scanDirs = [
            $docRoot . $basePath,
            $docRoot,
            $relRoot,
        ];
        foreach ($scanDirs as $scanDir) {
            $real = realpath($scanDir);
            if (!$real || !is_dir($real)) continue;
            $items = @scandir($real);
            if (!$items) continue;
            foreach ($items as $item) {
                if ($item === '.' || $item === '..') continue;
                $sub = $real . '/' . $item;
                if (!is_dir($sub)) continue;
                // Check sub/src/PHPMailer.php
                if (file_exists($sub . '/src/PHPMailer.php')) {
                    return realpath($sub . '/src');
                }
                // Check sub/PHPMailer.php (flat)
                if (file_exists($sub . '/PHPMailer.php')) {
                    return $sub;
                }
            }
        }

        return null;
    }

    /**
     * Check if PHPMailer is available.
     */
    public function isAvailable(): bool
    {
        return $this->getPHPMailerPath() !== null;
    }

    /**
     * Check if SMTP is configured.
     */
    public function isConfigured(): bool
    {
        return !empty($this->config['host']) && !empty($this->config['from_email']);
    }

    /**
     * Get a summary of the current mail setup status.
     */
    public function getStatus(): array
    {
        return [
            'mailer_available'  => $this->isAvailable(),
            'smtp_configured'   => $this->isConfigured(),
            'phpmailer_path'    => $this->getPHPMailerPathFound(),
            'host_set'          => !empty($this->config['host']),
            'from_email_set'    => !empty($this->config['from_email']),
            'auth_set'          => !empty($this->config['username']),
            'config'            => $this->config, // Include for debug
        ];
    }
}
