<?php

declare(strict_types=1);

namespace FatBottom\Controllers;

use FatBottom\Core\Auth;
use FatBottom\Core\Config;
use FatBottom\Core\Database;
use FatBottom\Core\Http;
use FatBottom\Core\Security;
use FatBottom\Core\View;
use FatBottom\Services\CartService;
use FatBottom\Services\Mailer;
use FatBottom\Services\MenuPdfExporter;
use FatBottom\Services\MySqlMigrator;
use FatBottom\Services\PaymentGateway;
use FatBottom\Services\StatsService;
use FatBottom\Services\OrderStatusService;

final class AdminController extends BaseController
{
    public function __construct(
        Config $config,
        Auth $auth,
        CartService $cart,
        private readonly Database $db,
        private readonly StatsService $stats,
        private readonly PaymentGateway $payments,
        private readonly Mailer $mailer,
        private readonly OrderStatusService $orderStatuses
    ) {
        parent::__construct($config, $auth, $cart);
    }

    public function dashboard(): void
    {
        $this->auth->requireStaff();
        $recentOrders = $this->db->fetchAll(
            'SELECT * FROM orders ORDER BY placed_at DESC LIMIT 10'
        );
        $popularItems = $this->db->fetchAll(
            'SELECT oi.item_name, SUM(oi.quantity) AS quantity, SUM(oi.line_total_cents) AS sales_cents
             FROM order_items oi
             JOIN orders o ON o.id = oi.order_id
             WHERE o.payment_status = ?
             GROUP BY oi.item_name
             ORDER BY quantity DESC LIMIT 6',
            ['paid']
        );
        $traffic = $this->db->isSqlite()
            ? $this->db->fetchAll(
                "SELECT date(created_at, 'localtime') AS day, COUNT(DISTINCT session_id) AS visitors
                 FROM visitor_events
                 WHERE created_at >= datetime('now', '-6 days')
                 GROUP BY date(created_at, 'localtime')
                 ORDER BY day"
            )
            : $this->db->fetchAll(
                "SELECT DATE(created_at) AS day, COUNT(DISTINCT session_id) AS visitors
                 FROM visitor_events
                 WHERE created_at >= DATE_SUB(NOW(), INTERVAL 6 DAY)
                 GROUP BY DATE(created_at)
                 ORDER BY day"
            );

        View::render('admin/dashboard', $this->shared([
            'title' => 'Dashboard',
            'adminPage' => 'dashboard',
            'stats' => $this->stats->dashboard(),
            'recentOrders' => $recentOrders,
            'popularItems' => $popularItems,
            'traffic' => $traffic,
        ]));
    }

    public function orders(): void
    {
        $this->auth->requireStaff();
        $status = Security::clean((string) ($_GET['status'] ?? ''));
        $query = Security::clean((string) ($_GET['q'] ?? ''));
        $where = ['1 = 1'];
        $parameters = [];
        if ($status !== '') {
            $where[] = 'o.status = ?';
            $parameters[] = $status;
        }
        if ($query !== '') {
            $where[] = '(o.order_number LIKE ? OR o.customer_name LIKE ? OR o.customer_email LIKE ?)';
            $parameters[] = '%' . $query . '%';
            $parameters[] = '%' . $query . '%';
            $parameters[] = '%' . $query . '%';
        }
        $orders = $this->db->fetchAll(
            'SELECT o.*, COALESCE(SUM(r.amount_cents), 0) AS refunded_cents
             FROM orders o
             LEFT JOIN refunds r ON r.order_id = o.id
             WHERE ' . implode(' AND ', $where) . '
             GROUP BY o.id
             ORDER BY o.placed_at DESC LIMIT 200',
            $parameters
        );
        View::render('admin/orders', $this->shared([
            'title' => 'Orders',
            'adminPage' => 'orders',
            'orders' => $orders,
            'statusFilter' => $status,
            'query' => $query,
        ]));
    }

