AdminController.php

46.01 KB
04/08/2025 16:12
PHP
AdminController.php
<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use App\Core\Validator;
use App\Models\Category;
use App\Models\Order;
use App\Models\Product;
use App\Models\User;
use Exception;

/**
 * Admin Controller
 * Handles admin dashboard functionality, analytics, and reporting
 */
class AdminController extends BaseController
{
    /**
     * @var mixed
     */
    private $orderModel;
    /**
     * @var mixed
     */
    private $productModel;
    /**
     * @var mixed
     */
    private $userModel;
    /**
     * @var mixed
     */
    private $categoryModel;

    public function __construct()
    {
        parent::__construct();
        $this->orderModel = new Order();
        $this->productModel = new Product();
        $this->userModel = new User();
        $this->categoryModel = new Category();
    }

    /**
     * Get dashboard overview statistics
     * GET /api/admin/dashboard
     */
    public function dashboard()
    {
        try {
            // Get date range from query parameters
            $startDate = $_GET['start_date'] ?? date('Y-m-01'); // First day of current month
            $endDate = $_GET['end_date'] ?? date('Y-m-d'); // Today

            // Get real statistics from database
            $stats = [
                'sales' => $this->getSalesStats($startDate, $endDate),
                'orders' => $this->getOrderStats($startDate, $endDate),
                'products' => $this->getProductStats(),
                'customers' => $this->getCustomerStats($startDate, $endDate)
            ];

            $this->jsonResponse([
                'success' => true,
                'data' => [
                    'period' => [
                        'start_date' => $startDate,
                        'end_date' => $endDate
                    ],
                    'sales' => $stats['sales'],
                    'orders' => $stats['orders'],
                    'products' => $stats['products'],
                    'customers' => $stats['customers']
                ]
            ]);

        } catch (Exception $e) {
            $this->jsonResponse([
                'success' => false,
                'error' => 'Failed to fetch dashboard data',
                'message' => $this->isDebugMode() ? $e->getMessage() : 'An error occurred'
            ], 500);
        }
    }

    /**
     * Get sales analytics
     * GET /api/admin/analytics/sales
     */
    public function salesAnalytics()
    {
        try {
            $period = $_GET['period'] ?? 'month'; // day, week, month, year
            $startDate = $_GET['start_date'] ?? date('Y-m-01');
            $endDate = $_GET['end_date'] ?? date('Y-m-d');

            $analytics = [
                'revenue_trend' => $this->getRevenueTrend($period, $startDate, $endDate),
                'top_products' => $this->getTopSellingProducts($startDate, $endDate),
                'sales_by_category' => $this->getSalesByCategory($startDate, $endDate),
                'payment_methods' => $this->getPaymentMethodStats($startDate, $endDate),
                'order_status_distribution' => $this->getOrderStatusDistribution($startDate, $endDate)
            ];

            $this->jsonResponse([
                'success' => true,
                'data' => $analytics
            ]);

        } catch (Exception $e) {
            $this->jsonResponse([
                'success' => false,
                'error' => 'Failed to fetch sales analytics',
                'message' => $this->isDebugMode() ? $e->getMessage() : 'An error occurred'
            ], 500);
        }
    }

    /**
     * Get customer analytics
     * GET /api/admin/analytics/customers
     */
    public function customerAnalytics()
    {
        try {
            $startDate = $_GET['start_date'] ?? date('Y-m-01');
            $endDate = $_GET['end_date'] ?? date('Y-m-d');

            $analytics = [
                'new_customers' => $this->getNewCustomersData($startDate, $endDate),
                'customer_lifetime_value' => $this->getCustomerLifetimeValue(),
                'repeat_customers' => $this->getRepeatCustomerStats($startDate, $endDate),
                'customer_segments' => $this->getCustomerSegments(),
                'geographic_distribution' => $this->getCustomerGeographicData()
            ];

            $this->jsonResponse([
                'success' => true,
                'data' => $analytics
            ]);

        } catch (Exception $e) {
            $this->jsonResponse([
                'success' => false,
                'error' => 'Failed to fetch customer analytics',
                'message' => $this->isDebugMode() ? $e->getMessage() : 'An error occurred'
            ], 500);
        }
    }

    /**
     * Get inventory analytics
     * GET /api/admin/analytics/inventory
     */
    public function inventoryAnalytics()
    {
        try {
            $analytics = [
                'low_stock_products' => $this->getLowStockProducts(),
                'out_of_stock_products' => $this->getOutOfStockProducts(),
                'inventory_value' => $this->getInventoryValue(),
                'stock_movement' => $this->getStockMovement(),
                'product_performance' => $this->getProductPerformance()
            ];

            $this->jsonResponse([
                'success' => true,
                'data' => $analytics
            ]);

        } catch (Exception $e) {
            $this->jsonResponse([
                'success' => false,
                'error' => 'Failed to fetch inventory analytics',
                'message' => $this->isDebugMode() ? $e->getMessage() : 'An error occurred'
            ], 500);
        }
    }

