<?php
// ============================================================
//  MJPOS – ProductController
// ============================================================

class ProductController extends Controller
{
    public function index(): void
    {
        Auth::requirePermission('products.view');

        $search  = trim($this->get('search', ''));
        $catId   = (int) $this->get('category', 0);
        $brandId = (int) $this->get('brand', 0);
        $status  = $this->get('status', 'active');
        $stock   = $this->get('stock',  'all'); // all | low | out
        $page    = max(1, (int) $this->get('page', 1));

        $where  = ['1=1'];
        $params = [];

        if ($search) {
            $where[]  = '(p.product_name LIKE ? OR p.product_code LIKE ? OR p.barcode LIKE ?)';
            $params[] = "%{$search}%";
            $params[] = "%{$search}%";
            $params[] = "%{$search}%";
        }
        if ($catId)   { $where[] = 'p.category_id = ?'; $params[] = $catId; }
        if ($brandId) { $where[] = 'p.brand_id = ?';    $params[] = $brandId; }
        if ($status !== 'all') { $where[] = 'p.status = ?'; $params[] = $status; }
        if ($stock === 'low') {
            $where[] = 'p.current_stock > 0 AND p.current_stock <= p.reorder_level';
        } elseif ($stock === 'out') {
            $where[] = 'p.current_stock <= 0';
        }

        $sql = 'SELECT p.*, c.category_name, b.brand_name, u.short_name AS unit_short
                FROM products p
                LEFT JOIN categories c ON c.id = p.category_id
                LEFT JOIN brands     b ON b.id = p.brand_id
                LEFT JOIN units      u ON u.id = p.unit_id
                WHERE ' . implode(' AND ', $where) . '
                ORDER BY p.product_name ASC';

        $pagination = Database::paginate($sql, $params, $page);
        $categories = Database::fetchAll("SELECT id, category_name FROM categories WHERE status='active' ORDER BY category_name");
        $brands     = Database::fetchAll("SELECT id, brand_name FROM brands WHERE status='active' ORDER BY brand_name");

        // Summary stats (all active products, not filtered)
        $totalActive     = (int)   Database::fetchColumn("SELECT COUNT(*) FROM products WHERE status='active'");
        $totalLowStock   = (int)   Database::fetchColumn("SELECT COUNT(*) FROM products WHERE status='active' AND current_stock > 0 AND current_stock <= reorder_level");
        $totalOutStock   = (int)   Database::fetchColumn("SELECT COUNT(*) FROM products WHERE status='active' AND current_stock <= 0");
        $totalStockValue = (float) Database::fetchColumn("SELECT COALESCE(SUM(current_stock * cost_price),0) FROM products WHERE status='active'");

        $pageTitle  = 'Products';
        $breadcrumb = [['label' => 'Products']];
        ob_start();
        require APP_PATH . '/views/products/index.php';
        $content = ob_get_clean();
        require APP_PATH . '/views/layouts/main.php';
    }

    public function create(): void
    {
        Auth::requirePermission('products.create');
        $this->formPage(null);
    }

    public function store(): void
    {
        Auth::requirePermission('products.create');
        Auth::requireCsrf();
        $data = $this->validateAndPrepare();
        if (!$data) return;

        // Handle image upload
        $data['product_image'] = $this->handleImageUpload();

        try {
            $id = Database::insert(
                'INSERT INTO products
                 (category_id,brand_id,unit_id,product_code,barcode,product_name,
                  generic_name,description,cost_price,selling_price,wholesale_price,
                  reorder_level,current_stock,tax_percent,discount_percent,product_image,
                  has_expiry,expiry_date,is_batch,is_serial,status)
                 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
                array_values($data)
            );

            // Record opening stock movement
            if ((float)$data['current_stock'] > 0) {
                Database::execute(
                    'INSERT INTO stock_movements
                     (product_id,movement_type,quantity_in,stock_after,reference_no,note,created_by)
                     VALUES (?,?,?,?,?,?,?)',
                    [(int)$id,'opening',$data['current_stock'],$data['current_stock'],
                     'OPENING','Opening stock', Auth::id()]
                );
            }

            Auth::logAudit(Auth::id(),'product_create','products',
                "Created product: {$data['product_name']} (code: {$data['product_code']})");
            redirectWith(APP_URL.'/products','success','Product created successfully.');
        } catch (PDOException $e) {
            $this->flash('error', 'Error: ' . ($e->getCode()==23000
                ? 'Product code or barcode already exists.'
                : $e->getMessage()));
            $this->redirect(APP_URL.'/products/create');
        }
    }

