<?php

/**

 * AI Model Trainer

 * Trains and updates AI models with new data

 */



class AITrainer {

    private $conn;

    private $models = [];

    

    public function __construct() {

        $this->conn = getDBConnection();

    }

    

    /**

     * Train payment predictor model - FIXED VERSION

     */

    public function trainPaymentPredictor($forceRetrain = false) {

        // Check if training is needed

        $lastTraining = $this->getLastTrainingTime('payment_predictor');

        if (!$forceRetrain && $lastTraining && (time() - strtotime($lastTraining)) < 86400) {

            return ['status' => 'skipped', 'message' => 'Model is up to date'];

        }

        

        // Get training data

        $trainingData = $this->getPaymentTrainingData();

        

        if (count($trainingData) < 3) {

            return [

                'status' => 'error', 

                'message' => 'Insufficient training data. Need at least 3 students with payment history.',

                'samples' => count($trainingData)

            ];

        }

        

        // Train the model and get weights

        $trainingResult = $this->trainModel($trainingData);

        

        // Create new model instance

        $model = new AIPaymentPredictor();

        

        // FIX: Use setter method or public property if available

        // Option 1: If there's a setter method

        if (method_exists($model, 'setModelData')) {

            $model->setModelData($trainingResult['weights']);

        } 

        // Option 2: If modelData is protected but has public getter/setter

        elseif (method_exists($model, '__set')) {

            $model->__set('modelData', $trainingResult['weights']);

        }

        // Option 3: Use reflection to access protected property (last resort)

        else {

            $reflection = new ReflectionClass($model);

            $property = $reflection->getProperty('modelData');

            $property->setAccessible(true);

            $property->setValue($model, $trainingResult['weights']);

        }

        

        // Set accuracy using similar approach

        if (method_exists($model, 'setAccuracy')) {

            $model->setAccuracy($trainingResult['accuracy']);

        } else {

            $reflection = new ReflectionClass($model);

            $property = $reflection->getProperty('accuracy');

            $property->setAccessible(true);

            $property->setValue($model, $trainingResult['accuracy']);

        }

        

        // Save the model

        $saveResult = $model->saveModel();

        

        // Log training

        $this->logTraining('payment_predictor', $trainingResult['accuracy'], count($trainingData));

        

        // Clear cache

        $model->clearCache();

        

        return [

            'status' => 'success',

            'accuracy' => $trainingResult['accuracy'],

            'samples' => count($trainingData),

            'training_date' => date('Y-m-d H:i:s'),

            'weights' => $trainingResult['weights']

        ];

    }

    

    /**

     * Alternative approach - Direct file save without using model instance

     */

    public function trainPaymentPredictorDirect($forceRetrain = false) {

        // Get training data

        $trainingData = $this->getPaymentTrainingData();

        

        if (count($trainingData) < 3) {

            return [

                'status' => 'error', 

                'message' => 'Insufficient training data. Need at least 3 students with payment history.',

                'samples' => count($trainingData)

            ];

        }

        

        // Train the model

        $trainingResult = $this->trainModel($trainingData);

        

        // Save directly to file

        $modelData = [

            'weights' => $trainingResult['weights'],

            'accuracy' => $trainingResult['accuracy'],

            'training_date' => date('Y-m-d H:i:s'),

            'version' => '2.0',

            'model_name' => 'payment_predictor'

        ];

        

        $modelFile = AI_MODELS_PATH . 'payment_predictor_v2.0.json';

        file_put_contents($modelFile, json_encode($modelData, JSON_PRETTY_PRINT));

        

        // Also save to database if needed

        $this->saveModelToDatabase('payment_predictor', '2.0', $modelData, $trainingResult['accuracy']);

        

        // Log training

        $this->logTraining('payment_predictor', $trainingResult['accuracy'], count($trainingData));

        

        return [

            'status' => 'success',

            'accuracy' => $trainingResult['accuracy'],

            'samples' => count($trainingData),

            'training_date' => date('Y-m-d H:i:s'),

            'model_file' => $modelFile,

            'weights' => $trainingResult['weights']

        ];

    }

    

    /**

     * Save model to database

     */