    /**
     * Get inventory overview
     * GET /api/admin/inventory
     */
    public function inventory()
    {
        try {
            $page = (int) ($_GET['page'] ?? 1);
            $limit = (int) ($_GET['limit'] ?? 20);
            $status = $_GET['status'] ?? null; // low_stock, out_of_stock, in_stock
            $search = $_GET['search'] ?? null;

            $inventory = $this->getInventoryData($page, $limit, $status, $search);

            $this->jsonResponse([
                'success' => true,
                'data' => $inventory
            ]);

        } catch (Exception $e) {
            $this->jsonResponse([
                'success' => false,
                'error' => 'Failed to fetch inventory data',
                'message' => $this->isDebugMode() ? $e->getMessage() : 'An error occurred'
            ], 500);
        }
    }

    /**
     * Update product stock
     * POST /api/admin/inventory/{productId}/stock
     */
    public function updateStock($productId)
    {
        try {
            if (!is_numeric($productId)) {
                $this->jsonResponse([
                    'success' => false,
                    'error' => 'Invalid product ID'
                ], 400);
                return;
            }

            $data = $this->getJsonInput();

            // Validate input
            $validator = new Validator();
            $isValid = $validator->validate($data, [
                'variant_id' => ['numeric'],
                'quantity' => ['required', 'numeric', 'min:0'],
                'operation' => ['required', 'in:set,add,subtract'],
                'reason' => ['max:500']
            ]);

            if (!$isValid) {
                $this->jsonResponse([
                    'success' => false,
                    'error' => 'Validation failed',
                    'errors' => $validator->getErrors()
                ], 400);
                return;
            }

            $variantId = $data['variant_id'] ?? null;
            $quantity = (int) $data['quantity'];
            $operation = $data['operation'];
            $reason = $data['reason'] ?? 'Manual adjustment';

            // Check if product exists
            $product = $this->productModel->find($productId);
            if (!$product) {
                $this->jsonResponse([
                    'success' => false,
                    'error' => 'Product not found'
                ], 404);
                return;
            }

            // Update stock based on operation
            $result = $this->updateProductStock($productId, $variantId, $quantity, $operation, $reason);

            if ($result['success']) {
                $this->jsonResponse([
                    'success' => true,
                    'message' => 'Stock updated successfully',
                    'data' => [
                        'product_id' => $productId,
                        'variant_id' => $variantId,
                        'new_quantity' => $result['new_quantity'],
                        'operation' => $operation,
                        'adjustment' => $quantity
                    ]
                ]);
            } else {
                $this->jsonResponse([
                    'success' => false,
                    'error' => $result['error']
                ], 400);
            }

        } catch (Exception $e) {
            $this->jsonResponse([
                'success' => false,
                'error' => 'Failed to update stock',
                'message' => $this->isDebugMode() ? $e->getMessage() : 'An error occurred'
            ], 500);
        }
    }

    /**
     * Get inventory alerts
     * GET /api/admin/inventory/alerts
     */
    public function inventoryAlerts()
    {
        try {
            $alerts = [
                'low_stock' => $this->getLowStockProducts(),
                'out_of_stock' => $this->getOutOfStockProducts(),
                'overstocked' => $this->getOverstockedProducts(),
                'negative_stock' => $this->getNegativeStockProducts()
            ];

            $this->jsonResponse([
                'success' => true,
                'data' => $alerts
            ]);

        } catch (Exception $e) {
            $this->jsonResponse([
                'success' => false,
                'error' => 'Failed to fetch inventory alerts',
                'message' => $this->isDebugMode() ? $e->getMessage() : 'An error occurred'
            ], 500);
        }
    }

    /**
     * Get sales statistics
     */
    private function getSalesStats(string $startDate, string $endDate): array
    {
        $db = \App\Core\Database::getInstance();

        try {
            // Total revenue for current period
            $revenueResult = $db->queryOne(
                "SELECT
                    SUM(total_amount) as total_revenue,
                    COUNT(*) as total_orders,
                    AVG(total_amount) as average_order_value
                 FROM orders
                 WHERE payment_status = 'paid'
                 AND DATE(created_at) BETWEEN ? AND ?",
                [$startDate, $endDate]
            );

            // Previous period comparison
            $daysDiff = (strtotime($endDate) - strtotime($startDate)) / (60 * 60 * 24);
            $prevStartDate = date('Y-m-d', strtotime($startDate.' -'.$daysDiff.' days'));
            $prevEndDate = date('Y-m-d', strtotime($startDate.' -1 day'));

            $prevRevenueResult = $db->queryOne(
                "SELECT SUM(total_amount) as total_revenue FROM orders
                 WHERE payment_status = 'paid'
                 AND DATE(created_at) BETWEEN ? AND ?",
                [$prevStartDate, $prevEndDate]
            );

            $currentRevenue = (float) ($revenueResult['total_revenue'] ?? 0);
            $prevRevenue = (float) ($prevRevenueResult['total_revenue'] ?? 0);
            $revenueChange = $prevRevenue > 0 ? (($currentRevenue - $prevRevenue) / $prevRevenue) * 100 : 0;

            // Get daily sales trend for the period
            $trendData = $this->getDailySalesTrend($startDate, $endDate);

            return [
                'total_revenue' => $currentRevenue,
                'total_orders' => (int) ($revenueResult['total_orders'] ?? 0),
                'average_order_value' => (float) ($revenueResult['average_order_value'] ?? 0),
                'revenue_change' => round($revenueChange, 1),
                'currency' => 'THB',
                'trend' => $trendData
            ];
        } catch (Exception $e) {
            return [
                'total_revenue' => 0.0,
                'total_orders' => 0,
                'average_order_value' => 0.0,
                'revenue_change' => 0.0,
                'currency' => 'THB'
            ];
        }
    }

