<?php
declare(strict_types=1);

namespace App\Controller;

use Cake\Event\EventInterface;
use Cake\Http\Exception\ForbiddenException;
use Cake\Http\Exception\NotFoundException;
use Cake\Utility\Text;

/**
 * ProductsController
 * - Public (no login): index, view, shop (alias), addToCart
 * - Admin only: add, edit, delete
 * - Image upload field name: "image_file"
 */
class ProductBrowserController extends AppController
{
    private \App\Model\Table\ProductsTable $Products;
    /** Allow guests for browse/cart actions */
    public function beforeFilter(EventInterface $event): void
    {
        parent::beforeFilter($event);
        $this->Products = $this->fetchTable('Products');
        // Authentication plugin present? -> allow unauthenticated actions
        if ($this->components()->has('Authentication')) {
            $this->Authentication->allowUnauthenticated(['index', 'view', 'shop', 'addToCart']);
        }
    }

    /** Helper: skip Authorization only if the component exists */
    private function skipAuthzIfPresent(): void
    {
        if ($this->components()->has('Authorization')) {
            $this->Authorization->skipAuthorization();
        }
    }

    /** Helper: true if current user is admin */
    private function isAdmin(): bool
    {
        $identity = $this->request->getAttribute('identity');
        return (bool) ($identity && ($identity->get('role') === 'admin' || $identity->get('is_admin') === true));
    }

    /** Landing alias for "Shop Now" / "Shop All" buttons */
    public function shop()
    {
        $this->skipAuthzIfPresent();
        return $this->redirect(['action' => 'index']);
    }

    /**
     * Product Browser (All Products)
     * Supports query params: q, category, min, max, sort (price|name|created), direction (asc|desc)
     * Also exposes priceMin/priceMax for a slider UI.
     */
    public function index()
    {
        $this->skipAuthzIfPresent();

        $q = $this->Products->find()->where(['archived' => 'No']);

        // --- SEARCH ---
        $term = trim((string)$this->request->getQuery('q', ''));
        if ($term !== '') {
            $safe = str_replace(['%', '_'], ['\%','\_'], $term);
            $q->where(function ($exp) use ($safe) {
                return $exp->or([
                    'Products.name LIKE'        => "%$safe%",
                    'Products.description LIKE' => "%$safe%",
                ]);
            });
        }
        $this->set('searchTerm', $term);

        // --- FILTERS ---
        $category = (string)$this->request->getQuery('category', '');
        $min = $this->request->getQuery('min');
        $max = $this->request->getQuery('max');

        if ($category !== '') {
            if ($category === 'Sale') {
                // Only rows where sale_price is set and less than price
                $q->where(function ($exp) {
                    return $exp->and([
                        $exp->isNotNull('Products.sale_price'),
                        'Products.sale_price < Products.price'
                    ]);
                });
            } else {
                $q->where(['Products.category' => $category]);
            }
        }
        if (is_numeric($min)) { $q->where(['Products.price >=' => (float)$min]); }
        if (is_numeric($max)) { $q->where(['Products.price <=' => (float)$max]); }

        // --- SORT ---
        $sort = (string)$this->request->getQuery('sort', '');
        $direction = strtolower((string)$this->request->getQuery('direction', 'asc')) === 'desc' ? 'DESC' : 'ASC';
        $sortable = ['name' => 'Products.name', 'price' => 'Products.price', 'created' => 'Products.created'];
        $q->orderBy(isset($sortable[$sort]) ? [$sortable[$sort] => $direction] : ['Products.created' => 'DESC']);

        // --- PRICE BOUNDS (for UI slider) ---
        $bounds = $this->Products->find()
            ->select([
                'minp' => $this->Products->find()->func()->min('price'),
                'maxp' => $this->Products->find()->func()->max('price'),
            ])->first();
        $priceMin = (float)($bounds->minp ?? 0);
        $priceMax = (float)($bounds->maxp ?? 0);

        // --- PAGINATION ---
        $this->paginate = ['limit' => 6];
        $products = $this->paginate($q);

        $this->set(compact('products', 'term', 'category', 'min', 'max', 'sort', 'direction', 'priceMin', 'priceMax'));
    }

    /** View single product */
    public function view($id = null)
    {
        $this->skipAuthzIfPresent();

        $product = $this->Products->get($id);
        $this->set(compact('product'));
    }

