<?php
defined('BASEPATH') OR exit('No direct script access allowed');

/**
 * Message_schedule_model
 *
 * Handles creation and retrieval of scheduled message batches.
 */
class Message_schedule_model extends CI_Model {

    /** @var int Default number of parts to split a send into */
    private const DEFAULT_PARTS = 4;

    public function __construct() {
        parent::__construct();
    }

    /**
     * Create a scheduled message with randomized batch sizes and dates.
     *
     * @param int $campaign_id
     * @param int $admin_id
     * @param string $message_content
     * @param int $total_recipients
     * @param int|null $parts
     * @param string|null $start_date Y-m-d formatted date
     * @param string|null $sender_id
     * @return object|false
     */
    public function create_schedule($campaign_id, $admin_id, $message_content, $total_recipients, $parts = null, $start_date = null, $sender_id = null, $attachment = null) {
        $parts = $parts ? max(1, (int) $parts) : self::DEFAULT_PARTS;

        if ($total_recipients < $parts) {
            return false;
        }

        $baseDate = $this->resolveStartDate($start_date);
        $batchSizes = $this->generateRandomBatches($total_recipients, $parts);
        $batchDates = $this->generateBatchDates($baseDate, $parts);

        $this->db->trans_start();

        $insertData = [
            'campaign_id' => $campaign_id,
            'created_by' => $admin_id,
            'sender_id' => $sender_id,
            'message_content' => $message_content,
            'total_recipients' => $total_recipients,
            'parts' => $parts
        ];

        // Only include attachment if the database column actually exists.
        // This provides a safe fallback for live systems where the schema
        // hasn't yet been altered.
        if ($attachment && $this->db->field_exists('attachment', 'scheduled_messages')) {
            $insertData['attachment'] = $attachment;
        } else if ($attachment) {
            log_message('warning', 'Attachment provided but DB column `scheduled_messages.attachment` does not exist. Skipping attachment storage.');
        }

        $this->db->insert('scheduled_messages', $insertData);

        $scheduleId = $this->db->insert_id();

        foreach ($batchSizes as $index => $size) {
            $this->db->insert('scheduled_message_batches', [
                'scheduled_message_id' => $scheduleId,
                'batch_number' => $index + 1,
                'batch_size' => $size,
                'scheduled_for' => $batchDates[$index],
                'status' => 'pending'
            ]);
        }

        $this->db->trans_complete();

        if (!$this->db->trans_status()) {
            return false;
        }

        return $this->get_schedule($scheduleId);
    }

    /**
     * Fetch a single schedule with batches.
     */
    public function get_schedule($schedule_id) {
        $this->db->select('sm.*, c.name as campaign_name, c.type as campaign_type, u.username as admin_name');
        $this->db->from('scheduled_messages sm');
        $this->db->join('campaigns c', 'c.id = sm.campaign_id');
        $this->db->join('users u', 'u.id = sm.created_by');
        $this->db->where('sm.id', $schedule_id);
        $schedule = $this->db->get()->row();

        if (!$schedule) {
            return false;
        }

        $schedule->batches = $this->get_batches([$schedule_id])[$schedule_id] ?? [];
        return $schedule;
    }

    /**
     * Fetch schedules, optionally filtered by campaign.
     *
     * @param int|null $campaign_id
     * @return array
     */
    public function get_schedules($campaign_id = null) {
        $this->db->select('sm.*, c.name as campaign_name, c.type as campaign_type, u.username as admin_name');
        $this->db->from('scheduled_messages sm');
        $this->db->join('campaigns c', 'c.id = sm.campaign_id');
        $this->db->join('users u', 'u.id = sm.created_by');

        if ($campaign_id) {
            $this->db->where('sm.campaign_id', $campaign_id);
        }

        $this->db->order_by('sm.created_at', 'DESC');
        $schedules = $this->db->get()->result();

        if (empty($schedules)) {
            return [];
        }

        $ids = array_map(static function ($schedule) {
            return $schedule->id;
        }, $schedules);

        $batches = $this->get_batches($ids);

        foreach ($schedules as $schedule) {
            $schedule->batches = $batches[$schedule->id] ?? [];
        }

        return $schedules;
    }

    /**
     * Fetch schedules for a campaign for user-facing view.
     */
    public function get_campaign_schedules($campaign_id) {
        return $this->get_schedules($campaign_id);
    }

    /**
     * Update batch status and timestamps.
     */
    public function update_batch_status($batch_id, $status, array $extra = []) {
        $allowed = ['pending', 'processing', 'sent'];

        if (!in_array($status, $allowed, true)) {
            return false;
        }

        $data = array_merge(['status' => $status], $extra);

        if ($status === 'sent') {
            if (!isset($data['sent_at'])) {
                $data['sent_at'] = date('Y-m-d H:i:s');
            }
            
            // When marking batch as sent, also update campaign_user_data records
            $this->process_batch_sends($batch_id, $data['sent_at']);
        } elseif (!array_key_exists('sent_at', $data)) {
            $data['sent_at'] = null;
        }

        $this->db->where('id', $batch_id);
        $this->db->update('scheduled_message_batches', $data);

        return $this->db->affected_rows() > 0;
    }
    
