<?php

declare(strict_types=1);

namespace Lab\Controllers;

use Lab\Cart;
use Lab\Changelog;
use Lab\Helpers;
use Lab\I18n;
use Lab\License;
use Lab\Mailer;
use Lab\Media;
use Lab\OrderStore;
use Lab\Output;
use Lab\Photos;
use Lab\Products;
use Lab\Settings;
use Lab\View;

final class KioskController
{
    public function __construct(
        private array $config,
        private Products $products,
        private Photos $photos,
        private OrderStore $orders,
        private Output $output,
    ) {}

    public function attract(): void
    {
        if (\PHP_OS_FAMILY === 'Darwin') {
            I18n::set('el');
            View::redirect('/kiosk');
        }
        View::render('attract', [
            'title' => 'Κιόσκ εκτυπώσεων',
            'kiosk' => true,
        ]);
    }

    public function install(): void
    {
        $root = dirname(__DIR__, 2);
        $setupBytes = 0;
        foreach ([
            $root . '/download/PanoramaLab-Setup.exe',
            $root . '/public/download/PanoramaLab-Setup.exe',
            $root . '/dist/PanoramaLab-Setup.exe',
        ] as $path) {
            if (is_file($path)) {
                $setupBytes = (int) filesize($path);
                break;
            }
        }
        $zipBytes = 0;
        foreach ([
            $root . '/download/PanoramaLab-Windows.zip',
            $root . '/public/download/PanoramaLab-Windows.zip',
            $root . '/dist/PanoramaLab-Windows.zip',
        ] as $path) {
            if (is_file($path)) {
                $zipBytes = (int) filesize($path);
                break;
            }
        }
        $macBytes = 0;
        foreach ([
            $root . '/download/PanoramaLab-Mac.zip',
            $root . '/public/download/PanoramaLab-Mac.zip',
            $root . '/dist/PanoramaLab-Mac.zip',
        ] as $path) {
            if (is_file($path)) {
                $macBytes = (int) filesize($path);
                break;
            }
        }
        $version = (string) ($this->config['version'] ?? '');
        View::render('install', [
            'title' => 'Λήψη Panorama Lab',
            'version' => $version,
            'changes' => Changelog::notesFor($version),
            'setupUrl' => Helpers::url('/download/PanoramaLab-Setup.exe'),
            'setupMb' => $setupBytes > 0 ? number_format($setupBytes / 1048576, 0, ',', '.') : '24',
            'zipUrl' => Helpers::url('/download/PanoramaLab-Windows.zip'),
            'zipMb' => $zipBytes > 0 ? number_format($zipBytes / 1048576, 0, ',', '.') : '21',
            'macUrl' => Helpers::url('/download/PanoramaLab-Mac.zip'),
            'macMb' => $macBytes > 0 ? number_format($macBytes / 1048576, 0, ',', '.') : '',
        ]);
    }

    public function kiosk(): void
    {
        Cart::boot('kiosk');
        Cart::setChannel('kiosk');
        Cart::applyOptions(['lang' => I18n::lang()]);
        View::render('workspace', [
            'title' => I18n::t('app'),
            'mode' => 'kiosk',
            'kiosk' => true,
            'products' => $this->products->active(),
            'stores' => $this->config['stores'] ?? [],
            'i18n' => I18n::all(),
            'lang' => I18n::lang(),
        ]);
    }

    public function web(): void
    {
        Cart::boot('web');
        Cart::setChannel('web');
        Cart::applyOptions(['lang' => I18n::lang()]);
        View::render('workspace', [
            'title' => I18n::t('online_order'),
            'mode' => 'web',
            'kiosk' => false,
            'products' => $this->products->active(),
            'stores' => $this->config['stores'] ?? [],
            'i18n' => I18n::all(),
            'lang' => I18n::lang(),
        ]);
    }

    public function thanks(): void
    {
        $code = (string) ($_SESSION['lab_last_order'] ?? '');
        $order = $code !== '' ? $this->orders->findByCode($code) : null;
        $how = (string) ($_GET['how'] ?? ($_SESSION['lab_last_fulfillment'] ?? 'later'));
        $how = in_array($how, ['now', 'soon', 'later'], true) ? $how : 'later';
        View::render('thanks', [
            'title' => 'Ευχαριστούμε',
            'order' => $order,
            'kiosk' => ($order['channel'] ?? 'kiosk') === 'kiosk',
            'how' => $how,
        ]);
    }