    public function edit(string $id): void
    {
        Auth::requirePermission('products.edit');
        $product = $this->findOr404((int)$id);
        $this->formPage($product);
    }

    public function update(string $id): void
    {
        Auth::requirePermission('products.edit');
        Auth::requireCsrf();
        $product = $this->findOr404((int)$id);
        $data    = $this->validateAndPrepare();
        if (!$data) return;

        $image = $this->handleImageUpload() ?? $product['product_image'];
        $data['product_image'] = $image;

        Database::execute(
            'UPDATE products SET
             category_id=?,brand_id=?,unit_id=?,product_code=?,barcode=?,product_name=?,
             generic_name=?,description=?,cost_price=?,selling_price=?,wholesale_price=?,
             reorder_level=?,current_stock=?,tax_percent=?,discount_percent=?,product_image=?,
             has_expiry=?,expiry_date=?,is_batch=?,is_serial=?,status=?
             WHERE id=?',
            [...array_values($data), (int)$id]
        );

        Auth::logAudit(Auth::id(),'product_update','products',
            "Updated product ID {$id}", $product, $data);
        redirectWith(APP_URL.'/products','success','Product updated.');
    }

    public function delete(string $id): void
    {
        Auth::requirePermission('products.delete');
        Auth::requireCsrf();
        Database::execute("UPDATE products SET status='inactive' WHERE id=?", [(int)$id]);
        Auth::logAudit(Auth::id(),'product_delete','products',"Deactivated product ID {$id}");
        redirectWith(APP_URL.'/products','success','Product deactivated.');
    }

    // ════════════════════════════════════════════════════════
    //  BARCODE GENERATING SYSTEM
    // ════════════════════════════════════════════════════════

    /** Generate a unique 12-digit numeric code (EAN-13 compatible base) */
    private function generateBarcode(): string
    {
        for ($attempt = 0; $attempt < 10; $attempt++) {
            // Prefix 200 = internal/in-store use range (standard GS1 convention)
            $base = '200' . str_pad((string) random_int(0, 999999999), 9, '0', STR_PAD_LEFT);
            $code = $base . $this->ean13CheckDigit($base);

            $exists = Database::fetchColumn(
                'SELECT COUNT(*) FROM products WHERE barcode = ?', [$code]
            );
            if (!$exists) return $code;
        }
        // Extremely unlikely fallback
        return '200' . time();
    }

    /** Calculate EAN-13 check digit for a 12-digit base */
    private function ean13CheckDigit(string $base12): string
    {
        $sum = 0;
        for ($i = 0; $i < 12; $i++) {
            $digit = (int) $base12[$i];
            $sum  += ($i % 2 === 0) ? $digit : $digit * 3;
        }
        $check = (10 - ($sum % 10)) % 10;
        return (string) $check;
    }

    /** AJAX: generate a fresh barcode preview (used by the Add/Edit Product form) */
    public function generateBarcodeAjax(): void
    {
        Auth::requirePermission('products.create');
        $this->json(['barcode' => $this->generateBarcode()]);
    }

    /** Single product barcode label — printable page */
    public function barcodeLabel(string $id): void
    {
        Auth::requirePermission('products.view');
        $product = $this->findOr404((int)$id);

        if (empty($product['barcode'])) {
            // Auto-assign one on the fly if missing, persist it
            $newCode = $this->generateBarcode();
            Database::execute('UPDATE products SET barcode=? WHERE id=?', [$newCode, (int)$id]);
            $product['barcode'] = $newCode;
        }

        $pageTitle = 'Barcode Label — ' . $product['product_name'];
        require APP_PATH . '/views/products/barcode_label.php';
    }