    /**
     * Process batch sends - update campaign_user_data status
     */
    private function process_batch_sends($batch_id, $sent_at) {
        // Get batch info
        $batch = $this->db->where('id', $batch_id)->get('scheduled_message_batches')->row();
        
        if (!$batch) {
            return false;
        }
        
        // Get scheduled message
        $schedule = $this->db->where('id', $batch->scheduled_message_id)->get('scheduled_messages')->row();
        
        if (!$schedule) {
            return false;
        }
        
        // Get campaign for fail percentage
        $campaign = $this->db->where('id', $schedule->campaign_id)->get('campaigns')->row();
        
        if (!$campaign) {
            return false;
        }
        
        // Get pending campaign_user_data records
        $this->db->select('campaign_user_data.*, master_data.mobile');
        $this->db->from('campaign_user_data');
        $this->db->join('master_data', 'master_data.id = campaign_user_data.master_data_id');
        $this->db->where('campaign_user_data.campaign_id', $schedule->campaign_id);
        $this->db->where('campaign_user_data.user_id', $schedule->created_by);
        $this->db->where('campaign_user_data.status', 'pending');
        $this->db->limit($batch->batch_size);
        $this->db->order_by('campaign_user_data.id', 'ASC');
        
        $records = $this->db->get()->result();
        
        if (empty($records)) {
            return false;
        }
        
        $this->load->model('Send_model');
        
        // Process each record
        foreach ($records as $record) {
            // Simulate fail logic
            $random = mt_rand(0, 10000) / 100;
            $will_fail = ($random < $campaign->fail_percentage);
            
            $status = $will_fail ? 'failed' : 'sent';
            $error_message = $will_fail ? 'Simulated failure (' . number_format($random, 2) . '% < ' . $campaign->fail_percentage . '%)' : null;
            
            // Update campaign_user_data
            $this->db->where('id', $record->id);
            $this->db->update('campaign_user_data', [
                'status' => $status,
                'sent_at' => $sent_at
            ]);
            
            // Insert into send_logs
            $this->db->insert('send_logs', [
                'campaign_id' => $schedule->campaign_id,
                'user_id' => $schedule->created_by,
                'campaign_data_id' => $record->id,
                'mobile' => $record->mobile,
                'message_content' => $schedule->message_content,
                'status' => $status,
                'error_message' => $error_message,
                'ip_address' => $this->input->ip_address(),
                'user_agent' => 'Batch Status Update',
                'created_at' => $sent_at
            ]);
        }
        
        return true;
    }

    /**
     * Retrieve batches grouped by schedule ID.
     *
     * @param array $scheduleIds
     * @return array<int, array>
     */
    private function get_batches(array $scheduleIds) {
        if (empty($scheduleIds)) {
            return [];
        }

        $this->db->from('scheduled_message_batches');
        $this->db->where_in('scheduled_message_id', $scheduleIds);
        $this->db->order_by('scheduled_message_id ASC, batch_number ASC');
        $batches = $this->db->get()->result();

        if (!empty($batches)) {
            $this->auto_update_batch_statuses($batches);
        }

        $grouped = [];
        foreach ($batches as $batch) {
            $grouped[$batch->scheduled_message_id][] = $batch;
        }

        return $grouped;
    }

    /**
     * Create equal proportional batch sizes that sum to total recipients.
     * Each delivery gets an equal percentage (e.g., 4 slots = 25% each)
     */
    private function generateRandomBatches($total, $parts) {
        if ($parts <= 1) {
            return [$total];
        }

        // Calculate equal portions
        $baseSize = floor($total / $parts);
        $remainder = $total % $parts;
        
        $batches = [];
        
        // Create equal-sized batches
        for ($i = 0; $i < $parts; $i++) {
            $batches[] = $baseSize;
        }
        
        // Distribute the remainder across first few batches
        for ($i = 0; $i < $remainder; $i++) {
            $batches[$i]++;
        }
        
        return $batches;
    }

    /**
     * Build an array of scheduled datetime strings.
     * All batches use the SAME date with different random times.
     */
    private function generateBatchDates(DateTimeImmutable $baseDate, $parts) {
        $dates = [];

        for ($i = 0; $i < $parts; $i++) {
            // Use the SAME base date for ALL batches
            $current = $baseDate;

            // Only randomize the time, not the date
            $hour = mt_rand(9, 17);
            $minute = mt_rand(0, 1) ? 30 : 0;
            $dates[] = $current->setTime($hour, $minute, 0)->format('Y-m-d H:i:s');
        }

        return $dates;
    }

    /**
     * Resolve the start date as a DateTimeImmutable.
     */
    private function resolveStartDate($start_date) {
        if ($start_date) {
            $date = DateTimeImmutable::createFromFormat('Y-m-d', $start_date);
            if ($date instanceof DateTimeImmutable) {
                return $date;
            }
        }

        return (new DateTimeImmutable())->setTime(9, 0, 0);
    }

    private function auto_update_batch_statuses(array &$batches) {
        $now = new DateTimeImmutable();

        foreach ($batches as $batch) {
            $scheduledAt = new DateTimeImmutable($batch->scheduled_for);

            if (in_array($batch->status, ['pending', 'processing'], true)) {
                // Mark as sent immediately when scheduled time arrives (within 10 seconds)
                if ($scheduledAt <= $now->modify('+10 seconds')) {
                    $sentAt = $scheduledAt;
                    $this->update_batch_status($batch->id, 'sent', ['sent_at' => $sentAt->format('Y-m-d H:i:s')]);
                    $batch->status = 'sent';
                    $batch->sent_at = $sentAt->format('Y-m-d H:i:s');
                }
            }
        }
    }
}