    public function orderDetail(): void
    {
        $this->auth->requireStaff();
        $orderId = (int) ($_GET['id'] ?? 0);
        $order = $this->db->fetch('SELECT * FROM orders WHERE id = ?', [$orderId]);
        if ($order === null) {
            Http::abort(404, 'Order not found.');
        }
        View::render('admin/order-detail', $this->shared([
            'title' => 'Order ' . $order['order_number'],
            'adminPage' => 'orders',
            'order' => $order,
            'items' => $this->db->fetchAll('SELECT * FROM order_items WHERE order_id = ?', [$orderId]),
            'events' => $this->db->fetchAll(
                'SELECT oe.*, u.full_name AS staff_name
                 FROM order_events oe LEFT JOIN users u ON u.id = oe.user_id
                 WHERE oe.order_id = ? ORDER BY oe.created_at DESC, oe.id DESC',
                [$orderId]
            ),
            'refunds' => $this->db->fetchAll(
                'SELECT r.*, u.full_name AS staff_name
                 FROM refunds r LEFT JOIN users u ON u.id = r.staff_user_id
                 WHERE r.order_id = ? ORDER BY r.created_at DESC',
                [$orderId]
            ),
            'customer' => !empty($order['customer_id'])
                ? $this->db->fetch('SELECT * FROM customers WHERE id = ?', [(int) $order['customer_id']])
                : null,
        ]));
    }

    public function updateOrder(): void
    {
        Security::verifyCsrf();
        $staff = $this->auth->requireStaff();
        $orderId = (int) ($_POST['order_id'] ?? 0);
        $status = Security::clean((string) ($_POST['status'] ?? ''));
        $note = Security::clean((string) ($_POST['status_note'] ?? ''));
        try {
            $result = $this->orderStatuses->update(
                $orderId,
                $status,
                $note,
                isset($_POST['customer_visible']),
                isset($_POST['send_email']),
                (int) $staff['id']
            );
            if ($result['email_error'] !== null) {
                Http::flash('warning', 'Status updated, but the email could not be sent: ' . $result['email_error']);
            } else {
                Http::flash(
                    'success',
                    'Order status updated' . ($result['email_requested'] ? ' and the customer was emailed.' : '.')
                );
            }
        } catch (\Throwable $error) {
            Http::flash('error', $error->getMessage());
        }
        Http::redirect('/admin/order?id=' . $orderId);
    }

    public function customers(): void
    {
        $this->auth->requireStaff();
        $query = Security::clean((string) ($_GET['q'] ?? ''));
        $active = Security::clean((string) ($_GET['active'] ?? ''));
        $where = ['1 = 1'];
        $parameters = [];
        if ($query !== '') {
            $where[] = '(c.full_name LIKE ? OR c.email LIKE ? OR c.phone LIKE ?)';
            $parameters = ['%' . $query . '%', '%' . $query . '%', '%' . $query . '%'];
        }
        if ($active === '1' || $active === '0') {
            $where[] = 'c.active = ?';
            $parameters[] = (int) $active;
        }
        View::render('admin/customers', $this->shared([
            'title' => 'Customers',
            'adminPage' => 'customers',
            'customers' => $this->db->fetchAll(
                'SELECT c.*, u.email AS account_email
                 FROM customers c LEFT JOIN users u ON u.id = c.user_id
                 WHERE ' . implode(' AND ', $where) . '
                 ORDER BY c.last_order_at DESC, c.full_name',
                $parameters
            ),
            'query' => $query,
            'activeFilter' => $active,
        ]));
    }

