<?php
/**
 * Order Model
 * Handle order operations and business logic
 */

namespace Models;

use Core\DB;
use Core\Helpers;

class Order {
    private $db;
    
    public function __construct() {
        $this->db = DB::getInstance();
    }
    
    /**
     * Create new SOS order
     */
    public function createSosOrder($data) {
        $orderId = $this->generateOrderId();
        
        $order = [
            'id' => $orderId,
            'client_id' => $data['client_id'] ?? null, // Can be null for anonymous orders
            'medicine_name' => $data['medicine_name'],
            'qty' => $data['qty'] ?? 1,
            'client_lat' => $data['client_lat'],
            'client_lng' => $data['client_lng'],
            'client_address' => $data['client_address'],
            'client_phone' => $data['client_phone'],
            'prescription_url' => $data['prescription_url'] ?? null,
            'status' => 'CREATED',
            'service_fee' => (int)getConfigValue('SERVICE_FEE', SERVICE_FEE),
            'delivery_fee' => (int)getConfigValue('DELIVERY_MIN', DELIVERY_MIN),
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s')
        ];
        
        $this->db->insert('orders', $order);
        
        // Log order creation event
        $this->addEvent($orderId, 'ORDER_CREATED', [
            'medicine' => $data['medicine_name'],
            'client_phone' => $data['client_phone']
        ]);
        
        return $orderId;
    }
    
    /**
     * Find nearby pharmacies with stock
     */
    public function findNearbyPharmacies($lat, $lng, $medicineName, $excludeIds = []) {
        $excludeWhere = '';
        $params = [$lat, $lng, "%{$medicineName}%"];
        
        if (!empty($excludeIds)) {
            $placeholders = str_repeat('?,', count($excludeIds) - 1) . '?';
            $excludeWhere = " AND p.id NOT IN ({$placeholders})";
            $params = array_merge($params, $excludeIds);
        }
        
        $sql = "
            SELECT p.*, ps.stock, ps.unit_price,
                   (6371 * acos(
                       cos(radians(?)) * 
                       cos(radians(p.lat)) * 
                       cos(radians(p.lng) - radians(?)) + 
                       sin(radians(?)) * 
                       sin(radians(p.lat))
                   )) AS distance
            FROM pharmacies p
            INNER JOIN pharmacy_stock ps ON p.id = ps.pharmacy_id
            INNER JOIN medicines m ON ps.medicine_id = m.id
            WHERE p.verified = 1 
            AND ps.stock > 0 
            AND m.name LIKE ?
            {$excludeWhere}
            ORDER BY distance ASC
            LIMIT 5
        ";
        
        $params = [$lat, $lng, $lat, "%{$medicineName}%"] + $excludeIds;
        
        return $this->db->fetchAll($sql, $params);
    }
    
    /**
     * Assign pharmacy to order
     */
    public function assignPharmacy($orderId, $pharmacyId, $medicineId = null) {
        $pharmacy = $this->db->fetchOne(
            "SELECT * FROM pharmacies WHERE id = ?",
            [$pharmacyId]
        );
        
        if (!$pharmacy) {
            throw new \Exception('Pharmacie non trouvée');
        }
        
        $order = $this->findById($orderId);
        if (!$order) {
            throw new \Exception('Commande non trouvée');
        }
        
        // Calculate distance and delivery fee
        $distance = Helpers::calculateDistance(
            $order['client_lat'],
            $order['client_lng'],
            $pharmacy['lat'],
            $pharmacy['lng']
        );
        
        $deliveryFee = Helpers::calculateDeliveryFee($distance);
        
        $this->db->update('orders', [
            'pharmacy_id' => $pharmacyId,
            'medicine_id' => $medicineId,
            'distance_km' => $distance,
            'delivery_fee' => $deliveryFee,
            'updated_at' => date('Y-m-d H:i:s')
        ], 'id = ?', [$orderId]);
        
        $this->addEvent($orderId, 'PHARMACY_ASSIGNED', [
            'pharmacy' => $pharmacy['name'],
            'distance' => $distance
        ]);
        
        return true;
    }
    