    /**
     * Get order statistics
     */
    private function getOrderStats(string $startDate, string $endDate): array
    {
        $db = \App\Core\Database::getInstance();

        try {
            $result = $db->query(
                "SELECT
                    status,
                    COUNT(*) as count,
                    SUM(total_amount) as total_amount
                 FROM orders
                 WHERE DATE(created_at) BETWEEN ? AND ?
                 GROUP BY status",
                [$startDate, $endDate]
            );

            $stats = [
                'total_orders' => 0,
                'orders_change' => 0,
                'by_status' => []
            ];

            foreach ($result as $row) {
                $stats['total_orders'] += $row['count'];
                $stats['by_status'][$row['status']] = [
                    'count' => (int) $row['count'],
                    'total_amount' => (float) $row['total_amount']
                ];
            }

            return $stats;
        } catch (Exception $e) {
            // Return default stats if query fails
            return [
                'total_orders' => 0,
                'orders_change' => 0,
                'by_status' => []
            ];
        }
    }

    /**
     * Get product statistics
     */
    private function getProductStats(): array
    {
        $db = \App\Core\Database::getInstance();

        try {
            $result = $db->queryOne(
                "SELECT
                    COUNT(*) as total_products,
                    SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active_products,
                    SUM(CASE WHEN status = 'inactive' THEN 1 ELSE 0 END) as inactive_products,
                    SUM(CASE WHEN featured = 1 THEN 1 ELSE 0 END) as featured_products
                 FROM products"
            );

            // Get products created this month vs last month for change calculation
            $thisMonth = date('Y-m-01');
            $lastMonth = date('Y-m-01', strtotime('-1 month'));
            $lastMonthEnd = date('Y-m-t', strtotime('-1 month'));

            $thisMonthResult = $db->queryOne(
                "SELECT COUNT(*) as this_month_products FROM products WHERE DATE(created_at) >= ?",
                [$thisMonth]
            );

            $lastMonthResult = $db->queryOne(
                "SELECT COUNT(*) as last_month_products FROM products WHERE DATE(created_at) BETWEEN ? AND ?",
                [$lastMonth, $lastMonthEnd]
            );

            $thisMonthCount = (int) ($thisMonthResult['this_month_products'] ?? 0);
            $lastMonthCount = (int) ($lastMonthResult['last_month_products'] ?? 0);
            $productsChange = $lastMonthCount > 0 ? (($thisMonthCount - $lastMonthCount) / $lastMonthCount) * 100 : 0;

            return [
                'total_products' => (int) ($result['total_products'] ?? 0),
                'active_products' => (int) ($result['active_products'] ?? 0),
                'inactive_products' => (int) ($result['inactive_products'] ?? 0),
                'featured_products' => (int) ($result['featured_products'] ?? 0),
                'products_change' => round($productsChange, 1)
            ];
        } catch (Exception $e) {
            return [
                'total_products' => 0,
                'active_products' => 0,
                'inactive_products' => 0,
                'featured_products' => 0,
                'products_change' => 0
            ];
        }
    }

    /**
     * Get customer statistics
     */
    private function getCustomerStats(string $startDate, string $endDate): array
    {
        $db = \App\Core\Database::getInstance();

        try {
            $result = $db->queryOne(
                "SELECT
                    COUNT(*) as total_customers,
                    SUM(CASE WHEN DATE(created_at) BETWEEN ? AND ? THEN 1 ELSE 0 END) as new_customers,
                    SUM(CASE WHEN is_active = 1 THEN 1 ELSE 0 END) as active_customers
                 FROM users
                 WHERE role = 'customer'",
                [$startDate, $endDate]
            );

            // Get previous period for comparison
            $daysDiff = (strtotime($endDate) - strtotime($startDate)) / (60 * 60 * 24);
            $prevStartDate = date('Y-m-d', strtotime($startDate.' -'.$daysDiff.' days'));
            $prevEndDate = date('Y-m-d', strtotime($startDate.' -1 day'));

            $prevResult = $db->queryOne(
                "SELECT COUNT(*) as prev_new_customers
                 FROM users
                 WHERE role = 'customer' AND DATE(created_at) BETWEEN ? AND ?",
                [$prevStartDate, $prevEndDate]
            );

            $currentNew = (int) ($result['new_customers'] ?? 0);
            $prevNew = (int) ($prevResult['prev_new_customers'] ?? 0);
            $customersChange = $prevNew > 0 ? (($currentNew - $prevNew) / $prevNew) * 100 : 0;

            return [
                'total_customers' => (int) ($result['total_customers'] ?? 0),
                'new_customers' => $currentNew,
                'active_customers' => (int) ($result['active_customers'] ?? 0),
                'customers_change' => round($customersChange, 1)
            ];
        } catch (Exception $e) {
            return [
                'total_customers' => 0,
                'new_customers' => 0,
                'active_customers' => 0,
                'customers_change' => 0
            ];
        }
    }