    public function customerDetail(): void
    {
        $this->auth->requireStaff();
        $customerId = (int) ($_GET['id'] ?? 0);
        $customer = $this->db->fetch(
            'SELECT c.*, u.email AS account_email, u.active AS account_active
             FROM customers c LEFT JOIN users u ON u.id = c.user_id
             WHERE c.id = ?',
            [$customerId]
        );
        if ($customer === null) {
            Http::abort(404, 'Customer not found.');
        }
        View::render('admin/customer-detail', $this->shared([
            'title' => $customer['full_name'],
            'adminPage' => 'customers',
            'customer' => $customer,
            'orders' => $this->db->fetchAll(
                'SELECT * FROM orders WHERE customer_id = ? ORDER BY placed_at DESC',
                [$customerId]
            ),
        ]));
    }

    public function saveCustomer(): void
    {
        Security::verifyCsrf();
        $this->auth->requireStaff();
        $customerId = (int) ($_POST['customer_id'] ?? 0);
        $customer = $this->db->fetch('SELECT * FROM customers WHERE id = ?', [$customerId]);
        if ($customer === null) {
            Http::abort(404, 'Customer not found.');
        }
        $name = Security::clean((string) ($_POST['full_name'] ?? ''));
        if ($name === '') {
            Http::flash('error', 'Customer name is required.');
            Http::redirect('/admin/customer?id=' . $customerId);
        }
        $this->db->execute(
            'UPDATE customers SET full_name = ?, phone = ?, active = ?, internal_notes = ?,
                updated_at = CURRENT_TIMESTAMP WHERE id = ?',
            [
                $name,
                Security::clean((string) ($_POST['phone'] ?? '')),
                isset($_POST['active']) ? 1 : 0,
                Security::clean((string) ($_POST['internal_notes'] ?? '')),
                $customerId,
            ]
        );
        Http::flash('success', 'Customer record saved.');
        Http::redirect('/admin/customer?id=' . $customerId);
    }

    public function deleteCustomer(): void
    {
        Security::verifyCsrf();
        $this->auth->requireStaff();
        $customerId = (int) ($_POST['customer_id'] ?? 0);
        $this->db->execute('UPDATE orders SET customer_id = NULL WHERE customer_id = ?', [$customerId]);
        $this->db->execute('DELETE FROM customers WHERE id = ?', [$customerId]);
        Http::flash('success', 'Customer record deleted. Historical orders were kept.');
        Http::redirect('/admin/customers');
    }

    public function refundOrder(): void
    {
        Security::verifyCsrf();
        $staff = $this->auth->requireStaff();
        $orderId = (int) ($_POST['order_id'] ?? 0);
        $order = $this->db->fetch(
            'SELECT o.*, COALESCE(SUM(r.amount_cents), 0) AS refunded_cents
             FROM orders o LEFT JOIN refunds r ON r.order_id = o.id
             WHERE o.id = ? GROUP BY o.id',
            [$orderId]
        );
        if ($order === null) {
            Http::abort(404, 'Order not found.');
        }
        $amountCents = (int) round(((float) ($_POST['amount'] ?? 0)) * 100);
        $available = (int) $order['total_cents'] - (int) $order['refunded_cents'];
        $reason = Security::clean((string) ($_POST['reason'] ?? 'Staff refund'));
        if ($amountCents <= 0 || $amountCents > $available) {
            Http::flash('error', 'Refund amount must be greater than zero and no more than the remaining paid amount.');
            Http::redirect('/admin/order?id=' . $orderId);
        }

        try {
            $refund = $this->payments->refund((string) $order['payment_reference'], $amountCents, $reason);
            $this->db->execute(
                'INSERT INTO refunds (order_id, staff_user_id, amount_cents, reason, provider_reference)
                 VALUES (?, ?, ?, ?, ?)',
                [$orderId, (int) $staff['id'], $amountCents, $reason, $refund['reference']]
            );
            $newRefunded = (int) $order['refunded_cents'] + $amountCents;
            $paymentStatus = $newRefunded >= (int) $order['total_cents'] ? 'refunded' : 'partially_refunded';
            $this->db->execute('UPDATE orders SET payment_status = ? WHERE id = ?', [$paymentStatus, $orderId]);
            $this->db->execute(
                'INSERT INTO order_events (order_id, user_id, event_type, details) VALUES (?, ?, ?, ?)',
                [$orderId, (int) $staff['id'], 'refund_issued', sprintf('$%.2f refunded: %s', $amountCents / 100, $reason)]
            );
            Http::flash('success', 'Refund issued successfully.');
        } catch (\Throwable $error) {
            Http::flash('error', $error->getMessage());
        }
        Http::redirect('/admin/order?id=' . $orderId);
    }