    /**
     * Pharmacy confirms order
     */
    public function pharmacyConfirm($orderId, $unitPrice) {
        $order = $this->findById($orderId);
        if (!$order) {
            throw new \Exception('Commande non trouvée');
        }
        
        $medicineTotal = $order['qty'] * $unitPrice;
        
        $this->db->beginTransaction();
        
        try {
            // Update order
            $this->db->update('orders', [
                'status' => 'PHARMACY_CONFIRMED',
                'med_price_total' => $medicineTotal,
                'updated_at' => date('Y-m-d H:i:s')
            ], 'id = ?', [$orderId]);
            
            // Update stock if medicine_id is known
            if ($order['medicine_id']) {
                $this->db->update('pharmacy_stock', [
                    'stock' => $this->db->query("SELECT stock FROM pharmacy_stock WHERE pharmacy_id = ? AND medicine_id = ?", 
                        [$order['pharmacy_id'], $order['medicine_id']])->fetchColumn() - $order['qty']
                ], 'pharmacy_id = ? AND medicine_id = ?', [$order['pharmacy_id'], $order['medicine_id']]);
            }
            
            $this->addEvent($orderId, 'PHARMACY_CONFIRMED', [
                'unit_price' => $unitPrice,
                'medicine_total' => $medicineTotal
            ]);
            
            $this->db->commit();
            return true;
            
        } catch (\Exception $e) {
            $this->db->rollback();
            throw $e;
        }
    }
    
    /**
     * Pharmacy rejects order
     */
    public function pharmacyReject($orderId, $reason = null) {
        $this->db->update('orders', [
            'pharmacy_id' => null,
            'medicine_id' => null,
            'status' => 'CREATED',
            'updated_at' => date('Y-m-d H:i:s')
        ], 'id = ?', [$orderId]);
        
        $this->addEvent($orderId, 'PHARMACY_REJECTED', [
            'reason' => $reason
        ]);
        
        return true;
    }
    
    /**
     * Find available couriers near pharmacy
     */
    public function findNearbyCouriers($pharmacyLat, $pharmacyLng, $radius = 20) {
        $sql = "
            SELECT c.*, u.name, u.phone,
                   (6371 * acos(
                       cos(radians(?)) * 
                       cos(radians(c.lat)) * 
                       cos(radians(c.lng) - radians(?)) + 
                       sin(radians(?)) * 
                       sin(radians(c.lat))
                   )) AS distance
            FROM couriers c
            INNER JOIN users u ON c.user_id = u.id
            WHERE c.active = 1 
            AND c.available = 1 
            AND u.active = 1
            HAVING distance <= ?
            ORDER BY distance ASC
            LIMIT 10
        ";
        
        return $this->db->fetchAll($sql, [$pharmacyLat, $pharmacyLng, $pharmacyLat, $radius]);
    }
    
    /**
     * Assign courier to order
     */
    public function assignCourier($orderId, $courierId) {
        $order = $this->findById($orderId);
        if (!$order || $order['status'] !== 'PHARMACY_CONFIRMED') {
            throw new \Exception('Commande non éligible pour assignation livreur');
        }
        
        $courier = $this->db->fetchOne(
            "SELECT c.*, u.name FROM couriers c 
             INNER JOIN users u ON c.user_id = u.id 
             WHERE c.id = ? AND c.active = 1 AND c.available = 1",
            [$courierId]
        );
        
        if (!$courier) {
            throw new \Exception('Livreur non disponible');
        }
        
        $this->db->beginTransaction();
        
        try {
            // Update order
            $this->db->update('orders', [
                'courier_id' => $courierId,
                'status' => 'COURIER_ASSIGNED',
                'updated_at' => date('Y-m-d H:i:s')
            ], 'id = ?', [$orderId]);
            
            // Mark courier as unavailable
            $this->db->update('couriers', [
                'available' => 0
            ], 'id = ?', [$courierId]);
            
            $this->addEvent($orderId, 'COURIER_ASSIGNED', [
                'courier' => $courier['name']
            ]);
            
            $this->db->commit();
            return true;
            
        } catch (\Exception $e) {
            $this->db->rollback();
            throw $e;
        }
    }
    
    /**
     * Update order status
     */
    public function updateStatus($orderId, $status) {
        $validStatuses = ['CREATED', 'PHARMACY_CONFIRMED', 'COURIER_ASSIGNED', 'PICKED_UP', 'DELIVERED', 'CLOSED'];
        
        if (!in_array($status, $validStatuses)) {
            throw new \Exception('Statut non valide');
        }
        
        $this->db->beginTransaction();
        
        try {
            $this->db->update('orders', [
                'status' => $status,
                'updated_at' => date('Y-m-d H:i:s')
            ], 'id = ?', [$orderId]);
            
            // Handle specific status changes
            if ($status === 'DELIVERED') {
                $this->processPayment($orderId);
            } elseif ($status === 'CLOSED') {
                // Make courier available again
                $order = $this->findById($orderId);
                if ($order['courier_id']) {
                    $this->db->update('couriers', ['available' => 1], 'id = ?', [$order['courier_id']]);
                }
            }
            
            $this->addEvent($orderId, 'STATUS_CHANGED_' . $status, [
                'new_status' => $status
            ]);
            
            $this->db->commit();
            return true;
            
        } catch (\Exception $e) {
            $this->db->rollback();
            throw $e;
        }
    }
    
