<?php

namespace App\Models;

use DateTimeImmutable;
use Illuminate\Support\Facades\Date;
use App\ColorPicker;
use Carbon\CarbonImmutable;
use DateTime;
use Ddeboer\Imap\ConnectionInterface;
use Ddeboer\Imap\Search\Date\Since;
use Ddeboer\Imap\Server;
use Exception;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;

class Email extends Model
{
    use ColorPicker, HasFactory;

    protected $table = 'emails';

    // Fillable fields to allow mass assignment
    protected $fillable = [
        'message_id',
        'subject',
        'from_name',
        'from_email',
        'to',
        'cc',
        'bcc',
        'timestamp',
        'body_text',
        'body_html',
        'is_seen',
        'is_flagged',
        'size',
        'mailbox',
        'raw_headers',
        'raw_body',
        'attachments',
    ];

    protected $casts = [
        'to' => 'array',
        'cc' => 'array',
        'bcc' => 'array',
        'attachments' => 'array', // If attachments are stored as a JSON field
        'timestamp' => 'datetime', // Cast timestamp to Carbon instance
    ];

    public static function connectMailBox($imap = null): ConnectionInterface
    {
        if ($imap === null) {
            $imap = json_decode((string) config('app.settings.imap_settings'), true);
        }
        $flags = $imap['protocol'].'/'.$imap['encryption'];
        $flags = $imap['validate_cert'] ? $flags.'/validate-cert' : $flags.'/novalidate-cert';
        $server = new Server($imap['host'], $imap['port'], $flags);

        return $server->authenticate($imap['username'], $imap['password']);
    }

