<?php

declare(strict_types=1);

namespace Lab;

use PDO;
use ZipArchive;

final class OrderStore
{
    public function __construct(private PDO $pdo, private array $config, private Photos $photos) {}

    public function create(array $customer, array $snapshot, array $products): array
    {
        if (($snapshot['print_count'] ?? 0) < 1) {
            throw new \RuntimeException('Διάλεξε τουλάχιστον μία εκτύπωση.');
        }

        $channel = (string) ($snapshot['channel'] ?? 'kiosk');
        $delivery = (string) ($customer['delivery'] ?? 'pickup');
        if ($channel === 'kiosk') {
            $delivery = 'pickup';
        }
        $acs = $delivery === 'acs' ? (int) ($this->config['shipping']['acs_cents'] ?? 490) : 0;
        $options = $snapshot['options'] ?? [];
        $rush = ! empty($options['rush']) ? 1 : 0;
        $rushFee = $rush ? (int) ($this->config['shipping']['rush_cents'] ?? 200) : 0;
        $paper = in_array(($options['paper'] ?? 'glossy'), ['glossy', 'matte'], true) ? $options['paper'] : 'glossy';
        $border = in_array(($options['border'] ?? 'none'), ['none', 'white'], true) ? $options['border'] : 'none';
        $payment = in_array(($options['payment'] ?? 'counter'), ['counter', 'cash', 'card'], true) ? $options['payment'] : 'counter';
        $total = (int) $snapshot['print_cents'] + $acs + $rushFee;
        $code = Helpers::orderCode();
        $orderDir = rtrim((string) $this->config['orders_dir'], '/') . '/' . $code;
        mkdir($orderDir, 0775, true);
        mkdir($orderDir . '/originals', 0775, true);

        $items = [];
        $cart = Cart::get();
        foreach ($snapshot['photos'] as $photoRow) {
            $id = (string) $photoRow['id'];
            $photo = $cart['photos'][$id] ?? null;
            if (! $photo) {
                continue;
            }
            $src = $this->photos->pathFor($photo);
            $dest = $orderDir . '/originals/' . $photo['stored_name'];
            copy($src, $dest);
            $items[] = [
                'photo_key' => $id,
                'original_name' => $photo['original_name'],
                'stored_name' => $photo['stored_name'],
                'width' => $photo['width'],
                'height' => $photo['height'],
                'fit' => $photoRow['fit'],
                'rotate' => $photoRow['rotate'],
                'straighten' => (float) ($photoRow['straighten'] ?? 0),
                'caption' => (string) ($photoRow['caption'] ?? ''),
                'brightness' => (int) ($photoRow['brightness'] ?? 0),
                'contrast' => (int) ($photoRow['contrast'] ?? 0),
                'saturation' => (int) ($photoRow['saturation'] ?? 0),
                'midtones' => (int) ($photoRow['midtones'] ?? 0),
                'wbR' => (float) ($photoRow['wbR'] ?? 1),
                'wbG' => (float) ($photoRow['wbG'] ?? 1),
                'wbB' => (float) ($photoRow['wbB'] ?? 1),
                'filter' => (string) ($photoRow['filter'] ?? 'none'),
                'flip' => (int) ($photoRow['flip'] ?? 0),
                'sharpen' => (int) ($photoRow['sharpen'] ?? 0),
                'order_border' => $border,
                'crop' => $photoRow['crop'],
                'lines' => $photoRow['lines'],
            ];
        }

        $this->pdo->beginTransaction();
        try {
            $insert = $this->pdo->prepare(
                'INSERT INTO orders (code, channel, status, customer_name, customer_phone, customer_email,
                    store_id, delivery, address_line, notes, paper, border, rush, payment, total_cents, items_json)
                 VALUES (:code, :channel, "new", :name, :phone, :email, :store_id, :delivery, :address, :notes,
                    :paper, :border, :rush, :payment, :total, :items)'
            );
            $insert->execute([
                'code' => $code,
                'channel' => $channel,
                'name' => $customer['name'],
                'phone' => $customer['phone'],
                'email' => $customer['email'] !== '' ? $customer['email'] : null,
                'store_id' => $customer['store_id'] !== '' ? $customer['store_id'] : null,
                'delivery' => $delivery,
                'address' => $customer['address'] !== '' ? $customer['address'] : null,
                'notes' => $customer['notes'] !== '' ? $customer['notes'] : null,
                'paper' => $paper,
                'border' => $border,
                'rush' => $rush,
                'payment' => $payment,
                'total' => $total,
                'items' => json_encode($items, JSON_UNESCAPED_UNICODE),
            ]);
            $orderId = (int) $this->pdo->lastInsertId();
            $photoStmt = $this->pdo->prepare(
                'INSERT INTO order_photos (order_id, photo_key, original_name, stored_name, width, height, bytes)
                 VALUES (:order_id, :photo_key, :original_name, :stored_name, :width, :height, :bytes)'
            );
            foreach ($items as $item) {
                $bytes = 0;
                $path = $orderDir . '/originals/' . $item['stored_name'];
                if (is_file($path)) {
                    $bytes = (int) filesize($path);
                }
                $photoStmt->execute([
                    'order_id' => $orderId,
                    'photo_key' => $item['photo_key'],
                    'original_name' => $item['original_name'],
                    'stored_name' => $item['stored_name'],
                    'width' => $item['width'],
                    'height' => $item['height'],
                    'bytes' => $bytes,
                ]);
            }
            $this->pdo->commit();
        } catch (\Throwable $e) {
            $this->pdo->rollBack();
            throw $e;
        }

