<?php

declare(strict_types=1);

namespace App\Helpers;

use App\Lib\Database;
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
use App\Util\Helper;

class EmailQueue
{
    private $db;

    private string $tableName = '_queue_email';

    public function __construct()
    {
        $this->db = Database::connectDB();
    }

    public static function htmlToText($html)
    {
        // Suppress errors related to invalid HTML input
        libxml_use_internal_errors(true);

        $dom = new \DOMDocument();
        // Use @ to suppress errors related to invalid HTML input
        @$dom->loadHTML($html);

        // Remove script and style elements
        while (($r = $dom->getElementsByTagName("script")) && $r->length) {
            $r->item(0)->parentNode->removeChild($r->item(0));
        }
        while (($r = $dom->getElementsByTagName("style")) && $r->length) {
            $r->item(0)->parentNode->removeChild($r->item(0));
        }

        // Use Xpath to handle specific formatting
        $xpath = new \DOMXPath($dom);

        // Handle <br> tags by adding a newline
        foreach ($xpath->query('//br') as $br) {
            $br->parentNode->replaceChild($dom->createTextNode("\n"), $br);
        }

        // Handle <a> tags by converting to markdown-style links
        foreach ($xpath->query('//a') as $a) {
            $href = $a->getAttribute('href');
            $text = $dom->saveHTML($a);
            $markdownLink = '[' . strip_tags($text) . '](' . $href . ')';
            $a->parentNode->replaceChild($dom->createTextNode($markdownLink), $a);
        }

        // Use nodeValue to obtain the text content of the DOM
        $text = \trim($dom->documentElement->textContent);

        // Replace multiple consecutive whitespaces with a single space
        $text = preg_replace('/\s+/', ' ', $text);

        // Convert HTML entities to their corresponding characters
        $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');

        // Clear the libxml error buffer
        libxml_clear_errors();

        return $text;
    }

    private function getTrackerLink(string $tracker)
    {
        $stmt = $this->db->fetch("SELECT * FROM {$this->tableName} WHERE LOCATE(?, tracker) > 0", $tracker);
        return $stmt;
    }

    private function generateTrackerLink(): string
    {
        // Generate a random string to use as the tracker ID
        $trackerId = bin2hex(random_bytes(8));

        $trackerLink = QUEUE['track_url'] . "/{$trackerId}";

        // Return the tracker link
        return $trackerLink;
    }

    public function addQueue($sender, $receiver, $subject, $body, ?DateTime $sendAt = null, $attachments = []): int
    {
        $tracker = $this->generateTrackerLink();
        $attachmentsString = implode(',', $attachments);

        // Check if $sendAt is valid
        $sendOn = null;
        if ($sendAt !== null) {
            $sendOn = $sendAt->format('Y-m-d H:i:s');
        } 

        // attach an image to track when the email is opened
        if (QUEUE['track_open'] == true) {
            $imageTag = '<img src="' . $tracker . '.webp" alt="img">';
            $body = preg_replace('/<body>/', '<body>' . $imageTag, $body, 1);
        }

        $this->db->query("INSERT INTO {$this->tableName}", [
            'id' => NULL,
            'sender' => json_encode($sender),
            'receiver' => json_encode($receiver),
            'subject' => $subject,
            'attachments' => $attachmentsString,
            'body' => $body,
            'schedule_at' => $sendOn,
            'tracker' => $tracker,
        ]);

        // returns the auto-increment of users row
        return (int) $this->db->getInsertId();
    }

    public function getPending(int $limit = 1): array
    {
        $sql = "SELECT * FROM {$this->tableName} WHERE status = ? AND (schedule_at >= NOW() or schedule_at IS NULL)  ORDER BY schedule_at DESC LIMIT ?";
        $stmt = $this->db->fetchAll($sql, 'pending', $limit);
        return $stmt;
    }

    public function getSent(): array
    {
        $sql = "SELECT * FROM {$this->tableName} WHERE status = ? ORDER BY updated_at DESC";
        $stmt = $this->db->fetchAll($sql, 'success');
        return $stmt;
    }

    public function getAll(): array
    {
        $sql = "SELECT * FROM {$this->tableName}";
        $stmt = $this->db->fetchAll($sql);
        return $stmt;
    }

    public function getQueue(int $queueID)
    {
        $sql = "SELECT * FROM {$this->tableName} WHERE id = ? LIMIT 1";
        $stmt = $this->db->fetch($sql, $queueID);
        return $stmt;
    }

    public function setEmailAsRead(int $queueId): bool
    {
        $stmt = $this->db->query("UPDATE {$this->tableName} SET isOpen = ?, open_at = NOW() WHERE id = ? AND isOpen = '0' LIMIT 1", '1', $queueId);
        if ($stmt) {
            return true;
        }

        return false;
    }