    public function menu(): void
    {
        $this->auth->requireStaff();
        $categories = $this->db->fetchAll('SELECT * FROM menu_categories ORDER BY display_order, name');
        $items = $this->db->fetchAll(
            'SELECT mi.*, mc.name AS category_name
             FROM menu_items mi JOIN menu_categories mc ON mc.id = mi.category_id
             ORDER BY mc.display_order, mi.display_order, mi.name'
        );
        View::render('admin/menu', $this->shared([
            'title' => 'Menu Manager',
            'adminPage' => 'menu',
            'categories' => $categories,
            'items' => $items,
        ]));
    }

    public function saveCategory(): void
    {
        Security::verifyCsrf();
        $this->auth->requireStaff();
        $id = (int) ($_POST['id'] ?? 0);
        $name = Security::clean((string) ($_POST['name'] ?? ''));
        if ($name === '') {
            Http::flash('error', 'Category name is required.');
            Http::redirect('/admin/menu');
        }
        $values = [
            $name,
            Security::slug((string) ($_POST['slug'] ?? $name)),
            Security::clean((string) ($_POST['description'] ?? '')),
            (int) ($_POST['display_order'] ?? 0),
            isset($_POST['active']) ? 1 : 0,
        ];
        if ($id > 0) {
            $values[] = $id;
            $this->db->execute(
                'UPDATE menu_categories SET name = ?, slug = ?, description = ?, display_order = ?, active = ?,
                    updated_at = CURRENT_TIMESTAMP WHERE id = ?',
                $values
            );
        } else {
            $this->db->execute(
                'INSERT INTO menu_categories (name, slug, description, display_order, active) VALUES (?, ?, ?, ?, ?)',
                $values
            );
        }
        Http::flash('success', 'Menu category saved.');
        Http::redirect('/admin/menu');
    }

    public function saveItem(): void
    {
        Security::verifyCsrf();
        $this->auth->requireStaff();
        $id = (int) ($_POST['id'] ?? 0);
        $name = Security::clean((string) ($_POST['name'] ?? ''));
        $categoryId = (int) ($_POST['category_id'] ?? 0);
        $priceCents = (int) round(((float) ($_POST['price'] ?? 0)) * 100);
        if ($name === '' || $categoryId < 1 || $priceCents < 0) {
            Http::flash('error', 'Item name, category, and a valid price are required.');
            Http::redirect('/admin/menu');
        }
        $compareAt = trim((string) ($_POST['compare_at_price'] ?? '')) === ''
            ? null
            : (int) round(((float) $_POST['compare_at_price']) * 100);
        $tags = array_values(array_filter(array_map(
            static fn (string $tag): string => Security::clean($tag),
            explode(',', (string) ($_POST['dietary_tags'] ?? ''))
        )));
        $values = [
            $categoryId,
            $name,
            Security::slug((string) ($_POST['slug'] ?? $name)),
            Security::clean((string) ($_POST['description'] ?? '')),
            $priceCents,
            $compareAt,
            Security::clean((string) ($_POST['image_url'] ?? '')),
            json_encode($tags, JSON_UNESCAPED_SLASHES),
            isset($_POST['featured']) ? 1 : 0,
            isset($_POST['active']) ? 1 : 0,
            (int) ($_POST['display_order'] ?? 0),
        ];
        if ($id > 0) {
            $values[] = $id;
            $this->db->execute(
                'UPDATE menu_items SET category_id = ?, name = ?, slug = ?, description = ?, price_cents = ?,
                    compare_at_cents = ?, image_url = ?, dietary_tags = ?, featured = ?, active = ?,
                    display_order = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
                $values
            );
        } else {
            $this->db->execute(
                'INSERT INTO menu_items
                    (category_id, name, slug, description, price_cents, compare_at_cents, image_url,
                     dietary_tags, featured, active, display_order)
                 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
                $values
            );
        }
        Http::flash('success', 'Menu item saved.');
        Http::redirect('/admin/menu');
    }