    private function saveModelToDatabase($modelName, $version, $modelData, $accuracy) {

        // Create table if not exists

        $this->conn->query("CREATE TABLE IF NOT EXISTS ai_models (

            id INT AUTO_INCREMENT PRIMARY KEY,

            model_name VARCHAR(100) NOT NULL,

            model_version VARCHAR(20) NOT NULL,

            model_data LONGTEXT NOT NULL,

            accuracy DECIMAL(5,4),

            training_date DATETIME,

            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

            UNIQUE KEY unique_model (model_name, model_version)

        )");

        

        $query = "INSERT INTO ai_models (model_name, model_version, model_data, accuracy, training_date) 

                  VALUES (?, ?, ?, ?, NOW())

                  ON DUPLICATE KEY UPDATE 

                  model_data = VALUES(model_data),

                  accuracy = VALUES(accuracy),

                  training_date = VALUES(training_date)";

        

        $stmt = $this->conn->prepare($query);

        $modelDataJson = json_encode($modelData);

        $stmt->bind_param("sssd", $modelName, $version, $modelDataJson, $accuracy);

        $stmt->execute();

        $stmt->close();

    }

    

    /**

     * Get payment training data

     */

    private function getPaymentTrainingData() {

        $query = "SELECT 

                    s.id as student_id,

                    COUNT(p.id) as payment_count,

                    COALESCE(SUM(p.amount_paid), 0) as total_paid,

                    COALESCE(AVG(p.amount_paid), 0) as avg_payment,

                    COUNT(CASE WHEN p.payment_date < DATE_ADD(

                        COALESCE((SELECT due_date FROM fee_structures WHERE id = p.fee_structure_id), p.payment_date),

                        INTERVAL 7 DAY

                    ) THEN 1 END) as late_payments,

                    COALESCE((SELECT SUM(fs.amount) FROM fee_structures fs 

                              WHERE fs.class_name = s.class), 500) as total_expected,

                    COALESCE(AVG(DATEDIFF(p.payment_date, 

                        COALESCE((SELECT due_date FROM fee_structures WHERE id = p.fee_structure_id), p.payment_date))), 0) as avg_delay,

                    COALESCE(DATEDIFF(NOW(), MAX(p.payment_date)), 999) as days_since_last,

                    COALESCE((SELECT COUNT(*) FROM payments p2 WHERE p2.student_id = s.id 

                     AND p2.payment_date > DATE_SUB(NOW(), INTERVAL 30 DAY)), 0) as payments_last_30_days

                  FROM students s

                  LEFT JOIN payments p ON s.id = p.student_id

                  WHERE s.status = 'active'

                  GROUP BY s.id";

        

        $result = $this->conn->query($query);

        $trainingData = [];

        

        while ($row = $result->fetch_assoc()) {

            // Calculate target variable

            $isGoodPayer = 1;

            

            if ($row['payment_count'] > 0) {

                $lateRatio = $row['late_payments'] / $row['payment_count'];

                if ($lateRatio > 0.3) $isGoodPayer = 0;

                if ($row['avg_delay'] > 15) $isGoodPayer = 0;

                if ($row['days_since_last'] > 60 && $row['total_paid'] < $row['total_expected']) $isGoodPayer = 0;

            } else {

                $isGoodPayer = 0.5;

            }

            

            $row['target'] = $isGoodPayer;

            $trainingData[] = $row;

        }

        

        return $trainingData;

    }

    

    /**

     * Train model using logistic regression

     */