        $order = $this->findByCode($code);
        if (! $order) {
            throw new \RuntimeException('Η παραγγελία δεν καταχωρήθηκε.');
        }
        file_put_contents($orderDir . '/order.json', json_encode([
            'order' => $order,
            'items' => $items,
            'total_cents' => $total,
        ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));

        $_SESSION['lab_last_order'] = $code;
        Cart::reset($channel);

        return $order;
    }

    public function find(int $id): ?array
    {
        $stmt = $this->pdo->prepare('SELECT * FROM orders WHERE id = :id');
        $stmt->execute(['id' => $id]);
        $row = $stmt->fetch();

        return $row ?: null;
    }

    public function findByCode(string $code): ?array
    {
        $stmt = $this->pdo->prepare('SELECT * FROM orders WHERE code = :code');
        $stmt->execute(['code' => $code]);
        $row = $stmt->fetch();

        return $row ?: null;
    }

    /** @return list<array<string, mixed>> */
    public function list(?string $status = null, string $q = ''): array
    {
        $q = trim($q);
        $where = [];
        $params = [];
        if ($status && $status !== 'all') {
            $where[] = 'status = :status';
            $params['status'] = $status;
        }
        if ($q !== '') {
            $where[] = '(customer_name LIKE :q OR customer_phone LIKE :q OR code LIKE :q)';
            $params['q'] = '%' . $q . '%';
        }
        $sql = 'SELECT * FROM orders';
        if ($where) {
            $sql .= ' WHERE ' . implode(' AND ', $where);
        }
        $sql .= ' ORDER BY id DESC LIMIT 300';
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);