    public function apiReset(): void
    {
        $this->requireCsrf();
        $body = Helpers::readJson();
        $channel = (string) ($body['channel'] ?? 'kiosk');
        Cart::reset($channel === 'web' ? 'web' : 'kiosk');
        Helpers::json(['ok' => true, 'cart' => Cart::snapshot($this->products->map())]);
    }

    public function apiCart(): void
    {
        Cart::boot();
        if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
            $this->requireCsrf();
            $body = Helpers::readJson();
            if (isset($body['edits']) && is_array($body['edits'])) {
                Cart::applyEdits($body['edits']);
            }
            if (isset($body['options']) && is_array($body['options'])) {
                Cart::applyOptions($body['options']);
            }
            if (! empty($body['duplicate']) && is_string($body['duplicate'])) {
                Cart::duplicatePhoto($body['duplicate'], fn (array $photo) => $this->photos->copyStored(Cart::sid(), $photo));
            }
            if (! empty($body['remove'])) {
                $ids = is_array($body['remove']) ? $body['remove'] : [$body['remove']];
                foreach ($ids as $rid) {
                    $rid = (string) $rid;
                    $photo = Cart::photo($rid);
                    if (! $photo) {
                        continue;
                    }
                    $path = $photo['path'] ?? '';
                    if (is_string($path) && $path !== '' && is_file($path)) {
                        @unlink($path);
                    }
                    Cart::removePhoto($rid);
                }
            }
        }
        Helpers::json(['ok' => true, 'cart' => Cart::snapshot($this->products->map())]);
    }

    public function apiUpload(): void
    {
        $this->requireCsrf();
        Cart::boot();
        set_time_limit(180);
        $max = (int) ($this->config['limits']['max_photos'] ?? 500);
        $cart = Cart::get();
        $used = count($cart['photos']);
        $contentLen = (int) ($_SERVER['CONTENT_LENGTH'] ?? 0);
        $files = $this->normalizeFiles($_FILES['photos'] ?? $_FILES['photo'] ?? null);
        if ($files === []) {
            $msg = $contentLen > 0
                ? 'Τα αρχεία είναι πολύ μεγάλα για μία μεταφόρτωση. Διάλεξε λιγότερα ή χρησιμοποίησε «Κάρτα / USB — όλα».'
                : 'Δεν επιλέχθηκαν αρχεία.';
            Helpers::json(['ok' => false, 'error' => $msg, 'errors' => [$msg]], 400);
        }
        $added = [];
        $errors = [];
        foreach ($files as $file) {
            if ($used >= $max) {
                $errors[] = 'Φτάσατε το όριο των ' . $max . ' φωτογραφιών.';
                break;
            }
            try {
                $photo = $this->photos->saveUpload(Cart::sid(), $file);
                Cart::addPhoto($photo);
                $added[] = $photo['id'];
                $used++;
            } catch (\Throwable $e) {
                $errors[] = ($file['name'] ?? 'αρχείο') . ': ' . $e->getMessage();
            }
        }
        Helpers::json([
            'ok' => $added !== [],
            'added' => $added,
            'errors' => $errors,
            'cart' => Cart::snapshot($this->products->map()),
        ], $added !== [] ? 200 : 400);
    }

    public function apiCompose(): void
    {
        $this->requireCsrf();
        Cart::boot();
        $body = Helpers::readJson();
        $type = (string) ($body['type'] ?? '');
        $ids = $body['ids'] ?? [];
        if (! is_array($ids)) {
            $ids = [];
        }
        $cart = Cart::get();
        $picked = [];
        foreach ($ids as $id) {
            $photo = $cart['photos'][(string) $id] ?? null;
            if ($photo) {
                $picked[] = $photo;
            }
        }
        if ($picked === [] && in_array($type, ['index', 'calendar', 'calendar-1', 'calendar-12', 'idsheet'], true)) {
            $picked = array_values($cart['photos']);
        }
        $edits = $cart['edits'] ?? [];
        foreach ($picked as &$photo) {
            $id = (string) ($photo['id'] ?? '');
            $e = is_array($edits[$id] ?? null) ? $edits[$id] : [];
            $photo['crop'] = $e['crop'] ?? $photo['crop'] ?? ['x' => 0, 'y' => 0, 'w' => 1, 'h' => 1];
            $photo['rotate'] = $e['rotate'] ?? $photo['rotate'] ?? 0;
            $photo['flip'] = $e['flip'] ?? $photo['flip'] ?? 0;
            $photo['straighten'] = $e['straighten'] ?? $photo['straighten'] ?? 0;
            $photo['brightness'] = $e['brightness'] ?? 0;
            $photo['contrast'] = $e['contrast'] ?? 0;
            $photo['saturation'] = $e['saturation'] ?? 0;
            $photo['midtones'] = $e['midtones'] ?? 0;
            $photo['wbR'] = $e['wbR'] ?? 1;
            $photo['wbG'] = $e['wbG'] ?? 1;
            $photo['wbB'] = $e['wbB'] ?? 1;
            $photo['filter'] = $e['filter'] ?? 'none';
        }
        unset($photo);
        try {
            if ($type === 'idsheet') {
                @set_time_limit(120);
                $pages = $this->photos->composeIdSheets(Cart::sid(), $picked);
                $lastId = '';
                foreach ($pages as $photo) {
                    Cart::addPhoto($photo);
                    Cart::applyEdits([
                        $photo['id'] => ['qty' => ['id9x13' => 1], 'fit' => 'fill'],
                    ]);
                    $lastId = (string) $photo['id'];
                }
                Helpers::json([
                    'ok' => true,
                    'id' => $lastId,
                    'cart' => Cart::snapshot($this->products->map()),
                ]);
            }
            if ($type === 'calendar' || $type === 'calendar-1' || $type === 'calendar-12') {
                @set_time_limit(120);
                $style = (string) ($body['style'] ?? '');
                if ($style === '' && $type === 'calendar-12') {
                    $style = 'months';
                }
                if ($style === '' && $type === 'calendar-1') {
                    $style = 'wall';
                }
                if ($style === '') {
                    $style = (string) (Cart::get()['options']['calendar_style'] ?? 'wall');
                }
                if (! in_array($style, ['wall', 'months', 'classic'], true)) {
                    $style = 'wall';
                }
                $pages = $this->photos->composeCalendar(Cart::sid(), $picked, $style);
                $sku = $style === 'wall' ? 'cal2027-1' : 'cal2027-12';
                $lastId = '';
                foreach ($pages as $photo) {
                    Cart::addPhoto($photo);
                    Cart::applyEdits([
                        $photo['id'] => ['qty' => [$sku => 1], 'fit' => 'fit'],
                    ]);
                    $lastId = (string) $photo['id'];
                }
                Helpers::json([
                    'ok' => true,
                    'id' => $lastId,
                    'cart' => Cart::snapshot($this->products->map()),
                ]);
            }
            $photo = $type === 'collage'
                ? $this->photos->composeCollage(Cart::sid(), $picked)
                : $this->photos->composeIndex(Cart::sid(), $picked);
            Cart::addPhoto($photo);
            Cart::applyEdits([
                $photo['id'] => ['qty' => ['10x15' => 1], 'fit' => 'fit'],
            ]);
            Helpers::json([
                'ok' => true,
                'id' => $photo['id'],
                'cart' => Cart::snapshot($this->products->map()),
            ]);
        } catch (\Throwable $e) {
            Helpers::json(['ok' => false, 'error' => $e->getMessage()], 422);
        }
    }

    public function apiCheckout(): void
    {
        $this->requireCsrf();
        Cart::boot();
        $body = Helpers::readJson();
        if (isset($body['edits']) && is_array($body['edits'])) {
            Cart::applyEdits($body['edits']);
        }
        if (isset($body['options']) && is_array($body['options'])) {
            Cart::applyOptions($body['options']);
        }
        $skip = ! empty($body['skip_customer']) || ! empty($body['skip_customer']);
        $name = trim((string) ($body['name'] ?? ''));
        $phone = preg_replace('/\s+/', '', (string) ($body['phone'] ?? '')) ?? '';
        $email = trim((string) ($body['email'] ?? ''));
        $storeId = '';
        $delivery = (string) ($body['delivery'] ?? 'pickup');
        $address = trim((string) ($body['address'] ?? ''));
        $notes = trim((string) ($body['notes'] ?? ''));
        if ($skip) {
            if ($name === '') {
                $name = 'Πελάτης κιόσκ';
            }
            if ($phone === '') {
                $phone = '0000000000';
            }
        } else {
            if ($name === '' || mb_strlen($name) < 2) {
                Helpers::json(['ok' => false, 'error' => 'Γράψε το όνομά σου ή επίλεξε παράβλεψη στοιχείων.'], 422);
            }
            if (! preg_match('/^[0-9+\-]{8,20}$/', $phone)) {
                Helpers::json(['ok' => false, 'error' => 'Γράψε ένα έγκυρο τηλέφωνο ή επίλεξε παράβλεψη στοιχείων.'], 422);
            }
        }
        if ($email !== '' && ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
            Helpers::json(['ok' => false, 'error' => 'Το email δεν είναι έγκυρο.'], 422);
        }
        if ($delivery === 'acs' && $address === '') {
            Helpers::json(['ok' => false, 'error' => 'Για ACS χρειάζεται διεύθυνση.'], 422);
        }
        try {
            $snapshot = Cart::snapshot($this->products->map());
            $order = $this->orders->create([
                'name' => $name,
                'phone' => $phone,
                'email' => $email,
                'store_id' => $storeId,
                'delivery' => $delivery === 'acs' ? 'acs' : 'pickup',
                'address' => $address,
                'notes' => $notes,
            ], $snapshot, $this->products->map());
            $printDir = '';
            try {
                $printDir = $this->output->saveFiles($order);
            } catch (\Throwable $ignored) {
                $printDir = '';
            }
            Mailer::sendNewOrder($order, $this->config);
            $_SESSION['lab_last_fulfillment'] = 'later';
            Helpers::json([
                'ok' => true,
                'code' => $order['code'],
                'total_cents' => (int) $order['total_cents'],
                'print_count' => (int) ($snapshot['print_count'] ?? 0),
                'print_count' => (int) ($snapshot['print_count'] ?? 0),
                'channel' => (string) ($order['channel'] ?? 'kiosk'),
                'print_dir' => $printDir,
                'print_dir' => $printDir,
                'redirect' => Helpers::url('/thanks'),
            ]);
        } catch (\Throwable $e) {
            Helpers::json(['ok' => false, 'error' => $e->getMessage()], 422);
        }
    }

    public function apiProducts(): void
    {
        Helpers::json(['ok' => true, 'products' => $this->products->active()]);
    }

    public function apiDrives(): void
    {
        Helpers::json(['ok' => true, 'drives' => Media::drives()]);
    }

    public function apiImportDrive(): void
    {
        $this->requireCsrf();
        Cart::boot();
        set_time_limit(180);
        $body = Helpers::readJson();
        $max = (int) ($this->config['limits']['max_photos'] ?? 500);
        $used = count(Cart::get()['photos']);
        $label = 'τις φωτογραφίες';
        $files = [];
        $kiosk = License::isKiosk();
        $paths = $body['paths'] ?? [];
        if ($kiosk && is_array($paths) && $paths !== []) {
            $files = $this->kioskLocalFiles($paths);
            $label = 'τα επιλεγμένα αρχεία';
        } else {
            $id = (string) ($body['drive'] ?? $body['folder'] ?? '');
            $drive = Media::resolveDrive($id);
            if (! $drive && $kiosk && $id !== '') {
                $real = realpath($id);
                if ($real && is_dir($real) && $this->kioskPathAllowed($real)) {
                    $drive = ['id' => $real, 'label' => basename($real), 'path' => $real];
                }
            }
            if (! $drive) {
                Helpers::json(['ok' => false, 'error' => 'Δεν βρέθηκε κάρτα, USB ή φάκελος. Διάλεξέ τον ξανά.'], 404);
            }
            $label = (string) $drive['label'];
            $files = Media::findImages((string) $drive['path'], $max);
        }
        if ($files === []) {
            if ($kiosk && is_array($paths) && $paths !== []) {
                error_log('kiosk import dropped paths: ' . json_encode($paths, JSON_UNESCAPED_UNICODE));
            }
            Helpers::json(['ok' => false, 'error' => 'Δεν βρέθηκαν φωτογραφίες JPEG, PNG ή WebP.'], 404);
        }
        $added = [];
        $errors = [];
        foreach ($files as $path) {
            if ($used >= $max) {
                $errors[] = 'Φτάσατε το όριο των ' . $max . ' φωτογραφιών.';
                break;
            }
            try {
                $photo = $this->photos->importFromPath(Cart::sid(), $path);
                Cart::addPhoto($photo);
                $added[] = $photo['id'];
                $used++;
            } catch (\Throwable $e) {
                $errors[] = basename($path) . ': ' . $e->getMessage();
            }
        }
        Helpers::json([
            'ok' => $added !== [],
            'added' => $added,
            'errors' => $errors,
            'label' => $label,
            'cart' => Cart::snapshot($this->products->map()),
        ], $added !== [] ? 200 : 400);
    }

    /** @param list<mixed> $raw */
    private function kioskLocalFiles(array $raw): array
    {
        $out = [];
        foreach ($raw as $item) {
            $path = $this->resolveReadableFile((string) $item);
            if ($path === null) {
                continue;
            }
            $out[] = $path;
        }

        return $out;
    }

    private function resolveReadableFile(string $item): ?string
    {
        $path = trim(html_entity_decode($item, ENT_QUOTES, 'UTF-8'));
        if ($path === '') {
            return null;
        }
        $slash = str_replace('/', '\\', $path);
        foreach ([$path, $slash] as $cand) {
            $real = @realpath($cand);
            $use = $real !== false ? $real : $cand;
            if (is_file($use) && $this->kioskPathAllowed($use)) {
                return $use;
            }
        }

        return null;
    }

    private function kioskPathAllowed(string $real): bool
    {
        $real = str_replace('\\', '/', $real);
        foreach (['/Users/', '/Volumes/', '/tmp/', '/private/tmp/', '/private/var/folders/', '/home/'] as $prefix) {
            if (str_starts_with($real, $prefix)) {
                return true;
            }
        }

        return PHP_OS_FAMILY === 'Windows' && preg_match('#^[A-Za-z]:/#', $real) === 1;
    }

    public function apiExit(): void
    {
        $this->requireCsrf();
        $body = Helpers::readJson();
        $pin = trim((string) ($body['pin'] ?? ''));
        $expected = (string) ($this->config['admin']['pin'] ?? '1989');
        if (! hash_equals($expected, $pin)) {
            Helpers::json(['ok' => false, 'error' => 'Λάθος PIN.'], 403);
        }
        $flag = dirname((string) $this->config['db_path']) . '/exit.flag';
        file_put_contents($flag, (string) time());
        Helpers::json(['ok' => true]);
    }

    public function apiPrinters(): void
    {
        $this->requireCsrf();
        $body = Helpers::readJson();
        if (! $this->staffPinOk((string) ($body['pin'] ?? ''))) {
            Helpers::json(['ok' => false, 'error' => 'Λάθος PIN προσωπικού.'], 403);
        }
        $code = (string) ($_SESSION['lab_last_order'] ?? '');
        $order = $code !== '' ? $this->orders->findByCode($code) : null;
        Helpers::json([
            'ok' => true,
            'printers' => Output::listPrinters(),
            'sizes' => $order ? $this->output->sizesInOrder($order) : [['id' => 'all', 'name' => 'Όλα τα μεγέθη', 'inch' => '', 'qty' => 0]],
            'hotfolder' => trim((string) (Settings::get()['dnp_hotfolder'] ?? '')) !== '',
        ]);
    }

    public function apiPrintNow(): void
    {
        $this->requireCsrf();
        $body = Helpers::readJson();
        if (! $this->staffPinOk((string) ($body['pin'] ?? ''))) {
            Helpers::json(['ok' => false, 'error' => 'Λάθος PIN προσωπικού.'], 403);
        }
        $code = (string) ($_SESSION['lab_last_order'] ?? '');
        if ($code === '') {
            Helpers::json(['ok' => false, 'error' => 'Δεν υπάρχει πρόσφατη παραγγελία.'], 404);
        }
        $order = $this->orders->findByCode($code);
        if (! $order) {
            Helpers::json(['ok' => false, 'error' => 'Η παραγγελία δεν βρέθηκε.'], 404);
        }
        $printer = trim((string) ($body['printer'] ?? ''));
        $sku = (string) ($body['sku'] ?? 'all');
        try {
            $result = $this->output->sendDnp($order, $printer === '' ? null : $printer, $sku);
            $_SESSION['lab_last_fulfillment'] = 'now';
            Helpers::json([
                'ok' => true,
                'code' => $code,
                'printed' => $result['printed'],
                'copied' => $result['copied'],
                'folder' => $result['folder'],
                'printer' => $result['printer'],
                'size' => $result['size'] ?? $sku,
                'windows' => PHP_OS_FAMILY === 'Windows',
                'redirect' => Helpers::url('/thanks?how=now'),
            ]);
        } catch (\Throwable $e) {
            Helpers::json(['ok' => false, 'error' => $e->getMessage()], 422);
        }
    }

    public function apiFinishLater(): void
    {
        $this->requireCsrf();
        $body = Helpers::readJson();
        $code = (string) ($_SESSION['lab_last_order'] ?? '');
        if ($code === '') {
            Helpers::json(['ok' => false, 'error' => 'Δεν υπάρχει πρόσφατη παραγγελία.'], 404);
        }
        $order = $this->orders->findByCode($code);
        if (! $order) {
            Helpers::json(['ok' => false, 'error' => 'Η παραγγελία δεν βρέθηκε.'], 404);
        }
        $when = (string) ($body['when'] ?? 'later');
        $when = $when === 'soon' ? 'soon' : 'later';
        $folder = '';
        try {
            $folder = $this->output->saveFiles($order);
        } catch (\Throwable $e) {
            Helpers::json(['ok' => false, 'error' => 'Δεν αποθηκεύτηκαν τα αρχεία: ' . $e->getMessage()], 422);
        }
        $_SESSION['lab_last_fulfillment'] = $when;
        Helpers::json([
            'ok' => true,
            'code' => $code,
            'folder' => $folder,
            'redirect' => Helpers::url('/thanks?how=' . $when),
        ]);
    }

    public function thumb(string $id): void
    {
        $photo = Cart::photo($id);
        if (! $photo) {
            http_response_code(404);
            exit;
        }
        session_write_close();
        $this->photos->outputThumb($photo);
    }

    public function preview(string $id): void
    {
        $photo = Cart::photo($id);
        if (! $photo) {
            http_response_code(404);
            exit;
        }
        session_write_close();
        $this->photos->outputPreview($photo);
    }

    public function original(string $id): void
    {
        $photo = Cart::photo($id);
        if (! $photo) {
            http_response_code(404);
            exit;
        }
        session_write_close();
        $this->photos->outputOriginal($photo);
    }

    private function staffPinOk(string $pin): bool
    {
        $pin = trim($pin);
        $expected = (string) ($this->config['admin']['pin'] ?? '1989');

        return $pin !== '' && strlen($pin) === strlen($expected) && hash_equals($expected, $pin);
    }

    private function requireCsrf(): void
    {
        if (! Helpers::verifyCsrf($_POST['csrf_token'] ?? null)) {
            Helpers::json(['ok' => false, 'error' => 'Η ενέργεια απορρίφθηκε.'], 403);
        }
    }

    /** @return list<array<string, mixed>> */
    private function normalizeFiles(mixed $files): array
    {
        if (! is_array($files) || ! isset($files['tmp_name'])) {
            return [];
        }
        if (is_array($files['tmp_name'])) {
            $out = [];
            foreach ($files['tmp_name'] as $i => $tmp) {
                $out[] = [
                    'name' => $files['name'][$i] ?? 'photo',
                    'type' => $files['type'][$i] ?? '',
                    'tmp_name' => $tmp,
                    'error' => $files['error'][$i] ?? UPLOAD_ERR_NO_FILE,
                    'size' => $files['size'][$i] ?? 0,
                ];
            }

            return $out;
        }

        return [$files];
    }
}
