<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Admin\Modules;
use App\Core\App;
use App\Core\Audit;
use App\Core\Auth;
use App\Core\Database as DB;
use App\Core\Request;
use App\Core\Response;
use App\Core\Schema;
use App\Core\View;

final class AdminController
{
    // --- Auth ---------------------------------------------------------------

    public function loginForm(): void
    {
        if (Auth::user() !== null) {
            Response::redirect('/admin');
        }
        View::bare('admin/login', ['csrf' => Auth::csrfToken(), 'error' => null]);
    }

    public function login(Request $request): void
    {
        Auth::verifyCsrf($request);
        if (Auth::attempt($request->str('email'), $request->str('password'))) {
            Response::redirect('/admin');
        }
        View::bare('admin/login', ['csrf' => Auth::csrfToken(), 'error' => 'Invalid email or password.']);
    }

    public function logout(Request $request): void
    {
        Auth::verifyCsrf($request);
        Auth::logout();
        Response::redirect('/admin/login');
    }

    // --- Dashboard ----------------------------------------------------------

    public function dashboard(): void
    {
        $user = Auth::requireAdmin();
        $counts = [];
        foreach (['treatments', 'packages', 'gallery_images', 'testimonials', 'team_members', 'blogs', 'faqs', 'contacts'] as $table) {
            $counts[$table] = (int) (DB::selectOne("SELECT COUNT(*) AS c FROM $table")['c'] ?? 0);
        }
        View::render('admin/dashboard', [
            'user'        => $user,
            'counts'      => $counts,
            'contacts'    => DB::select('SELECT * FROM contacts ORDER BY created_at DESC LIMIT 5'),
            'posts'       => DB::select('SELECT id, title, status, published_at FROM blogs ORDER BY published_at DESC LIMIT 5'),
            'reviews'     => DB::select('SELECT id, client_name, rating, date FROM testimonials ORDER BY date DESC LIMIT 5'),
            'activity'    => DB::select('SELECT a.*, u.name AS user_name FROM audit_logs a LEFT JOIN users u ON u.id = a.user_id ORDER BY a.created_at DESC LIMIT 8'),
            'active'      => 'dashboard',
        ]);
    }

    // --- Generic CRUD ---------------------------------------------------------

    /** @param array<string, string> $params */
    public function index(Request $request, array $params): void
    {
        Auth::requireAdmin();
        $module = $this->module($params['module']);
        $rows = DB::select(sprintf(
            'SELECT * FROM %s ORDER BY %s',
            $module['table'],
            $module['order'] ?? 'id DESC',
        ));
        View::render('admin/list', [
            'moduleKey' => $params['module'],
            'module'    => $module,
            'rows'      => $rows,
            'csrf'      => Auth::csrfToken(),
            'active'    => $params['module'],
        ]);
    }

    /** @param array<string, string> $params */
    public function create(Request $request, array $params): void
    {
        Auth::requireAdmin();
        $module = $this->module($params['module']);
        if (($module['canCreate'] ?? true) === false) {
            Response::redirect('/admin/' . $params['module']);
        }

        // Templatable modules (e.g. Pages) can start a new record from an
        // existing one: the picker on the form and the list's Duplicate button
        // both land here with ?from=<id>. Nothing is saved until the user
        // reviews and submits, which keeps the unique slug collision-free.
        $prefill = null;
        $templates = [];
        if (!empty($module['template'])) {
            // Column shown in the picker differs per table (pages: title, rooms: name).
            $label = $module['templateLabel'] ?? 'name';
            $templates = DB::select("SELECT id, $label AS label FROM {$module['table']} ORDER BY $label");
            $fromId = (int) $request->query('from');
            if ($fromId > 0) {
                $source = DB::selectOne("SELECT * FROM {$module['table']} WHERE id = ?", [$fromId]);
                if ($source !== null) {
                    unset($source['id']);
                    if (isset($source['slug'])) {
                        $source['slug'] = $this->uniqueSlug($module['table'], (string) $source['slug'] . '-copy');
                    }
                    $prefill = $source;
                }
            }
        }

        View::render('admin/form', [
            'moduleKey' => $params['module'],
            'module'    => $module,
            'row'       => null,
            'prefill'   => $prefill,
            'templates' => $templates,
            'csrf'      => Auth::csrfToken(),
            'active'    => $params['module'],
            'fks'       => $this->fkOptions($module),
        ]);
    }

    /** @param array<string, string> $params */
    public function edit(Request $request, array $params): void
    {
        Auth::requireAdmin();
        $module = $this->module($params['module']);
        $row = DB::selectOne("SELECT * FROM {$module['table']} WHERE id = ?", [(int) $params['id']]);
        if ($row === null) {
            Response::redirect('/admin/' . $params['module']);
        }
        View::render('admin/form', [
            'moduleKey' => $params['module'],
            'module'    => $module,
            'row'       => $row,
            'csrf'      => Auth::csrfToken(),
            'active'    => $params['module'],
            'fks'       => $this->fkOptions($module),
        ]);
    }