    public function specials(): void
    {
        $this->auth->requireStaff();
        View::render('admin/specials', $this->shared([
            'title' => 'Homepage Specials',
            'adminPage' => 'specials',
            'specials' => $this->db->fetchAll(
                'SELECT s.*, mi.name AS item_name
                 FROM specials s LEFT JOIN menu_items mi ON mi.id = s.menu_item_id
                 ORDER BY s.display_order, s.id'
            ),
            'items' => $this->db->fetchAll('SELECT id, name FROM menu_items WHERE active = 1 ORDER BY name'),
        ]));
    }

    public function saveSpecial(): void
    {
        Security::verifyCsrf();
        $this->auth->requireStaff();
        $id = (int) ($_POST['id'] ?? 0);
        $title = Security::clean((string) ($_POST['title'] ?? ''));
        if ($title === '') {
            Http::flash('error', 'Special title is required.');
            Http::redirect('/admin/specials');
        }
        $price = trim((string) ($_POST['price'] ?? '')) === ''
            ? null
            : (int) round(((float) $_POST['price']) * 100);
        $values = [
            (int) ($_POST['menu_item_id'] ?? 0) ?: null,
            $title,
            Security::clean((string) ($_POST['description'] ?? '')),
            Security::clean((string) ($_POST['badge'] ?? 'Special')),
            $price,
            utc_datetime(Security::clean((string) ($_POST['starts_at'] ?? ''))),
            utc_datetime(Security::clean((string) ($_POST['ends_at'] ?? ''))),
            isset($_POST['active']) ? 1 : 0,
            (int) ($_POST['display_order'] ?? 0),
        ];
        if ($id > 0) {
            $values[] = $id;
            $this->db->execute(
                'UPDATE specials SET menu_item_id = ?, title = ?, description = ?, badge = ?, price_cents = ?,
                    starts_at = ?, ends_at = ?, active = ?, display_order = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
                $values
            );
        } else {
            $this->db->execute(
                'INSERT INTO specials
                    (menu_item_id, title, description, badge, price_cents, starts_at, ends_at, active, display_order)
                 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
                $values
            );
        }
        Http::flash('success', 'Homepage special saved.');
        Http::redirect('/admin/specials');
    }

    public function users(): void
    {
        $this->auth->requireAdmin();
        $query = Security::clean((string) ($_GET['q'] ?? ''));
        $parameters = [];
        $where = '1 = 1';
        if ($query !== '') {
            $where = '(email LIKE ? OR full_name LIKE ? OR phone LIKE ?)';
            $parameters = ['%' . $query . '%', '%' . $query . '%', '%' . $query . '%'];
        }
        View::render('admin/users', $this->shared([
            'title' => 'Users & Staff',
            'adminPage' => 'users',
            'users' => $this->db->fetchAll("SELECT * FROM users WHERE {$where} ORDER BY role, full_name", $parameters),
            'query' => $query,
        ]));
    }