    /**
     * Process payment distribution when order is delivered
     */
    private function processPayment($orderId) {
        $order = $this->findById($orderId);
        if (!$order) {
            throw new \Exception('Commande non trouvée');
        }
        
        $distribution = Helpers::calculatePaymentDistribution(
            $order['med_price_total'],
            $order['delivery_fee']
        );
        
        // Create ledger entries
        $this->createLedgerEntry($orderId, 'CREDIT', 'MEDIFLASH', $distribution['mediflash'], 'Frais de service');
        $this->createLedgerEntry($orderId, 'CREDIT', 'PHARMACY', $distribution['pharmacy'], 'Paiement médicament + bonus');
        $this->createLedgerEntry($orderId, 'CREDIT', 'COURIER', $distribution['courier'], 'Frais de livraison');
        
        // Update wallets
        $this->updateWallet('MEDIFLASH', null, $distribution['mediflash']);
        $this->updateWallet('PHARMACY', $order['pharmacy_id'], $distribution['pharmacy']);
        $this->updateWallet('COURIER', $order['courier_id'], $distribution['courier']);
        
        return true;
    }
    
    /**
     * Create ledger entry
     */
    private function createLedgerEntry($orderId, $entryType, $account, $amount, $note) {
        return $this->db->insert('ledger', [
            'id' => $this->generateUuid(),
            'order_id' => $orderId,
            'entry_type' => $entryType,
            'account' => $account,
            'amount' => $amount,
            'note' => $note,
            'created_at' => date('Y-m-d H:i:s')
        ]);
    }
    
    /**
     * Update wallet balance
     */
    private function updateWallet($ownerType, $ownerId, $amount) {
        // Check if wallet exists
        $wallet = $this->db->fetchOne(
            "SELECT * FROM wallets WHERE owner_type = ? AND owner_id " . 
            ($ownerId ? "= ?" : "IS NULL"),
            array_filter([$ownerType, $ownerId])
        );
        
        if (!$wallet) {
            // Create wallet
            $this->db->insert('wallets', [
                'id' => $this->generateUuid(),
                'owner_type' => $ownerType,
                'owner_id' => $ownerId,
                'balance' => $amount,
                'updated_at' => date('Y-m-d H:i:s')
            ]);
        } else {
            // Update wallet
            $this->db->update('wallets', [
                'balance' => $wallet['balance'] + $amount,
                'updated_at' => date('Y-m-d H:i:s')
            ], 'id = ?', [$wallet['id']]);
        }
    }
    
    /**
     * Add order event
     */
    public function addEvent($orderId, $event, $meta = null) {
        return $this->db->insert('order_events', [
            'id' => $this->generateUuid(),
            'order_id' => $orderId,
            'event' => $event,
            'meta' => $meta ? json_encode($meta) : null,
            'created_at' => date('Y-m-d H:i:s')
        ]);
    }
    
    /**
     * Find order by ID with details
     */
    public function findById($orderId) {
        return $this->db->fetchOne("SELECT * FROM orders WHERE id = ?", [$orderId]);
    }
    