    /**
     * Get inventory statistics
     */
    private function getInventoryStats(): array
    {
        $db = \App\Core\Database::getInstance();

        // Get low stock and out of stock counts
        $stockResult = $db->queryOne(
            "SELECT
                COUNT(CASE WHEN pv.inventory_quantity <= p.min_stock_level AND pv.inventory_quantity > 0 THEN 1 END) as low_stock_count,
                COUNT(CASE WHEN pv.inventory_quantity = 0 THEN 1 END) as out_of_stock_count,
                SUM(pv.inventory_quantity * COALESCE(pv.price, p.base_price)) as inventory_value
             FROM products p
             LEFT JOIN product_variants pv ON p.id = pv.product_id
             WHERE p.status = 'active' AND p.track_inventory = 1"
        );

        return [
            'low_stock_count' => (int) ($stockResult['low_stock_count'] ?? 0),
            'out_of_stock_count' => (int) ($stockResult['out_of_stock_count'] ?? 0),
            'inventory_value' => (float) ($stockResult['inventory_value'] ?? 0)
        ];
    }

    /**
     * Get inventory data with pagination and filtering
     */
    private function getInventoryData(int $page, int $limit, ?string $status, ?string $search): array
    {
        $db = \App\Core\Database::getInstance();
        $offset = ($page - 1) * $limit;

        // Build WHERE clause
        $whereConditions = ['p.status = ?'];
        $params = ['active'];

        if ($search) {
            $whereConditions[] = '(p.name LIKE ? OR p.sku LIKE ?)';
            $params[] = "%{$search}%";
            $params[] = "%{$search}%";
        }

        $whereClause = implode(' AND ', $whereConditions);

        // Get total count
        $countSql = "SELECT COUNT(DISTINCT p.id) as total
                     FROM products p
                     WHERE {$whereClause}";
        $totalResult = $db->queryOne($countSql, $params);
        $total = (int) ($totalResult['total'] ?? 0);

        // Get inventory data
        $sql = "SELECT
                    p.id,
                    p.name,
                    p.sku,
                    p.base_price,
                    p.min_stock_level,
                    p.max_stock_level,
                    p.track_inventory,
                    p.inventory_quantity,
                    c.name as category_name
                FROM products p
                LEFT JOIN categories c ON p.category_id = c.id
                WHERE {$whereClause}
                ORDER BY p.name ASC
                LIMIT {$limit} OFFSET {$offset}";

        $results = $db->query($sql, $params);

        // Format products
        $products = [];
        foreach ($results as $row) {
            $products[] = [
                'id' => (int) $row['id'],
                'name' => $row['name'],
                'sku' => $row['sku'],
                'category' => $row['category_name'],
                'base_price' => (float) $row['base_price'],
                'min_stock_level' => (int) $row['min_stock_level'],
                'max_stock_level' => (int) $row['max_stock_level'],
                'track_inventory' => (bool) $row['track_inventory'],
                'inventory_quantity' => (int) $row['inventory_quantity'],
                'status' => $this->getStockStatus((int) $row['inventory_quantity'], (int) $row['min_stock_level'])
            ];
        }

        return [
            'inventory' => $products,
            'pagination' => [
                'current_page' => $page,
                'per_page' => $limit,
                'total' => $total,
                'total_pages' => ceil($total / $limit)
            ],
            'stats' => $this->getInventoryStats()
        ];
    }

