<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use App\Modules\Clients\Client;
use App\Modules\Clients\Customer;
use App\Modules\Clients\SiteUser;
use App\Modules\Bookings\Task;
use App\Modules\Bookings\Project;
use App\Modules\Bookings\SaleLead;
use App\Modules\Bookings\Support;
use App\Modules\Bookings\Comment;
use App\Modules\Admin\EmailSettings;
use App\Console\Commands\AbstractCommand;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Mail;
use App\Seraph\Services\GlobalMailerService;

class EmailBookingTest extends AbstractCommand
{
    /**
     * A friendly name of the command that will be used when loggin messages.
     *
     * @var string
     */
    protected $command_title = 'Email Bookings Test';

    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'bookings:emailtest';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Checks the predefined mailbox for new emails and creates a new task automatically.';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return void
     */
    public function fire()
    {
      $cc_assignee = null;

        if (DBconfig::get('emails.enabled')) {
            // Fetch a list of all the email addresses to check for
            $settings = EmailSettings::all();

            foreach ($settings as $setting) {
                // Try to connect to the server
                $server = new \Fetch\Server($setting->server, $setting->port, $setting->protocol);
                $server->setParam('DISABLE_AUTHENTICATOR', 'GSSAPI');
                $server->setAuthentication($setting->username, $setting->password);
                $messages = $server->search('UNSEEN', 5);
		die();
                if (count($messages)) {
                    foreach ($messages as $message) {
                        $message->setFlag('Seen');
                        $from = $message->getAddresses('from');
                        $cc = $message->getAddresses('cc');
                        if (! isset($from['name'])) {
                            $from['name'] = 'Unknown sender';
                        }

                        // Login as admin for every message
                        Auth::loginUsingId(1);

                        // Check if the email comes from a member of staff
                        $staff = User::where('email', 'like', $from['address'])->first();

                        // check if cc is a member of staff - if true then he/she will be assisnged the job
                        if($cc != null) {
                          $cc_assignee = User::where('email', 'like', $cc[0]['address'])->first();
                        }

                        // If the email came from a member of staff, scrap for a previous email address
                        if ($staff) {
                            // If automatic replay from staff, ignore message
                            if (preg_match('/^Automatic reply:/i', $message->getSubject()) === 1) {
                                $this->info('Skip automatic reply from '.$staff->name.'\'s email.');
                                continue;
                            }

                            $this->info('Message from staff');

                            // Login as this staff member
                            Auth::loginUsingId($staff->id);
                        }

                        // Checking the email subject for tags
                        // such as [1234] where the number is the task id
                        if ($this->hasValidTags($message->getSubject())) {
                            $task_id = $this->getTaskId($message->getSubject());

                            if (strcasecmp($task_id[0], "p") == 0) {
                              $project_id = substr($task_id , 1);
                              if( !$this->addProjectComment($project_id, $this->removeThreadedEmails($message->getMessageBody(false)), $message->getAddresses('to', true), $from, $staff)) {
                                $subject = $message->getSubject();
                                GlobalMailerService::send('emails.auto_bookings.project_problem', 
                                    ['project_id' => $project_id, 'body_html' => $message->getMessageBody(true)], 
                                    function ($mail) use ($from, $subject, $setting) {
                                        $mail->to($from['address'])
                                             ->subject($subject)
                                             ->from($setting->email, $setting->smtp_from_name ?: $setting->name);
                                    }
                                );
                              }
                            }
                            else if (! $this->addComment($task_id, $this->removeThreadedEmails($message->getMessageBody(false)), $message->getAddresses('to', true), $from, $staff)) {
                                // Failed, book as a normal message
                                $this->newBooking($message, $from, $setting, $cc_assignee);
                            }

                            continue;
                        }

                        // At this point the email does not contain any tags,
                        // that means it will be treated as a new email.
                        // -------------------------------------------------

                        if ($staff) {
                            // Check to see if the email has been forwarded:
                            if (property_exists($message->getHeaders(), 'references')) {
                                // this has been forwarded
                                // scanning for first From: email address
                                if (! $this->parseEmailBodyForAddress($message, $from)) {
                                    // An email could not be found
                                    $this->error('Could not find an email address from email received from '.$from['address'].'. Booking under the original sender.');
                                    // continue; // allow it to continue and book the message in either case
                                }
                            } else {
                                // This has not been forwarded
                                // and it comes from a member of staff
                                // skipping for now
                                $this->error('This email comes from a member of staff, '.$from['address'].', and it has not been forwarded!');
                                $this->info('Booking in as a normal support job');
                            }
                        }

                        $this->newBooking($message, $from, $setting, $cc_assignee);
                    }
                }
            } //-- endforeach --//
        } else {
            $this->info('Automatic booking is disabled.');
        }
    }

