<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Models\ScheduleCampaign;
use App\Models\ScheduleCampaignStat;
use App\Models\ScheduleCampaignStatLog;
use App\Models\SendingServer;
use App\Models\Contact;
use App\Http\Helper\Helper;
use DB;

class RunCampaigns extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'run:campaigns {id?} {thread_no?}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Run scheduled campaigns';

    /**
     * Execute the console command.
     */
    public function handle()
  {

    // Save timestamp when cron last executed
    $carbon = new \Carbon\Carbon();
    DB::table('settings')->whereId(config('custom.app_id'))->update([
      'attributes->cron_timestamp' => $carbon->now(),
    ]);
    $id = $this->argument('id');
    if($id) {
      $thread_no = $this->argument('thread_no');

      // Get a scheduled campaign need to send
      $schedule = ScheduleCampaign::findOrFail($id);

      // Check sending status and set status to completed
      $this->checkCampaignStatus($schedule->id);

      // For some reason if thread_no > total_threads
      if($thread_no > $schedule->total_threads) {
        ScheduleCampaign::whereId($schedule->id)->update(['thread_no' => 1, 'status' => 'Resume']);
        exit;
      }

      // If campaign is set for hourly limit then updte the time to 1 hour laterl send further
      $campaign_sending_speed = json_decode($schedule->sending_speed);
      if($campaign_sending_speed->limit) {
        $initial_limit = $campaign_sending_speed->initial_limit??$campaign_sending_speed->limit;
        $next_sending_datetime_minutes =  round(($initial_limit/$campaign_sending_speed->limit) * 60);
        $next_datetime = $carbon->parse($carbon->now(), config('app.timezone'))->addMinutes($next_sending_datetime_minutes);
        ScheduleCampaign::whereId($schedule->id)->update(['status' => 'RunningLimit', 'send_datetime' => $next_datetime]);
      } else {
        $this->setScheduleStatus($schedule->id, 'Running');
      }

      // Set notification
      if($schedule->sent == 0) {
        $notification_name = $schedule->name . ' ' .__('app.campaign_started');
        $attributes = [
          'file' => null
        ];
        $notification_attributes = json_encode($attributes);
        $notification = [
          'name' => $notification_name,
          'type' => 'other',
          'attributes' => $notification_attributes,
          'app_id' => $schedule->app_id,
          'user_id' => $schedule->user_id
        ];
        \App\Models\Notification::create($notification);
      }

      $this->sendCampaign($schedule, $thread_no);


    } else {
      $this->runCampaign();
    }
  }

  /**
   * Send campaign to subscribers
  */
  protected function sendCampaign($schedule, $thread_no)
  {
    // Retrun array of sending servers
    $sending_servers = SendingServer::getActiveSeningServers(explode(',', $schedule->sending_server_ids), 'array');

    // If no sending server then no need to do anything
    if(empty($sending_servers)) {
      $this->setScheduleStatus($schedule->id, 'System Paused');
    }

    // Will be use to reset the sending_server array when  all picked
    $sending_servers_data = $sending_servers;

    // Get schedule stat info will be use later
    $schedule_stat = ScheduleCampaignStat::whereScheduleCampaignId($schedule->id)->first();

    $settings = DB::table('settings')->whereId(config('custom.app_id'))->first();
    $tracking = $settings->tracking == 'enabled' ? true : false;
    $app_url = $settings->app_url;

    // mail headers will be set for admin 
    $mail_headers = json_decode(DB::table('users')->select('mail_headers')->whereAppId($schedule->app_id)->whereParentId(0)->value('mail_headers'), true);

    $limit = json_decode($schedule->sending_speed)->limit;
    if(!empty($limit) && trim($limit) != '') {
      $condition = $thread_no;
      $wait = floor(3600 / $limit); // divide on 1 hour
    } else {
      $condition = $schedule->total_threads;
      $wait = false;
    }

    // Need to execute the loop according to the threads start with thread no
    for($file_no=$thread_no; $file_no<=$condition; $file_no+=$schedule->threads) {
      $schedule->increment('thread_no');

      $path_schedule_campaign = str_replace('[user-id]', $schedule->user_id, config('custom.path_schedule_campaign'));
      $file = $path_schedule_campaign.$schedule->id.DIRECTORY_SEPARATOR. $file_no . '.csv';

      if(file_exists($file)) {
        $file_offsets = DB::table('file_offsets')->where('file', $file)->first();
        // Get file offset
        $offset_file = empty($file_offsets) ? 0 : $file_offsets->offset;
        if($offset_file) {
          // Delete entry after picking up offset may be use in next time
          DB::table('file_offsets')->where('file', $file)->delete();
        }
        $reader = \League\Csv\Reader::createFromPath($file, 'r');
        // Make associative array with names and skip header
        $reader->setHeaderOffset(0);
        // It may possible campaing paused in past then it wil continue form the same recored
        $stmt = (new \League\Csv\Statement())->offset($offset_file);
        //$contacts_csv = $reader->getRecords();
        $contacts_csv = $stmt->process($reader);

        $total_records = Helper::getCsvCount($file);

        foreach ($contacts_csv as $offset => $contact_csv) {
          // Need to stop the campaign instantally as campaign status is paused
          // Decrement should work for latest value
          $schedule = ScheduleCampaign::findOrFail($schedule->id);
          if ($schedule->status == 'Paused' || $schedule->status == 'System Paused') {
            // Store record no into db to execuet the file from same location instead from start when campaing resumed
            DB::table('file_offsets')->insert([
              'file' => $file,
              'offset' => --$offset // Minus 1 before to save other wise next recored will be picked when resumed
            ]);

            // The same file should be select when resumed
            $schedule->decrement('thread_no', 1);
            if($limit) {
              $carbon = new \Carbon\Carbon();
              $send_datetime = $carbon->parse($carbon->now(), config('app.timezone'));
              ScheduleCampaign::whereId($schedule->id)->update(['send_datetime' => $send_datetime]);
            }
            // Stop Campaign sending
            exit;
          }

          // Sending servers info that assigned previously to reset the sending servers when all picked
          if(empty($sending_servers)) {
            $sending_servers = $sending_servers_data;
          }

          // get sending server
          $sending_server = array_shift($sending_servers);

          $from_name = (empty($contact_csv['FROM_NAME']) || trim($contact_csv['FROM_NAME']) === '') ? $sending_server['from_name'] : $contact_csv['FROM_NAME'];
          $from_email = (empty($contact_csv['FROM_EMAIL']) || trim($contact_csv['FROM_EMAIL']) === '') ? $sending_server['from_email'] : $contact_csv['FROM_EMAIL'];
          $reply_email = (empty($contact_csv['REPLY_EMAIL']) || trim($contact_csv['REPLY_EMAIL']) === '') ? $sending_server['reply_email'] : $contact_csv['REPLY_EMAIL'];

          $sending_domain = Helper::getSendingDomainFromEmail($from_email);
          // if no domain found
          try {
            $domain = $sending_domain->protocol.$sending_domain->domain;
          } catch(\Exception $e) {
            \Log::error('run:campaigns => '.$e->getMessage());
            continue;
          }
          $message_id = Helper::getCustomMessageID($domain);

          // Try to connect with SendingServer
          $connection = Helper::configureSendingNode($sending_server['type'], $sending_server['sending_attributes'], $message_id);
          if($connection['success']) {
            $contact = Contact::whereId($contact_csv['CONTACT_ID'])->with('customFields')->first();

            if(!empty($sending_server['tracking_domain'])) {
              $tracking_domain = $sending_server['tracking_domain'];
            } else {
              $tracking_domain = Helper::getAppURL();
            }

            

            $from_name = Helper::replaceSpintags($from_name);
            $reply_email = filter_var($reply_email, FILTER_VALIDATE_EMAIL) ? $reply_email : null;

            // Data that will be use to replce the system variables
            $data_values = [
              'sender-name'    => $from_name,
              'sender-email'   => $from_email,
              'domain'         => $tracking_domain,
              'message-id'     => $message_id,
            ];

            // Replace system variables
            $subject = Helper::replaceSystemVariables($contact, $contact_csv['EMAIL_SUBJECT'], $data_values);
            $content = Helper::replaceSystemVariables($contact, $contact_csv['BROADCAST'], $data_values);

            // Create ScheduleCampaignStatLog and the id would be use to track the email
            $schedule_campaign_stat_log_data = [
              'schedule_campaign_stat_id' => $schedule_stat->id,
              'message_id' => $message_id,
              'email' => $contact_csv['EMAIL'],
              'list' => $contact_csv['LIST'],
              'sending_server' => $sending_server['name'],
            ];

            try {
              $schedule_campaign_stat_log = ScheduleCampaignStatLog::create($schedule_campaign_stat_log_data);
            } catch(\Exception $e) {
                  continue;
            }

            // If tracking is enabled, for TEXT format the tracing will not work
            if($tracking) {
              // click tracking should be before track_opens becuase don't want to convert that url
              $content = Helper::convertLinksForClickTracking($schedule_campaign_stat_log->id, $tracking_domain, $content, $app_url, '/click/');

              // Make open tracking url and pixel
              $track_open = $tracking_domain.'/open/'.base64_encode($schedule_campaign_stat_log->id);
              $content .= "<div style='float:left; clear:both; font-family:Arial; margin:40px auto; width:100%; line-height:175%; font-size:11px; color:#434343'><img border='0' src='".$track_open."' width='1' height='1' alt=''></div>";
            }

            // If sending type that supported by framework will be send with a same way
            if(in_array($sending_server['type'], Helper::sendingServersFramworkSuported())) {

                $message = new \Symfony\Component\Mime\Email();
                $message->from(new \Symfony\Component\Mime\Address($from_email, "$from_name"));
                $message->to($contact_csv['EMAIL']);
                $message->subject($subject);
                !empty($reply_email) ? $message->replyTo($reply_email) : '';
                if(!empty($sending_server['bounce']['email'])) {
                  $message->returnPath($sending_server['bounce']['email']);
                }

                // adding the envelope becuase bounce email having issue
                $envelope = new \Symfony\Component\Mailer\Envelope(
                    new \Symfony\Component\Mime\Address($from_email, "$from_name"), // From email
                    [new \Symfony\Component\Mime\Address($contact_csv['EMAIL'])] // Envelope recipient(s)
                );

                $headers= $message->getHeaders();
                $headers->addIdHeader('Message-ID', $message_id);
                // Header will use to process the bounces and fbls etc.
                $headers->addTextHeader('RZ-Type-ID', "campaign-{$schedule_campaign_stat_log->id}-{$schedule->app_id}");
                // Required header for good inboxing
                $headers->addTextHeader('List-Unsubscribe', sprintf('<mailto:%s?subject=unsubscribe>', urlencode($from_email)));
                $headers->addTextHeader('List-Unsubscribe-Post', 'List-Unsubscribe=One-Click');

                // Header will be use to process reports for Amazon
                if($sending_server['type'] == 'amazon_ses_api' && !empty(json_decode($sending_server['sending_attributes'])->process_reports) && json_decode($sending_server['sending_attributes'])->process_reports == 'yes') {
                  if(!empty(json_decode($sending_server['sending_attributes'])->amazon_configuration_set)) {
                    $headers->addTextHeader('X-SES-CONFIGURATION-SET', json_decode($sending_server['sending_attributes'])->amazon_configuration_set);
                  }
                }

                if(!empty($mail_headers)) {
                  foreach($mail_headers as $header_key => $header_value) {
                    $header_value = Helper::replaceSpintags($header_value);
                    $header_value = Helper::replaceCustomFields($header_value, $contact->customFields);
                    $header_value = Helper::replaceSystemVariables($contact, $header_value, $data_values);
                    $header_value = Helper::spinTaxParser($header_value);

                    $header_key = Helper::replaceHyphen($header_key);

                    $message->getHeaders()->addTextHeader($header_key, $header_value);
                  }
                }

                $message->html($content);
                $message->text(htmlspecialchars_decode(strip_tags($content)));


                /*if($sending_server['type'] == 'smtp' && $sending_domain->verified_key) {
                  $privateKey = $sending_domain->private_key;
                  $selector = $sending_domain->dkim;

                  $message = Helper::attachSigner($message, $privateKey, $sending_domain->domain, $selector);
                }*/

                // Will be check either need to load sending servers again or not
                $load_sending_servers = false;
                try {
                  $connection['transport']->send($message, $envelope);
                  $status = 'Sent';
                } catch(\Exception $e) {
                  \Log::error('run:campaigns => '.$e->getMessage());
                  $status = 'Failed';
                }
            } elseif($sending_server['type'] == 'sendgrid_api') {
              $message = new \SendGrid\Mail\Mail();
              $message->setFrom($from_email, "$from_name");
              !empty($reply_email) ? $message->setReplyTo($reply_email) : '';
              $message->addTo($contact_csv['EMAIL']);
              $message->setSubject($subject);
              $message->addContent("text/html", $content);
              // Custom variable that will use to process the devlivery reports
              $message->addCustomArg('rz_message_id', $message_id);

              $sendgrid = new \SendGrid(\Crypt::decrypt(json_decode($sending_server['sending_attributes'])->api_key));
              $load_sending_servers = false;
              try {
                $response = $sendgrid->send($message);
                // status start with 2 consider as sent
                if(substr($response->statusCode(), 1) == 2) {
                  $status = 'Sent';
                } else {
                  $status = 'Failed';
                }
              } catch(\Exception $e) {
                \Log::error('run:campaigns-sendgrid => '.$e->getMessage());
                $status = 'Failed';
              }
            }

            $sending_server_data = Helper::updateSendingServerCounters($sending_server['id']);

            if($sending_server_data['sending_server_paused']) {
              $load_sending_servers = true;
            }

            // update sent counter with 1 for both tables
            $schedule->increment('sent');
            $schedule_stat->increment('sent');

            // Update status
            ScheduleCampaignStatLog::whereId($schedule_campaign_stat_log->id)->update(['status' => $status]);

            // Check sending status and set status to completed
            $this->checkCampaignStatus($schedule->id);

          } else {
            // If sending server connection failed then need to update it as system inactive
            // Removing single and double quote due to output issue with js alert at frontend 
            SendingServer::whereId($sending_server['id'])->update(['status' => 'System Inactive', 'notification' => str_replace( ["'",'"'], '', explode('.',$connection['msg'])[0] )]); 

            $load_sending_servers = true;
          }

          if($load_sending_servers) {
            // Retrun new array of sending servers after make a sending server as system inactive
            $sending_servers_data = SendingServer::getActiveSeningServers(explode(',', $schedule->sending_server_ids), 'array');

            // If no sending server then no need to do anything
            if(empty($sending_servers_data)) {
              $this->setScheduleStatus($schedule->id, 'System Paused');
            }
          }

          if($wait) sleep($wait);
        } // End foreach $contacts

        // Check sending status and set status to completed
        $this->checkCampaignStatus($schedule->id);
        try {
          // Delete File after process
          unlink($file);
        } catch (\Exception $e) {
          \Log::error('run:campaigns => '.$e->getMessage());
        }
      }
    } // End for loop $threads
  } // End Function

  /**
   * Needs to send parallel request so doing this
  */
  protected function runCampaign()
  {
    Helper::getUrl(Helper::getAppURL().'/run_campaigns');
  }

  /**
   * Check sending status of campign if all sent the set status to completed
  */
  protected function checkCampaignStatus($id)
  {
    // picking up fresh entries
    $schedule = ScheduleCampaign::findOrFail($id);
    if($schedule->sent >= $schedule->scheduled) {
        ScheduleCampaignStat::whereScheduleCampaignId($schedule->id)->update(['end_datetime' => \Carbon\Carbon::now()]);
        ScheduleCampaign::whereId($schedule->id)->update(['status' => 'Completed']);

        // Set notification
        $notification_name = $schedule->name . ' ' .__('app.campaign_completed');
        $attributes = [
          'file' => null
        ];
        $notification_attributes = json_encode($attributes);
        $notification = [
          'name' => $notification_name,
          'type' => 'other',
          'attributes' => $notification_attributes,
          'app_id' => $schedule->app_id,
          'user_id' => $schedule->user_id
        ];
        \App\Models\Notification::create($notification);

        exit;
    }
  }

  /**
   * Update scheduled campaign status
  */
  protected function setScheduleStatus($id, $status)
  {
    ScheduleCampaign::whereId($id)->update(['status' => $status]);
  }
}