    /** Bulk barcode label sheet — select multiple products, print a full sheet */
    public function barcodeSheet(): void
    {
        Auth::requirePermission('products.view');

        $idsParam = trim($this->get('ids', ''));
        $catId    = (int) $this->get('category', 0);
        $search   = trim($this->get('search', ''));

        $where  = ["status = 'active'"];
        $params = [];

        if ($idsParam) {
            $ids = array_filter(array_map('intval', explode(',', $idsParam)));
            if ($ids) {
                $ph = implode(',', array_fill(0, count($ids), '?'));
                $where[] = "id IN ($ph)";
                $params  = array_merge($params, $ids);
            }
        } else {
            if ($catId) {
                $where[] = 'category_id = ?';
                $params[] = $catId;
            }
            if ($search) {
                $where[] = '(product_name LIKE ? OR product_code LIKE ?)';
                $params[] = "%$search%";
                $params[] = "%$search%";
            }
        }

        $products = Database::fetchAll(
            'SELECT id, product_code, product_name, barcode, selling_price
             FROM products WHERE ' . implode(' AND ', $where) .
            ' ORDER BY product_name LIMIT 200',
            $params
        );

        // Auto-assign barcodes to any selected product missing one
        foreach ($products as &$p) {
            if (empty($p['barcode'])) {
                $newCode = $this->generateBarcode();
                Database::execute('UPDATE products SET barcode=? WHERE id=?', [$newCode, $p['id']]);
                $p['barcode'] = $newCode;
            }
        }
        unset($p);

        $categories = Database::fetchAll(
            "SELECT id, category_name FROM categories WHERE status='active' ORDER BY category_name"
        );

        $pageTitle  = 'Barcode Label Sheet';
        $breadcrumb = [
            ['label' => 'Products', 'url' => APP_URL . '/products'],
            ['label' => 'Barcode Labels'],
        ];
        ob_start();
        require APP_PATH . '/views/products/barcode_picker.php';
        $content = ob_get_clean();
        require APP_PATH . '/views/layouts/main.php';
    }

    /** Print-only output (no app layout) for the selected label sheet */
    public function barcodeSheetPrint(): void
    {
        Auth::requirePermission('products.view');

        $idsParam = trim($this->get('ids', ''));
        $ids = array_filter(array_map('intval', explode(',', $idsParam)));
        if (!$ids) {
            echo 'No products selected.';
            return;
        }

        $ph = implode(',', array_fill(0, count($ids), '?'));
        $products = Database::fetchAll(
            "SELECT id, product_code, product_name, barcode, selling_price
             FROM products WHERE id IN ($ph)",
            $ids
        );

        $copies = max(1, (int) $this->get('copies', 1));
        $labelW = $this->get('size', 'medium'); // small|medium|large

        require APP_PATH . '/views/products/barcode_sheet_print.php';
    }

    /** AJAX product search for POS and purchase forms */
    public function search(): void
    {
        Auth::requireLogin();
        $q = trim($this->get('q', ''));
        if (strlen($q) < 1) { $this->json([]); return; }

        $products = Database::fetchAll(
            "SELECT p.id, p.product_code, p.product_name, p.barcode,
                    p.selling_price, p.cost_price, p.wholesale_price,
                    p.current_stock, p.tax_percent, p.discount_percent,
                    u.short_name AS unit
             FROM products p
             LEFT JOIN units u ON u.id = p.unit_id
             WHERE p.status = 'active'
               AND (p.product_name LIKE ? OR p.product_code LIKE ? OR p.barcode = ?)
             LIMIT 15",
            ["%{$q}%", "%{$q}%", $q]
        );
        $this->json($products);
    }

    // ── Private helpers ─────────────────────────────────────