    public static function fetchProcessStoreEmail(): void
    {

        try {
            $allowed = explode(',', 'doc,docx,xls,xlsx,ppt,pptx,xps,pdf,dxf,ai,psd,eps,ps,svg,ttf,zip,rar,tar,gzip,mp3,mpeg,wav,ogg,jpeg,jpg,png,gif,bmp,tif,webm,mpeg4,3gpp,mov,avi,mpegs,wmv,flx,txt');
            $connection = Email::connectMailBox();
            $mailbox = $connection->getMailbox('INBOX');
            $messages = $mailbox->getMessages();

            $result = '';
            foreach ($messages as $message) {

                $sender = $message->getFrom();
                $date = $message->getDate();
                if (!$date instanceof DateTimeImmutable) {
                    $date = new DateTime;
                    if ($message->getHeaders()->get('udate')) {
                        $date->setTimestamp($message->getHeaders()->get('udate'));
                    }
                }
                $content = '';
                $contentText = '';
                $html = $message->getBodyHtml();
                $text = $message->getBodyText();

                if ($text) {
                    $contentText = str_replace('<a', '<a target="blank"', str_replace(["\r\n", "\n"], '', $text));
                }
                if ($html) {
                    $content = str_replace('<a', '<a target="blank"', $html);
                } else {
                    $content = str_replace('<a', '<a target="blank"', str_replace(["\r\n", "\n"], '<br/>', $text));
                }

                $obj = [];

                $to = $message->getHeaders()->get('To') ? array_map(fn($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('To')) : [];

                $cc = $message->getHeaders()->get('Cc') ? array_map(fn($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('Cc')) : [];

                $bcc = $message->getHeaders()->get('Bcc') ? array_map(fn($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('Bcc')) : [];

                $messageTime = $message->getDate();
                $utcTime = CarbonImmutable::instance($messageTime)->setTimezone('UTC')->toDateTimeString();

                $obj['id'] = $message->getNumber();
                $obj['to'] = $to;
                $obj['cc'] = $cc;
                $obj['bcc'] = $bcc;
                $obj['subject'] = $message->getSubject();
                $obj['sender_name'] = $sender->getName();
                $obj['sender_email'] = $sender->getAddress();
                $obj['timestamp'] = $utcTime;
                $obj['size'] = $message->getSize();
                // $obj['date'] = $date->format(json_decode(config('app.settings.configuration_settings'))->date_format ?? 'd M Y h:i A');
                $obj['content'] = $content;
                $obj['contentText'] = $contentText;
                $obj['attachments'] = [];
                // $obj['raw_headers'] = $message->getRawHeaders();
                // $obj['raw_body'] = $message->getRawMessage();

                if ($message->hasAttachments()) {
                    $attachments = $message->getAttachments();
                    $directory = './tmp/attachments/'.$obj['id'].'/';

                    if (!is_dir($directory)) {
                        mkdir($directory, 0777, true);
                    }
                    foreach ($attachments as $attachment) {
                        $filenameArray = explode('.', (string) $attachment->getFilename());
                        $extension = $filenameArray[count($filenameArray) - 1];
                        if (in_array($extension, $allowed)) {
                            if (! file_exists($directory.$attachment->getFilename())) {
                                try {
                                    file_put_contents(
                                        $directory.$attachment->getFilename(),
                                        $attachment->getDecodedContent()
                                    );
                                } catch (Exception $e) {
                                    Log::error($e->getMessage());
                                }

                            }
                            if ($attachment->getFilename() !== 'undefined') {
                                $url = config('app.settings.app_base_url').str_replace('./', '/', $directory.$attachment->getFilename());
                                $structure = $attachment->getStructure();
                                if (isset($structure->id) && str_contains($obj['content'], trim($structure->id, '<>'))) {
                                    $obj['content'] = str_replace('cid:'.trim($structure->id, '<>'), $url, $obj['content']);
                                }
                                $obj['attachments'][] = [
                                    'file' => $attachment->getFilename(),
                                    'url' => $url,
                                ];
                            }
                        }

                    }

                }

                $response['data'][] = $obj;

                if (! $message->isSeen()) {
                    $initialData = $obj;
                    $data = [
                        'message_id' => Date::parse($utcTime)->format('Ymd').$initialData['id'],
                        'subject' => $initialData['subject'],
                        'from_name' => $initialData['sender_name'],
                        'from_email' => $initialData['sender_email'],
                        'to' => $initialData['to'],
                        'cc' => $initialData['cc'],
                        'bcc' => $initialData['bcc'],
                        'timestamp' => $initialData['timestamp'], // store in UTC
                        'body_text' => $initialData['contentText'],
                        'body_html' => $initialData['content'],
                        'is_seen' => false,
                        'is_flagged' => false,
                        'size' => $initialData['size'],
                        'mailbox' => 'INBOX',
                        'raw_headers' => null,
                        'raw_body' => null,
                        'attachments' => $initialData['attachments'],
                    ];

                    try {
                        self::query()->create($data);
                        $checkAction = config('app.move_or_delete');
                        if ($checkAction != null) {
                            if ($checkAction == 'delete') {
                                $message->delete();
                            } else {
                                $newMailBox = $connection->getMailbox($checkAction);
                                $message->move($newMailBox);
                            }
                        }

                    } catch (Exception) {
                        // \Log::error($e);
                    }
                } else {
                    $initialData = $obj;
                    $data = [
                        'message_id' => Date::parse($utcTime)->format('Ymd').$initialData['id'],
                        'subject' => $initialData['subject'],
                        'from_name' => $initialData['sender_name'],
                        'from_email' => $initialData['sender_email'],
                        'to' => $initialData['to'],
                        'cc' => $initialData['cc'],
                        'bcc' => $initialData['bcc'],
                        'timestamp' => $initialData['timestamp'], // store in UTC
                        'body_text' => $initialData['contentText'],
                        'body_html' => $initialData['content'],
                        'is_seen' => true,
                        'is_flagged' => false,
                        'size' => $initialData['size'],
                        'mailbox' => 'INBOX',
                        'raw_headers' => null,
                        'raw_body' => null,
                        'attachments' => $initialData['attachments'],
                    ];

                    try {
                        self::query()->create($data);
                        $checkAction = config('app.move_or_delete');
                        if ($checkAction != null) {
                            if ($checkAction == 'delete') {
                                $message->delete();
                            } else {
                                $newMailBox = $connection->getMailbox($checkAction);
                                $message->move($newMailBox);
                            }
                        }

                    } catch (Exception) {
                        // \Log::error($e);
                    }
                }

            }

            $connection->expunge();

        } catch (Exception $e) {
            Log::error($e->getMessage());
        }
    }

    public static function fetchEmailFromDB($email)
    {

        $validator = Validator::make(['email' => $email], [
            'email' => ['required', 'email'],
        ]);

        if ($validator->fails()) {
            return [];
        }

        return self::query()->whereJsonContains('to', $email)->orderBy('timestamp', 'desc')->get();
    }

    public static function parseEmail(string $email, $deleted = []): array
    {
        if (config('app.fetch_from_remote_db')) {
            $messages = RemoteEmail::fetchEmailFromDB($email);
        } else {
            $messages = self::fetchEmailFromDB($email);
        }
        $limit = json_decode((string) config('app.settings.configuration_settings'))->fetch_messages_limit ?? 15;
        $count = 1;
        $response = [
            'data' => [],
            'notifications' => [],
        ];

        foreach ($messages as $message) {

            // fix for null attachments
            if ($message['attachments'] === null) {
                $message['attachments'] = [];
            }

            if (in_array($message['message_id'], $deleted)) {
                // If it exists, delete the matching record from the 'emails' table
                if (config('app.fetch_from_remote_db')) {
                    RemoteEmail::query()->where('message_id', $message['message_id'])->delete();
                } else {
                    Email::query()->where('message_id', $message['message_id'])->delete();
                }

                continue;
            }

            $blocked = false;

            $timestamp = $message['timestamp'];
            $carbonTimestamp = Date::parse($timestamp, 'UTC');
            $obj = [];
            $obj['subject'] = $message['subject'];
            $obj['sender_name'] = $message['from_name'];
            $obj['sender_email'] = $message['from_email'];
            $obj['timestamp'] = $message['timestamp'];
            $obj['date'] = $carbonTimestamp->format('d M Y h:i A');
            $obj['datediff'] = $carbonTimestamp->diffForHumans(Date::now('UTC'));
            $obj['id'] = $message['message_id'];
            $obj['content'] = $message['body_html'];
            $obj['contentText'] = $message['body_text'];
            $obj['attachments'] = [];
            $obj['is_seen'] = $message['is_seen'];
            $obj['sender_photo'] = self::chooseColor(strtoupper(substr((string) $message['from_name'] ?: (string) $message['from_email'], 0, 1)));

            $domain = explode('@', (string) $obj['sender_email'])[1];
            $blocked = in_array($domain, json_decode((string) config('app.settings.configuration_settings'))->blocked_domains);
            if ($blocked) {
                $obj['subject'] = __('Blocked');
                $obj['content'] = __('Emails from').' '.$domain.' '.__('are blocked by Admin');
                $obj['contentText'] = __('Emails from').' '.$domain.' '.__('are blocked by Admin');
            }

            if (count($message['attachments']) > 0 && ! $blocked) {
                $obj['attachments'] = $message['attachments'];

            }

            $response['data'][] = $obj;
            if (! $message['is_seen']) {
                $response['notifications'][] = [
                    'subject' => $obj['subject'],
                    'sender_name' => $obj['sender_name'],
                    'sender_email' => $obj['sender_email'],
                ];
                if (config('app.zemail_log')) {
                    file_put_contents(storage_path('logs/zemail.csv'), request()->ip().','.date('Y-m-d h:i:s a').','.$obj['sender_email'].','.$email.PHP_EOL, FILE_APPEND);
                }
            }
            if (config('app.fetch_from_remote_db')) {
                RemoteEmail::query()->where('message_id', $message['message_id'])->update(['is_seen' => true]);
            } else {
                Email::query()->where('message_id', $message['message_id'])->update(['is_seen' => true]);
            }
            if (++$count > $limit) {
                break;
            }
        }

        return $response;
    }

    public static function deleteBulkAttachments(): void
    {
        $dir = public_path('/tmp/attachments');

        try {
            if (File::exists($dir)) {
                File::cleanDirectory($dir);
            }
        } catch (Exception $e) {
            Log::error($e->getMessage());
        }
    }

    public static function deleteBulkMailboxes(): string
    {
        $foldersToClean = ['INBOX', 'ZDUMP', 'Trash'];
        $cutoff = (new DateTime)->modify('-3 hours');
        $totalDeleted = 0;
        $maxToDelete = 100;

        foreach ($foldersToClean as $folderName) {
            $connection = Email::connectMailBox();
            if ($totalDeleted >= $maxToDelete) {
                $connection->expunge();
                break;
            }

            if ($connection->hasMailbox($folderName)) {
                $mailbox = $connection->getMailbox($folderName);
                $messages = $mailbox->getMessages(new Since(new DateTime('today')));

                foreach ($messages as $message) {
                    if ($totalDeleted >= $maxToDelete) {
                        $connection->expunge();
                        break 2; // exit both loops
                    }

                    $messageDate = $message->getDate();
                    if ($messageDate < $cutoff) {
                        $message->delete();
                        $totalDeleted++;
                    }
                }
            }
            $connection->expunge();
        }

        return "$totalDeleted message(s) deleted from Trash and ZDUMP.";
    }

    public static function deleteMessagesFromDB(): string
    {
        $cutoff = Date::now('UTC')->subHours(6)->toDateTimeString();
        $count = count(self::query()->where('timestamp', '<', $cutoff)
            ->orderBy('timestamp', 'desc')
            ->get());

        if ($count > 0) {
            self::query()->where('timestamp', '<', $cutoff)->delete();

            return "$count old message(s) deleted from the database.";
        }

        return 'No messages older than 6 hours found.';
    }

    public static function mailToDBStatus(): bool
    {
        if (config('app.fetch_from_remote_db')) {
            $latestRecord = RemoteEmail::query()->orderBy('timestamp', 'desc')->first();
        } else {
            $latestRecord = self::query()->orderBy('timestamp', 'desc')->first();
        }
        if (! $latestRecord) {
            return false;
        }

        $currentTime = Date::now('UTC');
        $lastRecordTime = Date::parse($latestRecord->timestamp);
        return $lastRecordTime->diffInMinutes($currentTime) < 5;
    }

    public static function cleanMailbox(): string
    {
        $foldersToClean = ['INBOX'];
        $cutoff = (new DateTime)->modify('-6 hours');
        $totalDeleted = 0;
        $maxToDelete = 100;

        foreach ($foldersToClean as $folderName) {
            $connection = Email::connectMailBox();
            if ($totalDeleted >= $maxToDelete) {
                $connection->expunge();
                break;
            }

            if ($connection->hasMailbox($folderName)) {
                $mailbox = $connection->getMailbox($folderName);
                $messages = $mailbox->getMessages(new Since(new DateTime('today')));

                foreach ($messages as $message) {
                    if ($totalDeleted >= $maxToDelete) {
                        $connection->expunge();
                        break 2; // exit both loops
                    }

                    $messageDate = $message->getDate();
                    if ($messageDate < $cutoff) {
                        $message->delete();
                        $totalDeleted++;
                    }
                }
            }
            $connection->expunge();
        }

        return "$totalDeleted message(s) deleted from Trash and ZDUMP.";
    }
}