    /**
     * Get the console command arguments.
     *
     * @return array
     */
    protected function getArguments()
    {
        return [
            ['type', InputArgument::OPTIONAL, 'The type of tasks that should be booked in', 'support'],
        ];
    }

    /**
     * Get the console command options.
     *
     * @return array
     */
    protected function getOptions()
    {
        return [

        ];
    }

    /**
     *	Checks to see wether the email subject contains
     *  a valid task id.
     *
     *	@return bool
     */
    protected function hasValidTags($subject)
    {
        return preg_match('/\[([P|p0-9]+)\]/', $subject); // nadeem - modified to accept project comments - denoted by a p inside the ie. [pXXXX] - upper or lower case
    }

    /**
     *	Retrieves the task id from the subject.
     *
     *	@return string
     */
    protected function getTaskId($subject)
    {
        preg_match('/\[([0-9]+)\]/', $subject, $matches);


        if (count($matches) > 1) {
            return $matches[1];
        }

        preg_match('/\[([p|P][0-9]+)\]/', $subject, $matches);
        if (count($matches) > 1) {
            return $matches[1];
        }
        return;
    }

    /**
     *	Stores a reply as a comment within the task.
     *
     *	@return bool
     */
    protected function addComment($task_id, $message, $emailed_to, $from, $staff)
    {
        $task = Task::find($task_id);

        // Clean the reply messages
        $message = $this->cleanReplyEmail($message);

        if ($task) {
            $comment = Comment::create([
                'related_id'     => $task_id,
                'related_type'   => 'task',
                'message'        => $message,
                'emailed_to'     => $emailed_to,
                'customer_reply' => $staff ? 1 : 0, // Mark it as a customer reply
                'from_name'      => isset($from['name']) ? $from['name'] : null,
                'from_address'   => isset($from['address']) ? $from['address'] : null,
                'auto_booked'    => 1, // Mark it as autobooked
                'public'         => 1, // Mark as public since it's from email/customer communication
            ]);

            if (! $comment->id) {
                $this->error('Unable to save comment for task id ['.$task_id.']');

                return false;
            }

            $this->info('Wrote a new comment for task id ['.$task_id.']');

            return true;
        } else {
            $this->error('Invalid task id ['.$task_id.'] in the subject line');

            return false;
        }
    }

    /**
     *	Stores a reply as a comment within the project.
     *
     *	@return bool
     */
    protected function addProjectComment($project_id, $message, $emailed_to, $from, $staff)
    {
        $project = Project::find($project_id);

        // Clean the reply messages
        $message = $this->cleanReplyEmail($message);

        if ($project) {
            $comment = Comment::create([
                'related_id'     => $project_id,
                'related_type'   => 'project',
                'message'        => $message,
                'emailed_to'     => $emailed_to,
                'customer_reply' => $staff ? 1 : 0, // Mark it as a customer reply
                'from_name'      => isset($from['name']) ? $from['name'] : null,
                'from_address'   => isset($from['address']) ? $from['address'] : null,
                'auto_booked'    => 1, // Mark it as autobooked
                'public'         => 1, // Mark as public since it's from email/customer communication
            ]);

            if (! $comment->id) {
                $this->error('Unable to save comment for project id ['.$project_id.']');

                return false;
            }

            $this->info('Wrote a new comment for project id ['.$project_id.']');

            return true;
        } else {
            $this->error('Invalid project id ['.$project_id.'] in the subject line');

            return false;
        }
    }