    private function formPage(?array $product): void
    {
        $categories = Database::fetchAll("SELECT id,category_name FROM categories WHERE status='active' ORDER BY category_name");
        $brands     = Database::fetchAll("SELECT id,brand_name FROM brands WHERE status='active' ORDER BY brand_name");
        $units      = Database::fetchAll("SELECT id,unit_name,short_name FROM units ORDER BY unit_name");

        $pageTitle  = $product ? 'Edit Product' : 'Add Product';
        $breadcrumb = [
            ['label'=>'Products','url'=> APP_URL.'/products'],
            ['label'=> $pageTitle]
        ];
        ob_start();
        require APP_PATH . '/views/products/form.php';
        $content = ob_get_clean();
        require APP_PATH . '/views/layouts/main.php';
    }

    private function findOr404(int $id): array
    {
        $product = Database::fetchOne('SELECT * FROM products WHERE id=?', [$id]);
        if (!$product) { http_response_code(404); die('Product not found.'); }
        return $product;
    }

    private function validateAndPrepare(): array|false
    {
        $d = [
            'category_id'      => ($this->post('category_id') ?: null),
            'brand_id'         => ($this->post('brand_id') ?: null),
            'unit_id'          => ($this->post('unit_id') ?: null),
            'product_code'     => trim($this->post('product_code','')),
            'barcode'          => trim($this->post('barcode','')) ?: null,
            'product_name'     => trim($this->post('product_name','')),
            'generic_name'     => trim($this->post('generic_name','')) ?: null,
            'description'      => trim($this->post('description','')) ?: null,
            'cost_price'       => (float) $this->post('cost_price',0),
            'selling_price'    => (float) $this->post('selling_price',0),
            'wholesale_price'  => (float) $this->post('wholesale_price',0),
            'reorder_level'    => (int)   $this->post('reorder_level',0),
            'current_stock'    => (float) $this->post('current_stock',0),
            'tax_percent'      => (float) $this->post('tax_percent',0),
            'discount_percent' => (float) $this->post('discount_percent',0),
            'product_image'    => null,
            'has_expiry'       => 0,       // set below after expiry logic
            'expiry_date'      => null,    // set below after expiry logic
            'is_batch'         => $this->post('is_batch') ? 1 : 0,
            'is_serial'        => $this->post('is_serial') ? 1 : 0,
            'status'           => $this->post('status','active'),
        ];

        if (empty($d['product_code']) || empty($d['product_name'])) {
            $this->flash('error','Product code and name are required.');
            $this->back();
            return false;
        }

        // Leave barcode empty if not provided — do NOT auto-generate on save.
        // User must explicitly click the ↻ Generate button in the form.
        if (empty($d['barcode'])) {
            $d['barcode'] = null;
        }

        // Expiry date — resolved now that $d exists
        $hasExpiry  = $this->post('has_expiry') ? 1 : 0;
        $expiryDate = trim($this->post('expiry_date',''));
        $d['has_expiry']  = $hasExpiry;
        $d['expiry_date'] = ($hasExpiry && $expiryDate) ? $expiryDate : null;
        if ($d['selling_price'] <= 0) {
            $this->flash('error','Selling price must be greater than 0.');
            $this->back();
            return false;
        }
        return $d;
    }

    private function handleImageUpload(): ?string
    {
        if (empty($_FILES['product_image']['tmp_name'])) return null;
        $file = $_FILES['product_image'];
        if ($file['size'] > MAX_FILE_SIZE) {
            $this->flash('warning','Image too large (max 2MB). Product saved without image.');
            return null;
        }
        $type = mime_content_type($file['tmp_name']);
        if (!in_array($type, ALLOWED_IMG_TYPES, true)) {
            $this->flash('warning','Invalid image type. Allowed: JPG, PNG, WebP.');
            return null;
        }
        $ext      = pathinfo($file['name'], PATHINFO_EXTENSION);
        $filename = randomCode(16) . '.' . strtolower($ext);
        $dest     = UPLOAD_PATH . '/products/' . $filename;
        @mkdir(dirname($dest), 0755, true);
        if (move_uploaded_file($file['tmp_name'], $dest)) {
            return 'products/' . $filename;
        }
        return null;
    }
}