    public function saveUser(): void
    {
        Security::verifyCsrf();
        $admin = $this->auth->requireAdmin();
        $id = (int) ($_POST['id'] ?? 0);
        $role = Security::clean((string) ($_POST['role'] ?? 'customer'));
        if (!in_array($role, ['customer', 'staff', 'manager', 'admin'], true)) {
            $role = 'customer';
        }
        if ($id === (int) $admin['id'] && !isset($_POST['active'])) {
            Http::flash('error', 'You cannot deactivate your own account.');
            Http::redirect('/admin/users');
        }
        if ($id > 0) {
            $this->db->execute(
                'UPDATE users SET full_name = ?, phone = ?, role = ?, active = ?, newsletter_opt_in = ?,
                    two_factor_enabled = ?,
                    updated_at = CURRENT_TIMESTAMP WHERE id = ?',
                [
                    Security::clean((string) ($_POST['full_name'] ?? '')),
                    Security::clean((string) ($_POST['phone'] ?? '')),
                    $role,
                    isset($_POST['active']) ? 1 : 0,
                    isset($_POST['newsletter_opt_in']) ? 1 : 0,
                    isset($_POST['two_factor_enabled']) ? 1 : 0,
                    $id,
                ]
            );
        } else {
            $email = strtolower(Security::clean((string) ($_POST['email'] ?? '')));
            $password = (string) ($_POST['password'] ?? '');
            if (!filter_var($email, FILTER_VALIDATE_EMAIL) || strlen($password) < 10) {
                Http::flash('error', 'New staff accounts need a valid email and a 10-character password.');
                Http::redirect('/admin/users');
            }
            $this->db->execute(
                'INSERT INTO users
                    (email, password_hash, full_name, phone, role, active, newsletter_opt_in,
                     two_factor_enabled, email_verified_at)
                 VALUES (?, ?, ?, ?, ?, 1, ?, ?, CURRENT_TIMESTAMP)',
                [
                    $email,
                    password_hash($password, PASSWORD_DEFAULT),
                    Security::clean((string) ($_POST['full_name'] ?? '')),
                    Security::clean((string) ($_POST['phone'] ?? '')),
                    $role,
                    isset($_POST['newsletter_opt_in']) ? 1 : 0,
                    isset($_POST['two_factor_enabled']) ? 1 : 0,
                ]
            );
        }
        Http::flash('success', 'User account saved.');
        Http::redirect('/admin/users');
    }

    public function settings(): void
    {
        $this->auth->requireAdmin();
        View::render('admin/settings', $this->shared([
            'title' => 'Store Settings',
            'adminPage' => 'settings',
            'taxRates' => $this->db->fetchAll('SELECT * FROM tax_rates ORDER BY jurisdiction, name'),
        ]));
    }

    public function saveSettings(): void
    {
        Security::verifyCsrf();
        $this->auth->requireAdmin();
        $textFields = [
            'app.url',
            'business.name',
            'business.phone',
            'business.email',
            'business.street',
            'business.city',
            'business.state',
            'business.postal_code',
            'business.hours',
            'business.announcement',
            'square.environment',
            'square.application_id',
            'square.location_id',
            'mailgun.domain',
            'mailgun.from_name',
            'mailgun.from_email',
            'database.mysql_host',
            'database.mysql_database',
            'database.mysql_username',
        ];
        foreach ($textFields as $key) {
            [$group, $field] = explode('.', $key, 2);
            if (isset($_POST[$group]) && is_array($_POST[$group]) && array_key_exists($field, $_POST[$group])) {
                $this->config->set($key, trim((string) $_POST[$group][$field]));
            }
        }
        $emailTemplates = isset($_POST['order_emails']) && is_array($_POST['order_emails'])
            ? $_POST['order_emails']
            : [];
        foreach (OrderStatusService::STATUSES as $status) {
            if (!isset($emailTemplates[$status]) || !is_array($emailTemplates[$status])) {
                continue;
            }
            $this->config->set(
                'order_emails.' . $status . '.subject',
                trim((string) ($emailTemplates[$status]['subject'] ?? ''))
            );
            $this->config->set(
                'order_emails.' . $status . '.body',
                trim((string) ($emailTemplates[$status]['body'] ?? ''))
            );
        }
        foreach (['square.access_token', 'mailgun.api_key', 'database.mysql_password'] as $secretKey) {
            [$group, $field] = explode('.', $secretKey, 2);
            $secret = isset($_POST[$group]) && is_array($_POST[$group])
                ? trim((string) ($_POST[$group][$field] ?? ''))
                : '';
            if ($secret !== '') {
                $this->config->set($secretKey, $secret);
            }
        }
        $ordering = isset($_POST['ordering']) && is_array($_POST['ordering']) ? $_POST['ordering'] : [];
        $square = isset($_POST['square']) && is_array($_POST['square']) ? $_POST['square'] : [];
        $mailgun = isset($_POST['mailgun']) && is_array($_POST['mailgun']) ? $_POST['mailgun'] : [];
        $database = isset($_POST['database']) && is_array($_POST['database']) ? $_POST['database'] : [];
        $this->config->set('ordering.accepting_orders', isset($ordering['accepting_orders']));
        $this->config->set('ordering.pickup_eta_minutes', max(5, (int) ($ordering['pickup_eta_minutes'] ?? 25)));
        $this->config->set('square.enabled', isset($square['enabled']));
        $this->config->set('mailgun.enabled', isset($mailgun['enabled']));
        $this->config->set('database.mysql_port', (int) ($database['mysql_port'] ?? 3306));
        $this->config->save();
        Http::flash('success', 'Store settings saved.');
        Http::redirect('/admin/settings');
    }