    /**
     *	Creates a new remote support job.
     *
     *	@return bool
     */
    protected function newTask($message, $client, $setting, $cc_assignee)
    {
        if (! $client->id) {
            $this->error('Failed to create new task because client id is missing.');

            return false;
        }

        // Original from address
        $original_from = $message->getAddresses('from');

        // Assign a default status
        $status = Status::repairs()->first();

        if (! $status) {
            $this->error('No statuses defined');
        }

        // Strip the task from the subject
        // this only applies if an invalid task id is listed
        $subject = $message->getSubject();
        $task_id = $this->getTaskId($subject);
        if ($task_id) {
            $subject = str_replace('['.$task_id.']', '', $subject);
        }

        $search_tags = [
            '@<style[^>]*?>.*?</style>@si',    // Strip style tags properly
            '@<base.*?>@si',    // Strip base tags properly
            '@<![\s\S]*?--[ \t\n\r]*>@',        // Strip multi-line comments including CDATA
        ];
        $body_html = preg_replace($search_tags, '', $message->getMessageBody(true));

        $data = [
            'title'       => $subject,
            'problem'     => $body_html,
            'status_id'   => $status->id,
            'auto_booked' => 1,
            'severity_id' => 2, // Normal,
            'subtype_id'  => 1,
        ];

        if ($client->site_id) {
            // This belongs to a business
            $data['client_id'] = $client->site->client_id;
            $data['client_type'] = 'client';
            $data['site_user_id'] = $client->id;
            $data['site_id'] = $client->site_id;
        } else {
            // This is a regular customer
            $data['client_id'] = $client->id;
            $data['client_type'] = 'customer';
        }

        if($cc_assignee) {
          $data['assigned_to'] = $cc_assignee->id;
        }

        $task = Support::create($data);

        if (! $task->id) {
            $this->error('Unable to create new task '.$task->errors->first());

            return false;
        } else {
            // We are trying to save attachments
            // and we will wrap it in a try catch as we
            // do not want to stop the execution of the code if it fails
            try {
                $this->saveAttachments($task, $message);
            } catch (Exception $e) {
                $this->error($e->__toString());
            }

            // Send an email back to the person who opened the task
            // ...
            if (strpos($original_from['address'], 'nagios@') === false) {
                GlobalMailerService::send('emails.auto_bookings.confirmation', 
                    ['task' => $task, 'body_html' => $message->getMessageBody(true)], 
                    function ($mail) use ($original_from, $task, $subject, $setting) {
                        $mail->to($original_from['address'])
                             ->subject('['.$task->id.'] '.$subject)
                             ->from($setting->email, $setting->smtp_from_name ?: $setting->name);
                    }
                );
            }

            $this->info('Successfully created a new task with id ['.$task->id.']');

            return true;
        }
    }

    /**
     *	Creates a new sales lead.
     *
     *	@return bool
     */
    protected function newSaleLead($message, $client, $setting)
    {
        if (! $client->id) {
            $this->error('Failed to create new task because client id is missing.');

            return false;
        }

        // Original from address
        $original_from = $message->getAddresses('from');

        // Assign a default status
        $status = Status::saleLeads()->first();

        if (! $status) {
            $this->error('No statuses defined');
        }

        // Strip the task from the subject
        // this only applies if an invalid task id is listed
        $subject = $message->getSubject();
        $task_id = $this->getTaskId($subject);
        if ($task_id) {
            $subject = str_replace('['.$task_id.']', '', $subject);
        }

        $search_tags = [
            '@<style[^>]*?>.*?</style>@siU',    // Strip style tags properly
            '@<![\s\S]*?--[ \t\n\r]*>@',        // Strip multi-line comments including CDATA
        ];
        $body_html = preg_replace($search_tags, '', $message->getMessageBody(true));

        $data = [
            'title'       => $subject,
            'problem'     => $body_html,
            'status_id'   => $status->id,
            'auto_booked' => 1,
            'onsite_end'  => Carbon::now()->addDays(7)->toDateTimeString(),
        ];

        if ($client->site_id) {
            // This belongs to a business
            $data['client_id'] = $client->site->client_id;
            $data['client_type'] = 'client';
            $data['site_user_id'] = $client->id;
            $data['site_id'] = $client->site_id;
        } else {
            // This is a regular customer
            $data['client_id'] = $client->id;
            $data['client_type'] = 'customer';
        }

        $task = SaleLead::create($data);

        if (! $task->id) {
            $this->error('Unable to create new task '.$task->errors->first());

            return false;
        } else {

            // We are trying to save attachemnts
            // and we will wrap it in a try catch as we
            // do not want to stop the execution of the code if it fails
            try {
                $this->saveAttachments($task, $message);
            } catch (Exception $e) {
                $this->error($e->__toString());
            }

            // Send an email back to the person who opened the task
            // ...
            if ($original_from['address'] != 'nagios@compuah1.miniserver.com') {
                GlobalMailerService::send('emails.auto_bookings.sales.confirmation', 
                    ['task' => $task, 'body_html' => $message->getMessageBody(true)], 
                    function ($mail) use ($original_from, $task, $subject, $setting) {
                        $mail->to($original_from['address'])
                             ->subject('['.$task->id.'] '.$subject)
                             ->from($setting->email, $setting->smtp_from_name ?: $setting->name);
                    }
                );
            }
            $this->info('Successfully created a new task with id ['.$task->id.']');

            return true;
        }
    }