    /**
     * Get order with full details
     */
    public function getWithDetails($orderId) {
        $order = $this->db->fetchOne("
            SELECT o.*, 
                   u.name as client_name, u.phone as client_phone_user, u.email as client_email,
                   p.name as pharmacy_name, p.address as pharmacy_address, p.lat as pharmacy_lat, p.lng as pharmacy_lng,
                   c.vehicle_type, cu.name as courier_name, cu.phone as courier_phone,
                   m.name as medicine_name_db, m.atc_code
            FROM orders o
            LEFT JOIN users u ON o.client_id = u.id
            LEFT JOIN pharmacies p ON o.pharmacy_id = p.id
            LEFT JOIN couriers c ON o.courier_id = c.id
            LEFT JOIN users cu ON c.user_id = cu.id
            LEFT JOIN medicines m ON o.medicine_id = m.id
            WHERE o.id = ?
        ", [$orderId]);
        
        if ($order) {
            // Get events
            $order['events'] = $this->db->fetchAll(
                "SELECT * FROM order_events WHERE order_id = ? ORDER BY created_at ASC",
                [$orderId]
            );
        }
        
        return $order;
    }
    
    /**
     * Get orders by client
     */
    public function getByClient($clientId) {
        return $this->db->fetchAll("
            SELECT o.*, p.name as pharmacy_name, c.vehicle_type, cu.name as courier_name
            FROM orders o
            LEFT JOIN pharmacies p ON o.pharmacy_id = p.id
            LEFT JOIN couriers c ON o.courier_id = c.id
            LEFT JOIN users cu ON c.user_id = cu.id
            WHERE o.client_id = ?
            ORDER BY o.created_at DESC
        ", [$clientId]);
    }
    
    /**
     * Get orders by pharmacy
     */
    public function getByPharmacy($pharmacyId, $status = null) {
        $where = "o.pharmacy_id = ?";
        $params = [$pharmacyId];
        
        if ($status) {
            $statuses = explode(',', $status);
            $placeholders = str_repeat('?,', count($statuses) - 1) . '?';
            $where .= " AND o.status IN ({$placeholders})";
            $params = array_merge($params, $statuses);
        }
        
        return $this->db->fetchAll("
            SELECT o.*, u.name as client_name, u.phone as client_phone_user
            FROM orders o
            LEFT JOIN users u ON o.client_id = u.id
            WHERE {$where}
            ORDER BY o.created_at DESC
        ", $params);
    }
    
    /**
     * Get orders by courier
     */
    public function getByCourier($courierId, $status = null) {
        $where = "o.courier_id = ?";
        $params = [$courierId];
        
        if ($status) {
            $statuses = explode(',', $status);
            $placeholders = str_repeat('?,', count($statuses) - 1) . '?';
            $where .= " AND o.status IN ({$placeholders})";
            $params = array_merge($params, $statuses);
        }
        
        return $this->db->fetchAll("
            SELECT o.*, u.name as client_name, p.name as pharmacy_name, p.address as pharmacy_address
            FROM orders o
            LEFT JOIN users u ON o.client_id = u.id
            LEFT JOIN pharmacies p ON o.pharmacy_id = p.id
            WHERE {$where}
            ORDER BY o.created_at DESC
        ", $params);
    }
    
    /**
     * Get available orders for couriers
     */
    public function getAvailableForCouriers($courierLat, $courierLng, $radius = 30) {
        return $this->db->fetchAll("
            SELECT o.*, p.name as pharmacy_name, p.lat as pharmacy_lat, p.lng as pharmacy_lng,
                   (6371 * acos(
                       cos(radians(?)) * 
                       cos(radians(p.lat)) * 
                       cos(radians(p.lng) - radians(?)) + 
                       sin(radians(?)) * 
                       sin(radians(p.lat))
                   )) AS distance
            FROM orders o
            INNER JOIN pharmacies p ON o.pharmacy_id = p.id
            WHERE o.status = 'PHARMACY_CONFIRMED'
            HAVING distance <= ?
            ORDER BY o.created_at ASC, distance ASC
        ", [$courierLat, $courierLng, $courierLat, $radius]);
    }
    
    /**
     * Get all orders with pagination
     */
    public function getAll($page = 1, $limit = 50) {
        $offset = ($page - 1) * $limit;
        
        return $this->db->fetchAll("
            SELECT o.*, u.name as client_name, p.name as pharmacy_name, cu.name as courier_name
            FROM orders o
            LEFT JOIN users u ON o.client_id = u.id
            LEFT JOIN pharmacies p ON o.pharmacy_id = p.id
            LEFT JOIN couriers c ON o.courier_id = c.id
            LEFT JOIN users cu ON c.user_id = cu.id
            ORDER BY o.created_at DESC
            LIMIT {$limit} OFFSET {$offset}
        ");
    }
    
    /**
     * Generate unique order ID
     */
    private function generateOrderId() {
        return 'SOS' . strtoupper(substr(uniqid(), -6)) . sprintf('%03d', mt_rand(1, 999));
    }
    
    /**
     * Generate UUID v4
     */
    private function generateUuid() {
        return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
            mt_rand(0, 0xffff), mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            mt_rand(0, 0x0fff) | 0x4000,
            mt_rand(0, 0x3fff) | 0x8000,
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
        );
    }
}
?>