    public function saveTax(): void
    {
        Security::verifyCsrf();
        $this->auth->requireAdmin();
        $id = (int) ($_POST['id'] ?? 0);
        $values = [
            Security::clean((string) ($_POST['name'] ?? 'Tax')),
            Security::clean((string) ($_POST['jurisdiction'] ?? 'state')),
            (int) round(((float) ($_POST['rate'] ?? 0)) * 100),
            isset($_POST['active']) ? 1 : 0,
        ];
        if ($id > 0) {
            $values[] = $id;
            $this->db->execute(
                'UPDATE tax_rates SET name = ?, jurisdiction = ?, rate_basis_points = ?, active = ?,
                    updated_at = CURRENT_TIMESTAMP WHERE id = ?',
                $values
            );
        } else {
            $this->db->execute(
                'INSERT INTO tax_rates (name, jurisdiction, rate_basis_points, active) VALUES (?, ?, ?, ?)',
                $values
            );
        }
        Http::flash('success', 'Tax rate saved.');
        Http::redirect('/admin/settings');
    }

    public function newsletters(): void
    {
        $this->auth->requireStaff();
        View::render('admin/newsletters', $this->shared([
            'title' => 'Newsletters',
            'adminPage' => 'newsletters',
            'newsletters' => $this->db->fetchAll(
                'SELECT n.*, u.full_name AS author
                 FROM newsletters n LEFT JOIN users u ON u.id = n.created_by
                 ORDER BY n.created_at DESC'
            ),
            'recipientCount' => (int) $this->db->fetch(
                'SELECT COUNT(*) AS count FROM users WHERE newsletter_opt_in = 1 AND active = 1'
            )['count'],
        ]));
    }