    /** @param array<string, string> $params */
    public function save(Request $request, array $params): void
    {
        Auth::requireAdmin();
        Auth::verifyCsrf($request);
        $module = $this->module($params['module']);
        $id = isset($params['id']) ? (int) $params['id'] : null;

        $data = [];
        foreach ($module['fields'] as $name => $field) {
            if (!empty($field['readonly'])) {
                continue;
            }
            $raw = $request->input($name);
            $value = match ($field['type']) {
                'checkbox' => $raw !== null && $raw !== '' && $raw !== '0' ? 1 : 0,
                'number'   => $raw === '' || $raw === null ? null : (float) $raw,
                'fk'       => $raw === '' || $raw === null ? null : (int) $raw,
                'json'     => $this->validJson((string) (is_scalar($raw) ? $raw : ''), $field),
                'password' => null, // handled below
                default    => is_scalar($raw) ? trim((string) $raw) : '',
            };
            if ($field['type'] === 'password') {
                $pw = $request->str($name);
                if ($pw !== '') {
                    $data[$name] = password_hash($pw, PASSWORD_BCRYPT);
                }
                continue;
            }
            if ($value === null && $field['type'] === 'json') {
                $this->flash('error', "Field “{$name}” contains invalid JSON.");
                Response::redirect('/admin/' . $params['module'] . ($id ? "/$id/edit" : '/create'));
            }
            $data[$name] = $value;
        }

        if ($id === null) {
            $newId = DB::insert($module['table'], $data);
            Audit::log('create', $module['table'], $newId);
        } else {
            DB::update($module['table'], $id, $data);
            Audit::log('update', $module['table'], $id);
        }

        $this->flash('ok', 'Saved.');
        Response::redirect('/admin/' . $params['module']);
    }

    /** @param array<string, string> $params */
    public function destroy(Request $request, array $params): void
    {
        Auth::requireAdmin();
        Auth::verifyCsrf($request);
        $module = $this->module($params['module']);
        if (($module['canDelete'] ?? true) !== false) {
            DB::delete($module['table'], (int) $params['id']);
            Audit::log('delete', $module['table'], (int) $params['id']);
            $this->flash('ok', 'Deleted.');
        }
        Response::redirect('/admin/' . $params['module']);
    }

    // --- Settings -------------------------------------------------------------

    public function settings(Request $request): void
    {
        Auth::requireAdmin();
        // Settings introduced after the original seed — self-heals databases
        // deployed before they existed.
        foreach ([
            'logo'              => '',
            'payment_methods'   => '["Cash","Visa","Mastercard","Bank transfer"]',
            'whatsapp_greeting' => "Hello Touch Therapies! I'd like to book a treatment.",
            'ai_enabled'        => '0',
            'ai_name'           => 'Isla',
            'ai_greeting'       => "Hi, I'm Isla! Ask me anything about our treatments, packages, prices, or opening hours.",
            'ai_api_key'        => '',
            'ai_model'          => 'claude-opus-4-8',
            'payments_enabled'  => '0',
            'deposit_usd'       => '25',
            'paypal_enabled'    => '0',
            'paypal_client_id'  => '',
            'paypal_secret'     => '',
            'paypal_sandbox'    => '1',
            'wipay_enabled'     => '0',
            'wipay_account_number' => '',
            'wipay_api_key'     => '',
            'wipay_country_code'   => 'TT',
            'wipay_currency'    => 'USD',
            'wipay_exchange_rate'  => '1',
            'wipay_fee_structure'  => 'merchant_absorb',
            'wipay_sandbox'     => '1',
            'slot_minutes'      => '60',
        ] as $name => $default) {
            if (DB::selectOne('SELECT id FROM settings WHERE name = ?', [$name]) === null) {
                DB::insert('settings', ['name' => $name, 'value' => $default]);
            }
        }
        $rows = DB::select('SELECT * FROM settings ORDER BY id');
        View::render('admin/settings', [
            'rows'   => $rows,
            'csrf'   => Auth::csrfToken(),
            'active' => 'settings',
        ]);
    }

    public function saveSettings(Request $request): void
    {
        Auth::requireAdmin();
        Auth::verifyCsrf($request);
        foreach ($request->body as $name => $value) {
            if ($name === '_csrf' || !is_scalar($value)) {
                continue;
            }
            DB::execute('UPDATE settings SET value = ? WHERE name = ?', [trim((string) $value), $name]);
        }
        Audit::log('update', 'settings');
        $this->flash('ok', 'Settings saved.');
        Response::redirect('/admin/settings');
    }