    /**
     *	Retrieves a client by email address.
     *
     *	@return \App\Modules\Clients\SiteUser - if this is a business customer
     *			\App\Modules\Clients\Customer - if this is a regular customer
     *			FALSE - if not found
     */
    protected function getClient($email)
    {
        $suffix = explode('@', $email);

        $this->info('Searching for '.$email.' within site users');

        // Check to see if the email address exists in our database
        $user = SiteUser::where('email', 'like', $email)->first();

        // A list of keywords that contain public email addresses
        $keywords = ['gmail', 'yahoo', 'hotmail', 'outlook', 'btinternet'];

        // Check for the suffix to see if the company exists
        if (! $user && $this->strposarray($suffix[1], $keywords) === false) {
            $user = SiteUser::where('email', 'like', '%'.$suffix[1])->first();
        }

        if (! $user) {
            $this->info('Could not find '.$email.' within site users');

            $client = Customer::where('email', 'like', $email)->first();

            if (! $client) {
                return false;
            }

            return $client;
        }

        return $user;
    }

    /**
     *	Procedure contains the logic for creating
     *	a new booking.
     */
    protected function newBooking($message, $from, $setting, $cc_assignee)
    {

        // Book this message in
        $this->info('Preparing to book the message in from '.$from['address']);

        // Retrieve the client data
        $client = $this->getClient($from['address']);

        if (! $client) {
            // Create a new client
            $this->info('Attempting to create a new client');
            $client = $this->createClient($from['name'], $from['address']);
        }

        // Create the task
        if ($setting->booking_type == 'sale_lead') {
            $this->newSaleLead($message, $client, $setting);
        } elseif ($setting->booking_type == 'support') {
            $this->newTask($message, $client, $setting, $cc_assignee);
        }
    }

    /**
     *	Creates a new client.
     *
     *	@return \App\Modules\Customer or FALSE on fail
     */
    protected function createClient($name, $email)
    {
        $ex_name = explode(' ', $name);
        $customer = Customer::create([
            'first_name' => isset($ex_name[0]) ? $ex_name[0] : 'Unknown',
            'last_name'  => isset($ex_name[1]) ? $ex_name[1] : 'customer',
            'email'      => $email,
            'phone'      => '0',
            'postcode'   => 'X',
            'town'       => 'Oxford',
        ]);

        if ($customer->id) {
            $this->info('Successfully created a new customer '.$name.' <'.$email.'>');

            return $customer;
        } else {
            $this->error('Failed to create customer '.$name.' <'.$email.'>: '.$customer->errors->first());

            return false;
        }
    }

    protected function parseEmailBodyForAddress($message, &$from)
    {
        // From:[^<]+<(?P<Email>[^@]+@(?P<Domain>[^>]+))
        preg_match_all('/(?:From: \s*([^\r\n]*))/', $message->getMessageBody(), $result);
        // print_r($result);
        // die;

        if (! isset($result[1])) {
            return false;
        } // We need to start lookin the second array which contains all the addresses without the 'FROM:' text

        $result = $result[1];

        for ($i = 0; $i < count($result); $i++) {
            if (isset($result[$i])) {
                $result[$i] = str_replace('mailto:', '', $result[$i]);
                $result[$i] = str_replace('[', '<', $result[$i]);
                $result[$i] = str_replace(']', '>', $result[$i]);

                // Fix emails being forwarded form apple mail
                $result[$i] = preg_replace('/<mailto:([^>])+>/', '', $result[$i]);

                // Try to parse the string
                try {
                    $address = imap_rfc822_parse_adrlist($result[$i], 'UNKNOWN');
                } catch (Exception $e) {
                    $this->error($e->__toString());
                    continue;
                }

                if (isset($address[0]) && $address[0]->host != 'UNKNOWN') {

                    // Override the from details with what we've found in the email body
                    $from = [
                        'address' => $address[0]->mailbox.'@'.$address[0]->host,
                        'name'    => property_exists($address[0], 'personal') ? $address[0]->personal : 'Unknown sender',
                    ];

                    return true;
                } else {
                    $this->error('Unable to parse email address, '.$result[$i]);
                }
            } else {
                $this->error('Email was forwarded, but the original address could not be found.');
            }
        }

        return false;
    }