    /**
     * Update product stock
     */
    private function updateProductStock(int $productId, ?int $variantId, int $quantity, string $operation, string $reason): array
    {
        $db = \App\Core\Database::getInstance();

        try {
            $db->getConnection()->beginTransaction();

            // Get current stock from products table
            $currentStock = $db->queryOne(
                "SELECT inventory_quantity FROM products WHERE id = ?",
                [$productId]
            );

            if (!$currentStock) {
                $db->getConnection()->rollBack();
                return ['success' => false, 'error' => 'Product not found'];
            }

            $currentQuantity = (int) $currentStock['inventory_quantity'];

            // Calculate new quantity based on operation
            switch ($operation) {
                case 'set':
                    $newQuantity = $quantity;
                    break;
                case 'add':
                    $newQuantity = $currentQuantity + $quantity;
                    break;
                case 'subtract':
                    $newQuantity = $currentQuantity - $quantity;
                    if ($newQuantity < 0) {
                        $db->getConnection()->rollBack();
                        return ['success' => false, 'error' => 'Insufficient stock for subtraction'];
                    }
                    break;
                default:
                    $db->getConnection()->rollBack();
                    return ['success' => false, 'error' => 'Invalid operation'];
            }

            // Update stock
            $db->execute(
                "UPDATE products SET inventory_quantity = ?, updated_at = NOW() WHERE id = ?",
                [$newQuantity, $productId]
            );

            // Log the inventory adjustment
            $this->logInventoryAdjustment($productId, $variantId, $currentQuantity, $newQuantity, $operation, $reason);

            $db->getConnection()->commit();

            return [
                'success' => true,
                'new_quantity' => $newQuantity
            ];

        } catch (Exception $e) {
            $db->getConnection()->rollBack();
            return ['success' => false, 'error' => 'Database error: '.$e->getMessage()];
        }
    }

