<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use DateTimeImmutable;
use Illuminate\Support\Facades\Log;
use App\ColorPicker;
use Carbon\Carbon;
use DateTime;
use Ddeboer\Imap\ConnectionInterface;
use Ddeboer\Imap\Search\Email\Cc;
use Ddeboer\Imap\Search\Email\To;
use Ddeboer\Imap\SearchExpression;
use Exception;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cookie;

class Premium extends Model
{
    use HasFactory;
    use HasFactory;
    use ColorPicker;

    public static function connectMailBox($imap = null): ConnectionInterface
    {
        $imapDB = json_decode((string) config('app.settings.imap_settings'), true);
        $imap = [
            'host' => $imapDB['premium_host'],
            'port' => $imapDB['premium_port'],
            'encryption' => $imapDB['premium_encryption'],
            'validate_cert' => $imapDB['premium_validate_cert'],
            'username' => $imapDB['premium_username'],
            'password' => $imapDB['premium_password'],
            'default_account' => $imapDB['premium_default_account'],
            'protocol' => $imapDB['premium_protocol'],
            'cc_check' => $imapDB['premium_cc_check'],
        ];

        return ZEmail::connectMailBox($imap);
    }

    public static function fetchMessages(?string $email, $type = 'to', $deleted = []): array
    {
        if ($email == null) {
            return [
                'data' => [],
                'notifications' => [],
            ];
        }
        $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 = self::connectMailBox();

        $mailbox = $connection->getMailbox('INBOX');
        $search = new SearchExpression;
        if ($type == 'cc') {
            $search->addCondition(new Cc($email));
        } else {
            $search->addCondition(new To($email));
        }
        $messages = $mailbox->getMessages($search, \SORTDATE, true);
        $limit = json_decode((string) config('app.settings.configuration_settings'))->fetch_messages_limit ?? 15;
        $count = 1;
        $response = [
            'data' => [],
            'notifications' => [],
        ];
        foreach ($messages as $message) {
            if (in_array($message->getNumber(), $deleted)) {
                $message->delete();

                continue;
            }
            $blocked = false;
            $sender = $message->getFrom();
            $date = $message->getDate();
            if (!$date instanceof DateTimeImmutable) {
                $date = new DateTime;
                if ($message->getHeaders()->get('udate')) {
                    $date->setTimestamp($message->getHeaders()->get('udate'));
                }
            }
            $datediff = new Carbon($date);
            $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));
            }
            if (json_decode((string) config('app.settings.configuration_settings'))->enable_masking_external_link) {
                $content = str_replace('href="', 'href="http://href.li/?', $content);
            }

            $obj = [];
            $obj['subject'] = $message->getSubject();
            $obj['sender_name'] = $sender->getName();
            $obj['sender_email'] = $sender->getAddress();
            $obj['timestamp'] = $message->getDate();
            $obj['date'] = $date->format(json_decode((string) config('app.settings.configuration_settings'))->date_format ?? 'd M Y h:i A');
            $obj['datediff'] = $datediff->diffForHumans();
            $obj['id'] = $message->getNumber();
            $obj['size'] = $message->getSize();
            $obj['content'] = $content;
            $obj['contentText'] = $contentText;
            $obj['attachments'] = [];
            $obj['is_seen'] = true;
            $obj['sender_photo'] = self::chooseColor(strtoupper(substr($sender->getName() ?: (string) $sender->getAddress(), 0, 1)));

            // Checking if Sender is Blocked
            $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 ($message->hasAttachments() && ! $blocked) {
                $attachments = $message->getAttachments();
                $directory = './tmp/premium/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((string) $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()) {
                $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);
                }
            }
            $message->markAsSeen();
            if (++$count > $limit) {
                break;
            }
        }

        $response['data'] = array_reverse($response['data']);
        $connection->expunge();

        return $response;
    }

    public static function getMessages(?string $email, $type = 'to', $deleted = []): array
    {
        return self::fetchMessages($email, $type, $deleted);
    }

    public static function deleteMessage(int $id): void
    {
        $connection = self::connectMailBox();
        $mailbox = $connection->getMailbox('INBOX');
        $mailbox->getMessage($id)->delete();
        $connection->expunge();
    }

    public static function getEmail($generate = false)
    {
        if (Cookie::has('p_email')) {
            return Cookie::get('p_email');
        }
        return $generate ? self::generateRandomEmail() : null;
    }

    public static function getEmails()
    {
        if (Cookie::has('p_emails')) {
            return unserialize(Cookie::get('p_emails'));
        }
        return [];
    }

    public static function setEmail($email): void
    {
        $emails = unserialize(Cookie::get('p_emails'));
        if (is_array($emails) && in_array($email, $emails)) {
            Cookie::queue('p_email', $email, 43800);
        }
    }

    public static function setEmailP($email): void
    {
        $usageLogs = UsageLog::query()->where(['user_id' => auth()->user()->id])->first();
        $emails = $usageLogs->emails_created_history;
        if (is_array($emails) && in_array($email, $emails)) {
            Cookie::queue('p_email', $email, 43800);
        }
    }

    public static function removeEmail($email): void
    {
        $emails = self::getEmails();
        $key = array_search($email, $emails);
        if ($key !== false) {
            array_splice($emails, $key, 1);
        }
        if (count($emails) > 0) {
            self::setEmail($emails[0]);
            Cookie::queue('p_emails', serialize($emails), 43800);
        } else {
            Cookie::queue('p_email', '', -1);
            Cookie::queue('p_emails', serialize([]), -1);
        }
    }

    public static function createCustomEmailFull($email): string
    {
        $data = explode('@', (string) $email);
        $username = $data[0];
        if (strlen($username) < json_decode((string) config('app.settings.configuration_settings'))->custom_username_length_min || strlen($username) > json_decode((string) config('app.settings.configuration_settings'))->custom_username_length_max) {
            $zemail = new Premium;
            $username = $zemail->generateRandomUsername();
        }
        $domain = $data[1];

        return self::createCustomEmail($username, $domain);
    }

    public static function createCustomEmail($username, string $domain): string
    {
        $username = preg_replace('/[^a-zA-Z0-9+.]/', '', strtolower((string) $username));

        $settings = json_decode((string) config('app.settings.configuration_settings'), true);
        $forbidden_ids = $settings['forbidden_ids'] ?? [];
        $gmail_usernames = $settings['premium_gmailUsernames'] ?? [];
        $outlook_usernames = $settings['premium_outlookUsernames'] ?? [];
        $domains = $settings['premium_domains'] ?? [];

        if (in_array($username, $forbidden_ids)) {
            return self::generateRandomEmail(true);
        }

        if ($username === '' && in_array($domain, ['gmail.com', 'googlemail.com'])) {
            return self::generateRandomGmail(true);
        }

        if ($username === '' && $domain === 'outlook.com') {
            return self::generateRandomOutlook(true);
        }

        $zemail = new Premium;

        if ($username === '' && in_array($domain, $domains)) {
            return $zemail->generateRandomUsername().'@'.$domain;
        }

        if ($domain === 'outlook.com') {
            if (str_contains((string) $username, '+')) {
                [$check_username, $post_username] = explode('+', (string) $username, 2);

                if (in_array($check_username, $outlook_usernames)) {
                    $email = $username.'@'.$domain;
                } else {
                    $email = $zemail->getRandomOutlookUser().'+'.$post_username.'@'.$domain;
                }
            } else {
                $email = $zemail->getRandomOutlookUser().'+'.$username.'@'.$domain;
            }
            self::storeEmail($email);

            return $email;
        }

        if (in_array($domain, ['gmail.com', 'googlemail.com'])) {
            if (str_contains((string) $username, '+')) {
                [$check_username, $post_username] = explode('+', (string) $username, 2);

                if (in_array($check_username, $gmail_usernames)) {
                    $email = $username.'@'.$domain;
                } else {
                    $email = $zemail->getRandomGmailUser().'+'.$post_username.'@'.$domain;
                }

            } elseif (str_contains((string) $username, '.')) {
                $check_username = str_replace('.', '', $username);

                if (in_array($check_username, $gmail_usernames)) {
                    $email = $username.'@'.$domain;
                } else {
                    $email = $zemail->generateRandomGmail().'@'.$domain;
                }

            } else {
                $email = $zemail->getRandomGmailUser().'+'.$username.'@'.$domain;
            }

            self::storeEmail($email);

            return $email;
        }

        // Handle other custom domains
        if (! in_array($domain, $domains)) {
            return self::generateRandomEmail(true);
        }

        $finalDomain = in_array($domain, $domains) ? $domain : ($domains[0] ?? 'example.com');
        $email = $username.'@'.$finalDomain;

        self::storeEmail($email);

        return $email;
    }

    public static function generateRandomEmail($store = true): string
    {
        $zemail = new Premium;
        $domain = $zemail->getRandomDomain();
        if ($domain == 'gmail.com') {
            $rd = mt_rand(0, 1);
            if ($rd === 0) {
                $email = $zemail->generateRandomGmail(false);
            } else {
                $email = $zemail->getRandomGmailUser().'+'.$zemail->generateRandomUsername().'@gmail.com';
            }
        } elseif ($domain == 'googlemail.com') {
            $rd = mt_rand(0, 1);
            if ($rd === 0) {
                $email = $zemail->generateRandomGmail(false);
            } else {
                $email = $zemail->getRandomGmailUser().'+'.$zemail->generateRandomUsername().'@googlemail.com';
            }
        } elseif ($domain == 'outlook.com') {
            $email = $zemail->getRandomOutlookUser().'+'.$zemail->generateRandomUsername().'@outlook.com';
        } else {
            $email = $zemail->generateRandomUsername().'@'.$domain;
        }
        if ($store) {
            self::storeEmail($email);
        }

        return $email;
    }

    public static function generateRandomGmail($store = true): string
    {
        $zemail = new Premium;
        $uname = $zemail->getRandomGmailUser();
        $uname_len = strlen((string) $uname);
        $len_power = $uname_len - 1;
        $combination = 2 ** $len_power;
        mt_rand(1, $combination);
        $formatted = implode(' ', str_split((string) $uname));
        $uname_exp = explode(' ', $formatted);

        $bin = intval('');
        for ($i = 0; $i < $len_power; $i++) {
            $bin .= mt_rand(0, 1);
        }
        $bin = explode(' ', implode(' ', str_split(strval($bin))));

        $email = '';
        for ($i = 0; $i < $len_power; $i++) {
            $email .= $uname_exp[$i];
            if ($bin[$i] !== '' && $bin[$i] !== '0') {
                $email .= '.';
            }
        }
        $email .= $uname_exp[$i];
        $gmail_rand = mt_rand(1, 10);
        if ($gmail_rand > 5) {
            $email .= '@gmail.com';
        } else {
            $email .= '@googlemail.com';
        }
        if ($store) {
            self::storeEmail($email);
        }

        return $email;
    }

    public static function generateRandomOutlook($store = true): string
    {
        $zemail = new Premium;
        $email = $zemail->getRandomOutlookUser().'+'.$zemail->generateRandomUsername().'@outlook.com';
        if ($store) {
            self::storeEmail($email);
        }

        return $email;
    }

    private static function storeEmail(string $email): void
    {
        Log::create([
            'user_id' => auth()->user()->id,
            'ip' => request()->ip(),
            'email' => $email,
        ]);

        self::storeUsageLog($email);

        Cookie::queue('p_email', $email, 43800);
        $emails = Cookie::has('p_emails') ? unserialize(Cookie::get('p_emails')) : [];
        if (! in_array($email, $emails)) {
            self::incrementEmailStats();
            $emails[] = $email;
            Cookie::queue('p_emails', serialize($emails), 43800);
        }
    }

    public static function incrementEmailStats($count = 1): void
    {
        Meta::incrementEmailIdsCreated($count);
        self::incrementEmailIdsCreated($count);
    }

    public static function incrementMessagesStats($count = 1): void
    {
        Meta::incrementMessagesReceived($count);
        self::incrementMessagesReceived($count);
    }

    private function generateRandomUsername(): string
    {
        $start = json_decode((string) config('app.settings.configuration_settings'))->random_username_length_min ?? 0;
        $end = json_decode((string) config('app.settings.configuration_settings'))->random_username_length_max ?? 0;
        if ($start == 0 && $end == 0) {
            return $this->generatePronounceableWord();
        }

        return $this->generatedRandomBetweenLength($start, $end);
    }

    protected function generatedRandomBetweenLength($start, $end): string
    {
        $length = random_int($start, $end);

        return $this->generateRandomString($length);
    }

    private function getRandomDomain()
    {
        $domains = json_decode((string) config('app.settings.configuration_settings'))->premium_domains ?? [];
        $count = count($domains);

        return $count > 0 ? $domains[random_int(1, $count) - 1] : '';
    }

    private function getRandomGmailUser()
    {
        $gmailusername = json_decode((string) config('app.settings.configuration_settings'))->premium_gmailUsernames ?? [];
        $count = count($gmailusername);

        return $count > 0 ? $gmailusername[random_int(1, $count) - 1] : '';
    }

    private function getRandomOutlookUser()
    {
        $outlook_username = json_decode((string) config('app.settings.configuration_settings'))->premium_outlookUsernames ?? [];
        $count = count($outlook_username);

        return $count > 0 ? $outlook_username[random_int(1, $count) - 1] : '';
    }

    private function generatePronounceableWord(): string
    {
        $c = 'bcdfghjklmnprstvwz'; // consonants except hard to speak ones
        $v = 'aeiou';              // vowels
        $a = $c.$v;                // both
        $random = '';
        for ($j = 0; $j < 2; $j++) {
            $random .= $c[random_int(0, strlen($c) - 1)];
            $random .= $v[random_int(0, strlen($v) - 1)];
            $random .= $a[random_int(0, strlen($a) - 1)];
        }

        return $random;
    }

    private function generateRandomString(int $length = 10): string
    {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyz';
        $charactersLength = strlen($characters);
        $randomString = '';
        for ($i = 0; $i < $length; $i++) {
            $randomString .= $characters[random_int(0, $charactersLength - 1)];
        }

        return $randomString;
    }

    /**
     * Stats Handling Functions
     */
    public static function incrementEmailIdsCreated($count = 1): void
    {
        $user = Auth::user();
        if (! $user) {
            return;
        }

        $usageLog = UsageLog::query()->firstOrCreate(['user_id' => $user->id], ['ip_address' => request()->ip()]);

        $usageLog->increment('emails_created_count', $count);
    }

    public static function incrementMessagesReceived($count = 1): void
    {
        $user = Auth::user();
        if (! $user) {
            return;
        }

        $usageLog = UsageLog::query()->firstOrCreate(['user_id' => $user->id], ['ip_address' => request()->ip()]);

        $usageLog->increment('emails_received_count', $count);
    }

    public static function storeUsageLog($email): void
    {
        try {
            $user = Auth::user();
            if (! $user) {
                return;
            }

            $ip = request()->ip();
            $usageLog = UsageLog::query()->firstOrCreate(['user_id' => $user->id], ['ip_address' => $ip]);

            $history = $usageLog->emails_created_history ?? [];
            if (! in_array($email, $history)) {
                $history[] = $email;
                // $usageLog->emails_created_count += 1;
                $usageLog->emails_created_history = $history;
                $usageLog->save();
            }
        } catch (Exception $exception) {
            Log::error($exception->getMessage());
        }

    }
}