    // --- Contacts inbox ---------------------------------------------------------

    public function contacts(Request $request): void
    {
        Auth::requireAdmin();
        View::render('admin/contacts', [
            'rows'   => DB::select('SELECT * FROM contacts ORDER BY created_at DESC LIMIT 200'),
            'csrf'   => Auth::csrfToken(),
            'active' => 'contacts',
        ]);
    }

    /** @param array<string, string> $params */
    public function contactHandled(Request $request, array $params): void
    {
        Auth::requireAdmin();
        Auth::verifyCsrf($request);
        DB::execute('UPDATE contacts SET handled = 1 - handled WHERE id = ?', [(int) $params['id']]);
        Response::redirect('/admin/contacts');
    }

    // --- Media upload ------------------------------------------------------------

    public function upload(Request $request): void
    {
        Auth::requireAdmin();
        Auth::verifyCsrf($request);
        $cfg = App::config('security');

        $file = $_FILES['file'] ?? null;
        if ($file === null || $file['error'] !== UPLOAD_ERR_OK) {
            Response::error('Upload failed.');
        }
        if ($file['size'] > $cfg['upload_max_kb'] * 1024) {
            Response::error('File too large.');
        }

        $mime = mime_content_type($file['tmp_name']) ?: '';
        $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        $allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp', 'image/gif' => 'gif'];
        if (!isset($allowed[$mime]) || !in_array($ext, $cfg['allowed_images'], true)) {
            Response::error('Only JPG, PNG, WebP or GIF images are allowed.');
        }

        $dir = $cfg['upload_dir'];
        if (!is_dir($dir)) {
            mkdir($dir, 0775, true);
        }

        $name = date('Ymd') . '-' . bin2hex(random_bytes(8));
        $target = "$dir/$name." . $allowed[$mime];
        move_uploaded_file($file['tmp_name'], $target);

        // Convert to WebP when possible (smaller files, per spec).
        $publicPath = '/uploads/' . basename($target);
        if (function_exists('imagewebp') && in_array($mime, ['image/jpeg', 'image/png'], true)) {
            $img = $mime === 'image/jpeg' ? @imagecreatefromjpeg($target) : @imagecreatefrompng($target);
            if ($img !== false) {
                $webp = "$dir/$name.webp";
                if (imagewebp($img, $webp, 82)) {
                    unlink($target);
                    $publicPath = '/uploads/' . basename($webp);
                }
                imagedestroy($img);
            }
        }

        DB::insert('media', [
            'filename'   => basename($publicPath),
            'path'       => $publicPath,
            'mime'       => $mime,
            'size_kb'    => (int) round($file['size'] / 1024),
            'created_at' => date('Y-m-d H:i:s'),
        ]);
        Audit::log('upload', 'media');

        Response::json(['url' => $publicPath], 201);
    }

    // --- Helpers -------------------------------------------------------------------

    /** @return array<string, mixed> */
    private function module(string $key): array
    {
        Schema::heal(); // ensure room/availability tables exist on pre-existing installs
        $module = Modules::get($key);
        if ($module === null) {
            http_response_code(404);
            echo 'Unknown module';
            exit;
        }
        return $module;
    }

    /** Return $base, or $base-2, $base-3… so it doesn't collide with an existing slug. */
    private function uniqueSlug(string $table, string $base): string
    {
        $slug = $base;
        $n = 1;
        while (DB::selectOne("SELECT id FROM $table WHERE slug = ?", [$slug]) !== null) {
            $slug = $base . '-' . (++$n);
        }
        return $slug;
    }

    /** @return array<string, array<int, array<string, mixed>>> */
    private function fkOptions(array $module): array
    {
        $out = [];
        foreach ($module['fields'] as $name => $field) {
            if ($field['type'] === 'fk') {
                $out[$name] = DB::select("SELECT id, name FROM {$field['table']} ORDER BY name");
            }
        }
        return $out;
    }

    /** @param array<string, mixed> $field */
    private function validJson(string $raw, array $field = []): ?string
    {
        $raw = trim($raw);
        if ($raw === '') {
            return '';
        }
        json_decode($raw);
        if (json_last_error() === JSON_ERROR_NONE) {
            return $raw;
        }
        // List fields accept plain text too: one item per line (or commas).
        if (($field['shape'] ?? null) === 'list' && !str_starts_with($raw, '[') && !str_starts_with($raw, '{')) {
            $parts = preg_split('/\r?\n|,/', $raw) ?: [];
            $items = array_values(array_filter(array_map('trim', $parts), fn (string $s): bool => $s !== ''));
            return json_encode($items, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?: null;
        }
        return null;
    }

    private function flash(string $type, string $message): void
    {
        Auth::start();
        $_SESSION['flash'] = ['type' => $type, 'message' => $message];
    }
}