    public function sendNewsletter(): void
    {
        Security::verifyCsrf();
        $staff = $this->auth->requireStaff();
        $subject = Security::clean((string) ($_POST['subject'] ?? ''));
        $content = trim((string) ($_POST['content'] ?? ''));
        if ($subject === '' || $content === '') {
            Http::flash('error', 'Newsletter subject and message are required.');
            Http::redirect('/admin/newsletters');
        }
        $recipients = $this->db->fetchAll(
            'SELECT id, email, full_name FROM users WHERE newsletter_opt_in = 1 AND active = 1 ORDER BY id'
        );
        $sent = 0;
        foreach ($recipients as $recipient) {
            $signature = hash_hmac('sha256', (string) $recipient['id'], $this->newsletterKey());
            $unsubscribe = rtrim((string) $this->config->get('app.url'), '/')
                . '/unsubscribe?user=' . $recipient['id'] . '&signature=' . $signature;
            $html = '<p>Hi ' . htmlspecialchars((string) $recipient['full_name'], ENT_QUOTES, 'UTF-8') . ',</p>'
                . '<div>' . nl2br(htmlspecialchars($content, ENT_QUOTES, 'UTF-8')) . '</div>'
                . '<hr><p style="font-size:12px">You received this because you joined the Fat Bottom Grille news list. '
                . '<a href="' . htmlspecialchars($unsubscribe, ENT_QUOTES, 'UTF-8') . '">Unsubscribe</a></p>';
            try {
                $this->mailer->send((string) $recipient['email'], $subject, $html);
                $sent++;
            } catch (\Throwable) {
                continue;
            }
        }
        $this->db->execute(
            'INSERT INTO newsletters (subject, content_html, status, recipient_count, created_by, sent_at)
             VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)',
            [$subject, nl2br(htmlspecialchars($content, ENT_QUOTES, 'UTF-8')), 'sent', $sent, (int) $staff['id']]
        );
        Http::flash('success', "Newsletter sent to {$sent} subscribed users.");
        Http::redirect('/admin/newsletters');
    }

    public function exportMenuPdf(): void
    {
        $this->auth->requireStaff();
        $categories = $this->db->fetchAll('SELECT * FROM menu_categories WHERE active = 1 ORDER BY display_order, name');
        foreach ($categories as &$category) {
            $category['items'] = $this->db->fetchAll(
                'SELECT * FROM menu_items WHERE category_id = ? AND active = 1 ORDER BY display_order, name',
                [(int) $category['id']]
            );
        }
        unset($category);
        (new MenuPdfExporter())->output((string) $this->config->get('business.name'), $categories);
    }

    public function exportOrdersCsv(): never
    {
        $this->auth->requireStaff();
        $orders = $this->db->fetchAll('SELECT * FROM orders ORDER BY placed_at DESC');
        header('Content-Type: text/csv');
        header('Content-Disposition: attachment; filename="fat-bottom-orders-' . date('Y-m-d') . '.csv"');
        $output = fopen('php://output', 'wb');
        fputcsv($output, [
            'Order', 'Placed', 'Customer', 'Email', 'Phone', 'Status', 'Payment',
            'Subtotal', 'Tax', 'Total', 'Instructions',
        ]);
        foreach ($orders as $order) {
            fputcsv($output, [
                $order['order_number'],
                $order['placed_at'],
                $order['customer_name'],
                $order['customer_email'],
                $order['customer_phone'],
                $order['status'],
                $order['payment_status'],
                number_format((int) $order['subtotal_cents'] / 100, 2, '.', ''),
                number_format((int) $order['tax_cents'] / 100, 2, '.', ''),
                number_format((int) $order['total_cents'] / 100, 2, '.', ''),
                $order['special_instructions'],
            ]);
        }
        fclose($output);
        exit;
    }

    public function migrateMySql(): void
    {
        Security::verifyCsrf();
        $this->auth->requireAdmin();
        try {
            $result = (new MySqlMigrator($this->config))->migrate();
            if (isset($_POST['switch_driver'])) {
                $this->config->set('database.driver', 'mysql');
                $this->config->save();
            }
            Http::flash(
                'success',
                sprintf('Copied %d tables and %d rows to MySQL%s.', $result['tables'], $result['rows'], isset($_POST['switch_driver']) ? ' and enabled MySQL mode' : '')
            );
        } catch (\Throwable $error) {
            Http::flash('error', $error->getMessage());
        }
        Http::redirect('/admin/settings');
    }

    private function newsletterKey(): string
    {
        return hash('sha256', (string) $this->config->get('app.key') . '|newsletter');
    }
}