    protected function strposarray($haystack, $needles = [], $offset = 0)
    {
        $chr = [];
        foreach ($needles as $needle) {
            $res = strpos($haystack, $needle, $offset);
            if ($res !== false) {
                $chr[$needle] = $res;
            }
        }
        if (empty($chr)) {
            return false;
        }

        return min($chr);
    }

    /**
     * Saves all the attachments to the task.
     *
     * @param App\Modules\Bookings\Task $task
     * @param Fetch\Message 			$message
     *
     * @return null
     */
    protected function saveAttachments($task, $message)
    {
        $attachments = $message->getAttachments();

        $this->info('Getting attachments');

        // Check to see if any attachments exists
        if (! $attachments || ! $task) {
            return false;
        }

        $this->info('Has attachments');

        // The library returns a single object instead of an array
        // if only one attachment is found
        // so we turn it into an array
        if (! is_array($attachments)) {
            $attachments = [$attachments];
        }

        // We are now saving each attachment to the correct folder
        foreach ($attachments as $attachment) {
            $extension = last(explode('.', $attachment->getFileName()));

            $data = [
                'name'         => $attachment->getFileName(),
                'size'         => $attachment->getSize(),
                'filename'     => str_random(32).'.'.$extension,
                'related_id'   => $task->id,
                'related_type' => 'task',
            ];

            // attempt to move the file to the files folder
            $savePath = Config::get('files.path').'/'.$data['filename'];
            if ($attachment->saveAs($savePath)) {
                $file = Files::create($data);

                if (! $file->exists) {
                    $this->error('Failed to save the file to the database '.$data['name']);

                    // Delete the file from the disk
                    File::delete($savePath);
                }
            } else {
                $this->error('Failed to save the attachment '.$data['name'].' at the location: '.$savePath);
            }
        }
    }

    /**
     * Strips quotes (older messages) from a message body.
     *
     * This function removes any lines that begin with a quote character (>).
     * Note that quotes in reply bodies will also be removed by this function,
     * so only use this function if you're okay with this behavior.
     *
     * @param $message (string)
     *   The message to be cleaned.
     * @param $plain_text_output (bool)
     *   Set to TRUE to also run the text through strip_tags() (helpful for
     *   cleaning up HTML emails).
     *
     * @return (string)
     *   Same as message passed in, but with all quoted text removed.
     *
     * @see http://stackoverflow.com/a/12611562/100134
     */
    public function cleanReplyEmail($message, $plain_text_output = false)
    {
        // Strip markup if $plain_text_output is set.
        if ($plain_text_output) {
            $message = strip_tags($message);
        }

        // Remove quoted lines (lines that begin with '>').
        $message = preg_replace("/(^\w.+:\n)?(^>.*(\n|$))+/mi", '', $message);

        // Remove lines beginning with 'On' and ending with 'wrote:' (matches
        // Mac OS X Mail, Gmail).
        $message = preg_replace('/^(On).*(wrote:).*$/sm', '', $message);

        // Remove lines like '----- Original Message -----' (some other clients).
        // Also remove lines like '--- On ... wrote:' (some other clients).
        $message = preg_replace('/^---.*$/mi', '', $message);

        // Remove lines like '____________' (some other clients).
        $message = preg_replace('/^____________.*$/mi', '', $message);

        // Remove blocks of text with formats like:
        //   - 'From: Sent: To: Subject:'
        //   - 'From: To: Sent: Subject:'
        //   - 'From: Date: To: Reply-to: Subject:'
        $message = preg_replace('/From:.*^(To:).*^(Subject:).*/sm', '', $message);

        // Remove any remaining whitespace.
        $message = trim($message);

        return $message;
    }

    public function removeThreadedEmails($message)
    {
        $re = '/(FROM:) *[a-zA-Z0-9 _]*[\\r]?\\n/';
        // preg_split splits the message into an array of individual emails
        $individalMessages = preg_split($re, $message);

        if(!empty($individalMessages)) {
            // we want to return the first
            return $individalMessages[0];
        }
        // otherwise we return the original message
        return $message;
    }

}