    public function setEmailStatus(int $queueId, string $status): bool
    {
        $stmt = $this->db->query("UPDATE {$this->tableName} SET `status` = ?, sent_at = NOW() WHERE id = ? AND `status` = 'pending' LIMIT 1", $status, $queueId);
        if ($stmt) {
            return true;
        }

        return false;
    }

    public function readTrackingLink(string $trackerLink)
    {
        if (empty($trackerLink)) {
            @header("HTTP/1.1 401 Unauthorized");
            Helper::jsonResult(["status" => false, "data" => ["message" => "Unauthorized access to webpage resource"]]);
            exit;
        }

        $trackingData = $this->getTrackerLink($trackerLink);
        if ($trackingData === null) {
            @header("HTTP/1.1 401 Unauthorized");
            Helper::jsonResult(["status" => false, "data" => ["message" => "Invalid email tracking link"]]);
            exit;
        }

        // Mark email as read
        $this->setEmailAsRead($trackingData->id);
    }

    public function sendInstant($sender, $receiver, $subject, $body, $attachments = []): bool|string
    {
        $mail = new PHPMailer(true);
        $this->configureMailer($mail);

        $this->process($mail, $sender, $receiver, $subject, $body, $attachments);

        return true;
    }

    public function sendQueue(int $QueueId)
    {
        $pendingQueue = $this->getQueue($QueueId);
        $mail = new PHPMailer(true);
        $this->configureMailer($mail);

        // extract attachments from email object 
        $attachments = array_filter(explode(',', $pendingQueue['attachments']));

        $this->process($mail, $pendingQueue['sender'], $pendingQueue['receiver'], $pendingQueue['subject'], $pendingQueue['body'], $attachments);

        $this->setEmailStatus($pendingQueue['id'], 'success');
    }

    public function sendPending()
    {
        $pendingEmails = $this->getPending(\QUEUE['send_sequence']);
        $mail = new PHPMailer(true);
        $this->configureMailer($mail);

        foreach ($pendingEmails as $email) {
            // extract attachments from email object 
            $attachments = array_filter(explode(',', $email->attachments));

            $this->process($mail, $email->sender, $email->receiver, $email->subject, $email->body, $attachments);
            $this->setEmailStatus((int) $email->id, 'success');
        }

        return true;
    }

    // check SMTP settings If it works then return true
    public function checkSMTPSettings(): bool
    {
        $mail = new PHPMailer(true);
        $this->configureMailer($mail);

        $sender = ["email" => $_ENV['SMTP_EMAIL'], "name" => APP['name']];
        $receiver = ["email" => APP['email'], "name" => "Testing Full Name"];

        $subject = "Test Email";
        $body = "This is a test email to check SMTP settings.";

        $this->process($mail, $sender, $receiver, $subject, $body);
        return true;
    }

    private function configureMailer(PHPMailer $mail): void
    {
        $mail->isSMTP();
        $mail->SMTPDebug = 0;
        $mail->SMTPKeepAlive = true;
        $mail->SMTPAutoTLS = true;
        $mail->Encoding = 'base64';
        $mail->CharSet = 'UTF-8';

        // assign SMTP configuration to mailer object 
        $mail->Host       = $_ENV['SMTP_HOST'];
        $mail->SMTPAuth   = $_ENV['SMTP_AUTH'];
        $mail->Username   = $_ENV['SMTP_USERNAME'];
        $mail->Password   = $_ENV['SMTP_PASSWORD'];
        $mail->SMTPSecure = $_ENV['SMTP_SECURE'];
        $mail->Port       = $_ENV['SMTP_PORT'];
    }

    private function process(PHPMailer $mail, $sender, $receiver, $subject, $body, $attachments = [])
    {
        try {
            $senderData = is_array($sender) ? $sender : json_decode($sender, true);
            $receiverData =  is_array($receiver) ? $receiver : json_decode($receiver, true);

            // assign sender and receiver information to mailer object 
            $mail->setFrom($senderData['email'], $senderData['name']);
            $mail->addAddress($receiverData['email'], $receiverData['name']);

            $mail->isHTML(true);
            $mail->Subject = $subject;

            // convert HTML to text
            $plain_text = self::htmlToText($body);
            $mail->Body    = $body;
            $mail->AltBody = $plain_text;

            if (!empty($attachments)) {
                foreach ($attachments as $attachment) {
                    $mail->addAttachment($attachment);
                }
            }

            $mail->WordWrap = 50;
            $mail->send();
        } catch (\Exception $e) {
            Helper::jsonResult(["status" => false, "data" => ["message" => $mail->ErrorInfo]]);
        }
    }
}