    /** ADMIN: Create product */
    public function add()
    {
        if (!$this->isAdmin()) {
            throw new ForbiddenException('Admins only.');
        }

        $product = $this->Products->newEmptyEntity();

        if ($this->request->is('post')) {
            $product = $this->Products->patchEntity($product, $this->request->getData());

            $this->handleImageUpload($product);

            if ($this->Products->save($product)) {
                $this->Flash->success(__('Product added.'));
                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error(__('Could not add product. Please try again.'));
        }

        $this->set(compact('product'));
    }

    /** ADMIN: Edit product */
    public function edit($id = null)
    {
        if (!$this->isAdmin()) {
            throw new ForbiddenException('Admins only.');
        }

        $product = $this->Products->get($id);

        if ($this->request->is(['patch', 'post', 'put'])) {
            $oldImage = $product->image ?? null;

            $product = $this->Products->patchEntity($product, $this->request->getData());

            $this->handleImageUpload($product, $oldImage);

            if ($this->Products->save($product)) {
                $this->Flash->success(__('Product updated.'));
                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error(__('Could not update product. Please try again.'));
        }

        $this->set(compact('product'));
    }

    /** ADMIN: Delete product */
    public function delete($id = null)
    {
        if (!$this->isAdmin()) {
            throw new ForbiddenException('Admins only.');
        }

        $this->request->allowMethod(['post', 'delete']);
        $product = $this->Products->findById($id)->first();
        if (!$product) {
            throw new NotFoundException('Product not found');
        }

        $filename = $product->image ?? null;

        if ($this->Products->delete($product)) {
            if ($filename) {
                $path = WWW_ROOT . 'img' . DS . 'products' . DS . $filename;
                if (is_file($path)) { @unlink($path); }
            }
            $this->Flash->success(__('The product has been deleted.'));
        } else {
            $this->Flash->error(__('The product could not be deleted. Please, try again.'));
        }

        return $this->redirect(['action' => 'index']);
    }

    /**
     * Add to cart (session-based)
     * Session structure: Cart = [product_id => qty]
     */
    public function addToCart($id = null)
    {
        $this->skipAuthzIfPresent();
        $this->request->allowMethod(['post', 'get']);
        $product = $this->Products->findById($id)->first();
        if (!$product) {
            throw new NotFoundException('Product not found');
        }
        $quantity = (int)($this->request->getData('quantity') ?: $this->request->getQuery('quantity', 1));
        if ($quantity < 1 || $quantity > $product->stock) {
            $quantity = 1;
        }
        $session = $this->request->getSession();
        $cart = (array)$session->read('Cart');
        $cart[$product->id] = ($cart[$product->id] ?? 0) + $quantity;
        $session->write('Cart', $cart);
        $this->Flash->success(__('{0} (x{1}) added to cart.', $product->name, $quantity));
        return $this->redirect($this->referer(['action' => 'index'], true));
    }

    /**
     * Save uploaded file from 'image_file' to /webroot/img/products and set $product->image
     * @param \Cake\Datasource\EntityInterface $product
     * @param string|null $oldFilename old filename to remove after replace
     */
    private function handleImageUpload($product, ?string $oldFilename = null): void
    {
        $upload = $this->request->getData('image_file');

        if (!$upload || $upload->getError() !== UPLOAD_ERR_OK || !$upload->getClientFilename()) {
            return; // nothing uploaded
        }

        $ext = strtolower(pathinfo($upload->getClientFilename(), PATHINFO_EXTENSION));
        $allowed = ['png', 'jpg', 'jpeg', 'webp'];
        if (!in_array($ext, $allowed, true)) {
            $this->Flash->error('Unsupported image type. Use PNG/JPG/WebP.');
            return;
        }

        $dir = WWW_ROOT . 'img' . DS . 'products';
        if (!is_dir($dir)) {
            mkdir($dir, 0775, true);
        }

        $filename = Text::uuid() . 'ProductBrowser' . $ext;
        $upload->moveTo($dir . DS . $filename);

        // Persist only filename; render as /img/products/<filename> in the view
        $product->image = $filename;

        // remove old file if replaced
        if ($oldFilename && $oldFilename !== $filename) {
            $oldPath = $dir . DS . $oldFilename;
            if (is_file($oldPath)) { @unlink($oldPath); }
        }
    }
}