        return $stmt->fetchAll() ?: [];
    }

    /** @return list<array<string, mixed>> */
    public function today(): array
    {
        return $this->pdo->query(
            "SELECT * FROM orders WHERE date(created_at) = date('now','localtime') AND status != 'cancelled' ORDER BY id DESC"
        )->fetchAll() ?: [];
    }

    public function stats(): array
    {
        $today = $this->pdo->query(
            "SELECT COUNT(*) FROM orders WHERE date(created_at) = date('now','localtime') AND status != 'cancelled'"
        )->fetchColumn();
        $open = $this->pdo->query(
            "SELECT COUNT(*) FROM orders WHERE status IN ('new','printing')"
        )->fetchColumn();
        $ready = $this->pdo->query(
            "SELECT COUNT(*) FROM orders WHERE status = 'ready'"
        )->fetchColumn();
        $revenue = $this->pdo->query(
            "SELECT COALESCE(SUM(total_cents),0) FROM orders WHERE date(created_at) = date('now','localtime') AND status != 'cancelled'"
        )->fetchColumn();

        return [
            'today' => (int) $today,
            'open' => (int) $open,
            'ready' => (int) $ready,
            'revenue_cents' => (int) $revenue,
        ];
    }

    public function setStatus(int $id, string $status): void
    {
        $allowed = ['new', 'printing', 'ready', 'delivered', 'cancelled'];
        if (! in_array($status, $allowed, true)) {
            throw new \RuntimeException('Άγνωστη κατάσταση.');
        }
        $stmt = $this->pdo->prepare(
            'UPDATE orders SET status = :status, updated_at = CURRENT_TIMESTAMP WHERE id = :id'
        );
        $stmt->execute(['status' => $status, 'id' => $id]);
    }

    public function exportHotfolder(array $order, array $products): string
    {
        $code = (string) $order['code'];
        $srcDir = rtrim((string) $this->config['orders_dir'], '/') . '/' . $code . '/originals';
        $destRoot = rtrim((string) $this->config['hotfolder_dir'], '/') . '/' . $code;
        if (is_dir($destRoot)) {
            $this->rrmdir($destRoot);
        }
        mkdir($destRoot, 0775, true);

        $items = json_decode((string) $order['items_json'], true);
        if (! is_array($items)) {
            $items = [];
        }
        $job = [];
        foreach ($items as $item) {
            $source = $srcDir . '/' . $item['stored_name'];
            if (! is_file($source)) {
                continue;
            }
            foreach ($item['lines'] as $line) {
                $sku = (string) $line['sku'];
                $product = $products[$sku] ?? null;
                if (! $product) {
                    continue;
                }
                $qty = max(1, (int) $line['qty']);
                $sizeDir = $destRoot . '/' . $sku;
                if (! is_dir($sizeDir)) {
                    mkdir($sizeDir, 0775, true);
                }
                $base = pathinfo((string) $item['original_name'], PATHINFO_FILENAME);
                $base = preg_replace('/[^\w.-]+/', '_', $base) ?: 'photo';
                $out = $sizeDir . '/' . $base . '_x' . $qty . '.jpg';
                $item['order_border'] = (string) ($order['border'] ?? 'none');
                $this->photos->exportPrint($source, $item, $product, $out);
                $job[] = [
                    'file' => $sku . '/' . basename($out),
                    'sku' => $sku,
                    'qty' => $qty,
                    'fit' => $item['fit'] ?? 'fill',
                ];
            }
        }
        file_put_contents($destRoot . '/job.json', json_encode([
            'code' => $code,
            'customer' => $order['customer_name'],
            'phone' => $order['customer_phone'],
            'files' => $job,
        ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));

        $stmt = $this->pdo->prepare(
            'UPDATE orders SET status = CASE WHEN status = "new" THEN "printing" ELSE status END,
                exported_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = :id'
        );
        $stmt->execute(['id' => $order['id']]);

        return $destRoot;
    }

    public function zipOrder(array $order): string
    {
        $code = (string) $order['code'];
        $dir = rtrim((string) $this->config['orders_dir'], '/') . '/' . $code;
        $zipPath = $dir . '/' . $code . '.zip';
        $zip = new ZipArchive();
        if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
            throw new \RuntimeException('Δεν δημιουργήθηκε το zip.');
        }
        $originals = $dir . '/originals';
        if (is_dir($originals)) {
            foreach (scandir($originals) ?: [] as $name) {
                if ($name === '.' || $name === '..') {
                    continue;
                }
                $file = $originals . '/' . $name;
                if (is_file($file)) {
                    $zip->addFile($file, $name);
                }
            }
        }
        $json = $dir . '/order.json';
        if (is_file($json)) {
            $zip->addFile($json, 'order.json');
        }
        $zip->close();

        return $zipPath;
    }

    public function originalPath(array $order, string $stored): string
    {
        $path = rtrim((string) $this->config['orders_dir'], '/') . '/' . $order['code'] . '/originals/' . basename($stored);
        if (! is_file($path)) {
            throw new \RuntimeException('Το αρχείο δεν βρέθηκε.');
        }

        return $path;
    }

    private function rrmdir(string $dir): void
    {
        foreach (scandir($dir) ?: [] as $base) {
            if ($base === '.' || $base === '..') {
                continue;
            }
            $item = $dir . '/' . $base;
            if (is_dir($item)) {
                $this->rrmdir($item);
            } else {
                @unlink($item);
            }
        }
        @rmdir($dir);
    }
}