    /**
     * Log inventory adjustment
     */
    private function logInventoryAdjustment(int $productId, ?int $variantId, int $oldQuantity, int $newQuantity, string $operation, string $reason): void
    {
        $db = \App\Core\Database::getInstance();

        // Create inventory_adjustments table if it doesn't exist
        $db->execute("
            CREATE TABLE IF NOT EXISTS inventory_adjustments (
                id INT AUTO_INCREMENT PRIMARY KEY,
                product_id INT NOT NULL,
                variant_id INT NULL,
                old_quantity INT NOT NULL,
                new_quantity INT NOT NULL,
                adjustment_quantity INT NOT NULL,
                operation ENUM('set', 'add', 'subtract') NOT NULL,
                reason TEXT,
                admin_id INT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE
            )
        ");

        $adjustmentQuantity = $newQuantity - $oldQuantity;
        $adminId = $_SESSION['user_id'] ?? null;

        $db->execute(
            "INSERT INTO inventory_adjustments (product_id, variant_id, old_quantity, new_quantity, adjustment_quantity, operation, reason, admin_id)
             VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
            [$productId, $variantId, $oldQuantity, $newQuantity, $adjustmentQuantity, $operation, $reason, $adminId]
        );
    }

    /**
     * Get stock status based on quantity and min level
     */
    private function getStockStatus(int $quantity, int $minLevel): string
    {
        if ($quantity <= 0) {
            return 'out_of_stock';
        } elseif ($quantity <= $minLevel) {
            return 'low_stock';
        } else {
            return 'in_stock';
        }
    }

    /**
     * Get low stock products
     */
    private function getLowStockProducts(): array
    {
        $db = \App\Core\Database::getInstance();

        $result = $db->query(
            "SELECT
                p.id,
                p.name,
                p.sku,
                p.inventory_quantity,
                p.min_stock_level
             FROM products p
             WHERE p.track_inventory = 1
             AND p.inventory_quantity <= p.min_stock_level
             AND p.inventory_quantity > 0
             ORDER BY p.inventory_quantity ASC
             LIMIT 20"
        );

        return array_map(function ($row) {
            return [
                'product_id' => (int) $row['id'],
                'name' => $row['name'],
                'sku' => $row['sku'],
                'current_stock' => (int) $row['inventory_quantity'],
                'min_stock_level' => (int) $row['min_stock_level']
            ];
        }, $result);
    }

    /**
     * Get out of stock products
     */
    private function getOutOfStockProducts(): array
    {
        $db = \App\Core\Database::getInstance();

        $result = $db->query(
            "SELECT
                p.id,
                p.name,
                p.sku
             FROM products p
             WHERE p.track_inventory = 1
             AND p.inventory_quantity = 0
             ORDER BY p.name ASC
             LIMIT 20"
        );

        return array_map(function ($row) {
            return [
                'product_id' => (int) $row['id'],
                'name' => $row['name'],
                'sku' => $row['sku']
            ];
        }, $result);
    }

    /**
     * Get overstocked products
     */
    private function getOverstockedProducts(): array
    {
        $db = \App\Core\Database::getInstance();

        $result = $db->query(
            "SELECT
                p.id,
                p.name,
                p.sku,
                p.inventory_quantity,
                p.max_stock_level
             FROM products p
             WHERE p.track_inventory = 1
             AND p.max_stock_level > 0
             AND p.inventory_quantity > p.max_stock_level
             ORDER BY (p.inventory_quantity - p.max_stock_level) DESC
             LIMIT 20"
        );

        return array_map(function ($row) {
            return [
                'product_id' => (int) $row['id'],
                'name' => $row['name'],
                'sku' => $row['sku'],
                'current_stock' => (int) $row['inventory_quantity'],
                'max_stock_level' => (int) $row['max_stock_level'],
                'excess_quantity' => (int) $row['inventory_quantity'] - (int) $row['max_stock_level']
            ];
        }, $result);
    }

    /**
     * Get products with negative stock
     */
    private function getNegativeStockProducts(): array
    {
        $db = \App\Core\Database::getInstance();

        $result = $db->query(
            "SELECT
                p.id,
                p.name,
                p.sku,
                p.inventory_quantity
             FROM products p
             WHERE p.track_inventory = 1
             AND p.inventory_quantity < 0
             ORDER BY p.inventory_quantity ASC
             LIMIT 20"
        );

        return array_map(function ($row) {
            return [
                'product_id' => (int) $row['id'],
                'name' => $row['name'],
                'sku' => $row['sku'],
                'current_stock' => (int) $row['inventory_quantity']
            ];
        }, $result);
    }

    // Placeholder methods for additional analytics
    /**
     * @param string $period
     * @param string $startDate
     * @param string $endDate
     */
    private function getRevenueTrend(string $period, string $startDate, string $endDate): array
    {return [];}
    /**
     * @param string $startDate
     * @param string $endDate
     */
    private function getTopSellingProducts(string $startDate, string $endDate): array
    {return [];}
    /**
     * @param string $startDate
     * @param string $endDate
     */
    private function getSalesByCategory(string $startDate, string $endDate): array
    {return [];}
    /**
     * @param string $startDate
     * @param string $endDate
     */
    private function getPaymentMethodStats(string $startDate, string $endDate): array
    {return [];}
    /**
     * @param string $startDate
     * @param string $endDate
     */
    private function getOrderStatusDistribution(string $startDate, string $endDate): array
    {return [];}
    /**
     * @param string $startDate
     * @param string $endDate
     */
    private function getNewCustomersData(string $startDate, string $endDate): array
    {return [];}
    private function getCustomerLifetimeValue(): array
    {return [];}
    /**
     * @param string $startDate
     * @param string $endDate
     */
    private function getRepeatCustomerStats(string $startDate, string $endDate): array
    {return [];}
    private function getCustomerSegments(): array
    {return [];}
    private function getCustomerGeographicData(): array
    {return [];}
    private function getInventoryValue(): array
    {return [];}
    private function getStockMovement(): array
    {return [];}
    private function getProductPerformance(): array
    {return [];}

    /**
     * Get customers for admin management
     * GET /api/admin/customers
     */
    public function customers()
    {
        try {
            $page = (int) ($_GET['page'] ?? 1);
            $limit = min((int) ($_GET['limit'] ?? 20), 100);
            $search = $_GET['search'] ?? '';
            $role = $_GET['role'] ?? '';
            $status = $_GET['status'] ?? '';

            $filters = [];
            if ($search) {
                $filters['search'] = $search;
            }

            if ($role) {
                $filters['role'] = $role;
            }

            if ($status !== '') {
                $filters['is_active'] = (bool) $status;
            }

            $offset = ($page - 1) * $limit;
            $result = $this->userModel->getUsers($filters, $limit, $offset);

            $this->jsonResponse([
                'success' => true,
                'data' => [
                    'users' => $result['users'],
                    'pagination' => [
                        'current_page' => $page,
                        'per_page' => $limit,
                        'total' => $result['total'],
                        'total_pages' => ceil($result['total'] / $limit)
                    ]
                ]
            ]);

        } catch (Exception $e) {
            $this->jsonResponse([
                'success' => false,
                'error' => 'Failed to fetch customers',
                'message' => $this->isDebugMode() ? $e->getMessage() : 'An error occurred'
            ], 500);
        }
    }

    /**
     * Get products for admin management
     * GET /api/admin/products
     */
    public function products()
    {
        try {
            $page = (int) ($_GET['page'] ?? 1);
            $limit = min((int) ($_GET['limit'] ?? 20), 100);
            $category = $_GET['category'] ?? '';
            $status = $_GET['status'] ?? '';
            $search = $_GET['search'] ?? '';

            $filters = [
                'status' => $status ?: null,
                'category_id' => $category ?: null,
                'search' => $search ?: null
            ];

            $offset = ($page - 1) * $limit;
            $result = $this->productModel->getProducts($filters, $limit, $offset);

            $this->jsonResponse([
                'success' => true,
                'data' => [
                    'products' => $result['products'],
                    'pagination' => [
                        'current_page' => $page,
                        'per_page' => $limit,
                        'total' => $result['total'],
                        'total_pages' => ceil($result['total'] / $limit)
                    ]
                ]
            ]);

        } catch (Exception $e) {
            $this->jsonResponse([
                'success' => false,
                'error' => 'Failed to fetch products',
                'message' => $this->isDebugMode() ? $e->getMessage() : 'An error occurred'
            ], 500);
        }
    }

    /**
     * Get categories for admin management
     * GET /api/admin/categories
     */
    public function categories()
    {
        try {
            $search = $_GET['search'] ?? '';
            $status = $_GET['status'] ?? '';
            $parent_id = $_GET['parent_id'] ?? null;

            $filters = [
                'search' => $search ?: null,
                'status' => $status ?: null,
                'parent_id' => $parent_id
            ];

            $categories = $this->categoryModel->getCategories($filters);

            $this->jsonResponse([
                'success' => true,
                'data' => [
                    'categories' => $categories
                ]
            ]);

        } catch (Exception $e) {
            $this->jsonResponse([
                'success' => false,
                'error' => 'Failed to fetch categories',
                'message' => $this->isDebugMode() ? $e->getMessage() : 'An error occurred'
            ], 500);
        }
    }

    /**
     * Create a new category
     * POST /api/admin/categories
     */
    public function createCategory()
    {
        try {
            $data = $this->getJsonInput();

            // Validate input
            $validator = new Validator();
            $rules = [
                'name' => ['required', 'string', 'min:2', 'max:100'],
                'description' => ['optional', 'string'],
                'parent_id' => ['optional', 'integer'],
                'status' => ['optional', 'string'],
                'sort_order' => ['optional', 'integer']
            ];

            if (!$validator->validate($data, $rules)) {
                $this->jsonResponse([
                    'success' => false,
                    'error' => 'Validation failed',
                    'errors' => $validator->getErrors()
                ], 400);
                return;
            }

            $categoryData = [
                'name' => trim($data['name']),
                'description' => isset($data['description']) ? trim($data['description']) : null,
                'parent_id' => isset($data['parent_id']) ? (int)$data['parent_id'] : null,
                'status' => $data['status'] ?? 'active',
                'sort_order' => $data['sort_order'] ?? 0
            ];

            $categoryId = $this->categoryModel->create($categoryData);

            if ($categoryId) {
                $category = $this->categoryModel->findById($categoryId);
                $this->jsonResponse([
                    'success' => true,
                    'message' => 'Category created successfully',
                    'data' => [
                        'category' => $category
                    ]
                ], 201);
            } else {
                throw new Exception('Failed to create category');
            }

        } catch (Exception $e) {
            $this->jsonResponse([
                'success' => false,
                'error' => 'Failed to create category',
                'message' => $this->isDebugMode() ? $e->getMessage() : 'An error occurred'
            ], 500);
        }
    }

    /**
     * Update category
     * POST /api/admin/categories/{id} with _method=PUT
     */
    public function updateCategory($categoryId)
    {
        try {
            $data = $this->getJsonInput();

            // Validate input
            $validator = new Validator();
            $rules = [
                'name' => ['required', 'string', 'min:2', 'max:100'],
                'description' => ['optional', 'string'],
                'parent_id' => ['optional', 'integer'],
                'status' => ['optional', 'string'],
                'sort_order' => ['optional', 'integer']
            ];

            if (!$validator->validate($data, $rules)) {
                $this->jsonResponse([
                    'success' => false,
                    'error' => 'Validation failed',
                    'errors' => $validator->getErrors()
                ], 400);
                return;
            }

            // Check if category exists
            $category = $this->categoryModel->findById($categoryId);
            if (!$category) {
                $this->jsonResponse([
                    'success' => false,
                    'error' => 'Category not found'
                ], 404);
                return;
            }

            $categoryData = [
                'name' => trim($data['name']),
                'description' => isset($data['description']) ? trim($data['description']) : null,
                'parent_id' => isset($data['parent_id']) ? (int)$data['parent_id'] : null,
                'status' => $data['status'] ?? $category['status'],
                'sort_order' => $data['sort_order'] ?? $category['sort_order']
            ];

            $updated = $this->categoryModel->update($categoryId, $categoryData);

            if ($updated) {
                $updatedCategory = $this->categoryModel->findById($categoryId);
                $this->jsonResponse([
                    'success' => true,
                    'message' => 'Category updated successfully',
                    'data' => [
                        'category' => $updatedCategory
                    ]
                ]);
            } else {
                throw new Exception('Failed to update category');
            }

        } catch (Exception $e) {
            $this->jsonResponse([
                'success' => false,
                'error' => 'Failed to update category',
                'message' => $this->isDebugMode() ? $e->getMessage() : 'An error occurred'
            ], 500);
        }
    }

    /**
     * Delete category
     * POST /api/admin/categories/{id}/delete with _method=DELETE
     */
    public function deleteCategory($categoryId)
    {
        try {
            // Check if category exists
            $category = $this->categoryModel->findById($categoryId);
            if (!$category) {
                $this->jsonResponse([
                    'success' => false,
                    'error' => 'Category not found'
                ], 404);
                return;
            }

            // Check if category has products
            $productCount = $this->productModel->countByCategory($categoryId);
            if ($productCount > 0) {
                $this->jsonResponse([
                    'success' => false,
                    'error' => 'Cannot delete category with existing products',
                    'message' => "This category has {$productCount} products. Please move or delete them first."
                ], 400);
                return;
            }

            // Check if category has subcategories
            $subcategoryCount = $this->categoryModel->countSubcategories($categoryId);
            if ($subcategoryCount > 0) {
                $this->jsonResponse([
                    'success' => false,
                    'error' => 'Cannot delete category with subcategories',
                    'message' => "This category has {$subcategoryCount} subcategories. Please move or delete them first."
                ], 400);
                return;
            }

            $deleted = $this->categoryModel->delete($categoryId);

            if ($deleted) {
                $this->jsonResponse([
                    'success' => true,
                    'message' => 'Category deleted successfully'
                ]);
            } else {
                throw new Exception('Failed to delete category');
            }

        } catch (Exception $e) {
            $this->jsonResponse([
                'success' => false,
                'error' => 'Failed to delete category',
                'message' => $this->isDebugMode() ? $e->getMessage() : 'An error occurred'
            ], 500);
        }
    }

    /**
     * Get orders for admin management
     * GET /api/admin/orders
     */
    public function orders()
    {
        try {
            $page = (int) ($_GET['page'] ?? 1);
            $limit = min((int) ($_GET['limit'] ?? 20), 100);
            $status = $_GET['status'] ?? '';
            $paymentStatus = $_GET['payment_status'] ?? '';
            $dateFrom = $_GET['date_from'] ?? '';
            $dateTo = $_GET['date_to'] ?? '';

            $filters = [];
            if ($status) {
                $filters['status'] = $status;
            }

            if ($paymentStatus) {
                $filters['payment_status'] = $paymentStatus;
            }

            if ($dateFrom) {
                $filters['date_from'] = $dateFrom;
            }

            if ($dateTo) {
                $filters['date_to'] = $dateTo;
            }

            $offset = ($page - 1) * $limit;
            $result = $this->orderModel->getOrders($filters, $limit, $offset);

            $this->jsonResponse([
                'success' => true,
                'data' => [
                    'orders' => $result['orders'],
                    'pagination' => [
                        'current_page' => $page,
                        'per_page' => $limit,
                        'total' => $result['total'],
                        'total_pages' => ceil($result['total'] / $limit)
                    ]
                ]
            ]);

        } catch (Exception $e) {
            $this->jsonResponse([
                'success' => false,
                'error' => 'Failed to fetch orders',
                'message' => $this->isDebugMode() ? $e->getMessage() : 'An error occurred'
            ], 500);
        }
    }

    /**
     * Update order status
     * POST /api/admin/orders/{id}/status
     */
    public function updateOrderStatus($orderId)
    {
        try {
            $data = $this->getJsonInput();

            $validator = new Validator();
            $isValid = $validator->validate($data, [
                'status' => ['required', 'in:pending,confirmed,processing,shipped,delivered,cancelled'],
                'notes' => ['max:1000']
            ]);

            if (!$isValid) {
                $this->jsonResponse([
                    'success' => false,
                    'error' => 'Validation failed',
                    'errors' => $validator->getErrors()
                ], 400);
                return;
            }

            $success = $this->orderModel->updateStatus(
                $orderId,
                $data['status'],
                $data['notes'] ?? null
            );

            if ($success) {
                // TODO: Send notification email to customer
                $this->jsonResponse([
                    'success' => true,
                    'message' => 'Order status updated successfully'
                ]);
            } else {
                $this->jsonResponse([
                    'success' => false,
                    'error' => 'Failed to update order status'
                ], 500);
            }

        } catch (Exception $e) {
            $this->jsonResponse([
                'success' => false,
                'error' => 'Failed to update order status',
                'message' => $this->isDebugMode() ? $e->getMessage() : 'An error occurred'
            ], 500);
        }
    }

    /**
     * Get daily sales trend data for chart
     */
    private function getDailySalesTrend(string $startDate, string $endDate): array
    {
        $db = \App\Core\Database::getInstance();

        try {
            // Get daily sales data
            $result = $db->query(
                "SELECT
                    DATE(created_at) as date,
                    SUM(total_amount) as daily_revenue,
                    COUNT(*) as daily_orders
                 FROM orders
                 WHERE payment_status = 'paid'
                 AND DATE(created_at) BETWEEN ? AND ?
                 GROUP BY DATE(created_at)
                 ORDER BY DATE(created_at)",
                [$startDate, $endDate]
            );

            // Create labels and data arrays
            $labels = [];
            $data = [];

            // Fill in missing dates with 0 values
            $start = strtotime($startDate);
            $end = strtotime($endDate);
            $dailyData = [];

            // Index existing data by date
            foreach ($result as $row) {
                $dailyData[$row['date']] = (float) $row['daily_revenue'];
            }

            // Generate complete date range
            for ($current = $start; $current <= $end; $current += 86400) {
                $dateStr = date('Y-m-d', $current);
                $labels[] = date('M j', $current); // Format: "Aug 1"
                $data[] = $dailyData[$dateStr] ?? 0;
            }

            return [
                'labels' => $labels,
                'data' => $data
            ];

        } catch (Exception $e) {
            // Return empty trend data on error
            return [
                'labels' => [],
                'data' => []
            ];
        }
    }
}