    private function trainModel($trainingData) {

        // Initialize weights

        $weights = [

            'payment_count' => 0.5,

            'avg_payment' => 0.5,

            'late_ratio' => -0.5,

            'avg_delay' => -0.3,

            'days_since_last' => -0.2,

            'completion_rate' => 1.0,

            'recent_activity' => 0.8

        ];

        

        $learningRate = 0.01;

        $epochs = 50;

        

        for ($epoch = 0; $epoch < $epochs; $epoch++) {

            foreach ($trainingData as $data) {

                // Normalize features

                $features = [

                    'payment_count' => min($data['payment_count'] / 20, 1),

                    'avg_payment' => min($data['avg_payment'] / 1000, 1),

                    'late_ratio' => $data['payment_count'] > 0 ? $data['late_payments'] / $data['payment_count'] : 0,

                    'avg_delay' => min($data['avg_delay'] / 60, 1),

                    'days_since_last' => min($data['days_since_last'] / 90, 1),

                    'completion_rate' => $data['total_expected'] > 0 ? $data['total_paid'] / $data['total_expected'] : 0.5,

                    'recent_activity' => min($data['payments_last_30_days'] / 5, 1)

                ];

                

                // Calculate prediction

                $score = 0;

                $score += $weights['payment_count'] * $features['payment_count'];

                $score += $weights['avg_payment'] * $features['avg_payment'];

                $score += $weights['late_ratio'] * $features['late_ratio'];

                $score += $weights['avg_delay'] * $features['avg_delay'];

                $score += $weights['days_since_last'] * $features['days_since_last'];

                $score += $weights['completion_rate'] * $features['completion_rate'];

                $score += $weights['recent_activity'] * $features['recent_activity'];

                

                $prediction = 1 / (1 + exp(-$score));

                $error = $prediction - $data['target'];

                

                // Update weights

                foreach ($weights as $key => $value) {

                    $weights[$key] -= $learningRate * $error * $features[str_replace('_weight', '', $key)];

                }

            }

        }

        

        // Calculate accuracy

        $correct = 0;

        $total = 0;

        

        foreach ($trainingData as $data) {

            if ($data['payment_count'] == 0) continue;

            

            $features = [

                'payment_count' => min($data['payment_count'] / 20, 1),

                'avg_payment' => min($data['avg_payment'] / 1000, 1),

                'late_ratio' => $data['late_payments'] / $data['payment_count'],

                'avg_delay' => min($data['avg_delay'] / 60, 1),

                'days_since_last' => min($data['days_since_last'] / 90, 1),

                'completion_rate' => $data['total_paid'] / $data['total_expected'],

                'recent_activity' => min($data['payments_last_30_days'] / 5, 1)

            ];

            

            $score = 0;

            $score += $weights['payment_count'] * $features['payment_count'];

            $score += $weights['avg_payment'] * $features['avg_payment'];

            $score += $weights['late_ratio'] * $features['late_ratio'];

            $score += $weights['avg_delay'] * $features['avg_delay'];

            $score += $weights['days_since_last'] * $features['days_since_last'];

            $score += $weights['completion_rate'] * $features['completion_rate'];

            $score += $weights['recent_activity'] * $features['recent_activity'];

            

            $prediction = 1 / (1 + exp(-$score));

            $predictedClass = $prediction > 0.5 ? 1 : 0;

            

            $total++;

            if ($predictedClass == $data['target']) {

                $correct++;

            }

        }

        

        $accuracy = $total > 0 ? $correct / $total : 0.7;

        

        return [

            'weights' => $weights,

            'accuracy' => $accuracy

        ];

    }

    

    /**

     * Get last training time

     */

    private function getLastTrainingTime($modelName) {

        $this->conn->query("CREATE TABLE IF NOT EXISTS ai_training_logs (

            id INT AUTO_INCREMENT PRIMARY KEY,

            model_name VARCHAR(100) NOT NULL,

            accuracy DECIMAL(5,4),

            samples_used INT,

            training_date DATETIME NOT NULL,

            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

        )");

        

        $query = "SELECT training_date FROM ai_training_logs 

                  WHERE model_name = ? ORDER BY training_date DESC LIMIT 1";

        $stmt = $this->conn->prepare($query);

        if (!$stmt) return null;

        

        $stmt->bind_param("s", $modelName);

        $stmt->execute();

        $result = $stmt->get_result();

        $row = $result->fetch_assoc();

        $stmt->close();

        

        return $row['training_date'] ?? null;

    }

    

    /**

     * Log training activity

     */

    private function logTraining($modelName, $accuracy, $samples) {

        $query = "INSERT INTO ai_training_logs (model_name, accuracy, samples_used, training_date) 

                  VALUES (?, ?, ?, NOW())";

        $stmt = $this->conn->prepare($query);

        if (!$stmt) return;

        

        $stmt->bind_param("sdi", $modelName, $accuracy, $samples);

        $stmt->execute();

        $stmt->close();

    }

    

    /**

     * Get training statistics

     */

    public function getTrainingStats() {

        $this->conn->query("CREATE TABLE IF NOT EXISTS ai_training_logs (

            id INT AUTO_INCREMENT PRIMARY KEY,

            model_name VARCHAR(100) NOT NULL,

            accuracy DECIMAL(5,4),

            samples_used INT,

            training_date DATETIME NOT NULL,

            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

        )");

        

        $query = "SELECT 

                    model_name,

                    COUNT(*) as training_count,

                    AVG(accuracy) as avg_accuracy,

                    MAX(training_date) as last_training,

                    SUM(samples_used) as total_samples

                  FROM ai_training_logs

                  GROUP BY model_name";

        

        $result = $this->conn->query($query);

        $stats = [];

        

        while ($row = $result->fetch_assoc()) {

            $stats[] = $row;

        }

        

        return $stats;

    }

}