<?php
namespace App\Controllers\Admin;
use App\Core\{View, Database as DB, ImageProcessor};
use App\Models\{Event, Photo, Order, OrderItem};

class AuthController
{
    public function showLogin(): void
    {
        if (!empty($_SESSION['admin'])) redirect(base_url('admin'));
        View::render('admin/login', [], 'admin');
    }
    public function login(): void
    {
        $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';

        // Rate limiting — bloquear após 10 tentativas em 15 min
        if (!\App\Core\Security::checkLoginRate($ip)) {
            flash('error', 'Demasiadas tentativas. Aguarda 15 minutos.');
            redirect(base_url('admin/login'));
        }

        $user = \App\Core\Security::clean($_POST['user'] ?? '');
        $pass = $_POST['pass'] ?? '';

        if ($user === config('app.admin.user') &&
            \password_verify($pass, config('app.admin.hash'))) {
            \App\Core\Security::resetLoginRate($ip);
            // Regenerar sessão após login (previne session fixation)
            \session_regenerate_id(true);
            $_SESSION['admin']      = true;
            $_SESSION['admin_user'] = $user;
            redirect(base_url('admin'));
        }

        flash('error', 'Utilizador ou password incorretos.');
        redirect(base_url('admin/login'));
    }
    public function logout(): void
    {
        $_SESSION = []; \session_destroy();
        redirect(base_url('admin/login'));
    }
}

class DashboardController
{
    public function index(): void
    {
        $stats      = Order::stats();
        $recent     = Order::recent(10);
        $eventCount = (int)DB::val("SELECT COUNT(*) FROM events");
        $photoCount = (int)DB::val("SELECT COUNT(*) FROM photos");
        View::render('admin/dashboard', compact('stats','recent','eventCount','photoCount'), 'admin');
    }
}

class EventController
{
    public function index(): void
    {
        $events = Event::all();
        View::render('admin/events/index', compact('events'), 'admin');
    }
    public function store(): void
    {
        if (!csrf_ok()) abort(403);
        $name = \trim($_POST['name']??'');
        if (!$name) { flash('error','Nome obrigatório.'); redirect(base_url('admin/eventos')); }
        $s = slug($name); $base=$s; $i=1;
        while (DB::one("SELECT id FROM events WHERE slug=?",[$s])) $s=$base.'-'.$i++;
        Event::create(['name'=>$name,'slug'=>$s,'description'=>$_POST['description']??null,
            'date_event'=>($_POST['date_event']??'')?:null,'order_prefix'=>\strtoupper(\trim($_POST['order_prefix']??'')),
            'password'=>\trim($_POST['password']??'')]);
        flash('success','Evento criado.');
        redirect(base_url('admin/eventos'));
    }
    public function update(int $id): void
    {
        if (!csrf_ok()) abort(403);
        Event::update($id,['name'=>$_POST['name'],'description'=>$_POST['description']??null,
            'date_event'=>($_POST['date_event']??'')?:null,'status'=>$_POST['status']??'hidden',
            'order_prefix'=>\strtoupper(\trim($_POST['order_prefix']??'')),
            'password'=>\trim($_POST['password']??'')]);
        flash('success','Evento atualizado.');
        redirect(base_url('admin/eventos'));
    }
    public function toggle(int $id): void { Event::toggle($id); redirect(base_url('admin/eventos')); }

    public function report(int $id): void
    {
        $event = Event::find($id);
        if (!$event) abort(404);
        $rawOrders = Order::all(['event_id' => $id]);
        $orders = [];
        foreach ($rawOrders as $o) {
            $items = Order::items($o['id']);
            $o['items']       = $items;
            $o['total_fotos'] = \array_sum(\array_column($items,'quantity'));
            $orders[] = $o;
        }
        \extract(\compact('event','orders'));
        require ROOT . '/resources/views/admin/events/report.php';
        exit;
    }

    public function soldPhotos(int $id): void
    {
        $event = Event::find($id);
        if (!$event) abort(404);

        // Agregar fotos vendidas de encomendas validadas (paid + sent)
        // Usar o filename actual da foto (caso tenha sido renomeada)
        $photos = DB::all("
            SELECT
                COALESCE(p.filename, oi.filename) AS filename,
                SUM(oi.quantity)                  AS total_qty,
                COUNT(DISTINCT oi.order_id)       AS num_orders
            FROM order_items oi
            INNER JOIN orders o ON o.id = oi.order_id
            LEFT  JOIN photos  p ON p.id = oi.photo_id
            WHERE o.event_id = ?
              AND o.status IN ('paid','sent')
            GROUP BY COALESCE(p.filename, oi.filename)
            ORDER BY total_qty DESC, COALESCE(p.filename, oi.filename) ASC
        ", [$id]);

        $totalFotos  = array_sum(array_column($photos, 'total_qty'));
        $totalLinhas = count($photos);

        \extract(\compact('event','photos','totalFotos','totalLinhas'));
        require ROOT . '/resources/views/admin/events/sold-photos.php';
        exit;
    }

    public function resetSales(int $id): void
    {
        if (!csrf_ok()) abort(403);
        $event = Event::find($id);
        if (!$event) abort(404);
        $orders = DB::all("SELECT id FROM orders WHERE event_id=?", [$id]);
        foreach ($orders as $o) {
            DB::exec("DELETE FROM order_items WHERE order_id=?", [$o['id']]);
        }
        DB::exec("DELETE FROM orders WHERE event_id=?", [$id]);
        flash('success', 'Vendas do evento "'.$event['name'].'" foram eliminadas.');
        redirect(base_url('admin/eventos/'.$id.'/fotos'));
    }

    public function delete(int $id): void
    {
        if (!csrf_ok()) abort(403);
        $photos = DB::all("SELECT * FROM photos WHERE event_id=?", [$id]);

        // 1. Apagar order_items que referenciam fotos deste evento
        foreach ($photos as $p) {
            DB::exec("DELETE FROM order_items WHERE photo_id=?", [$p['id']]);
        }
        // 2. Apagar todas as encomendas do evento
        DB::exec("DELETE FROM orders WHERE event_id=?", [$id]);

        // 3. Apagar fotos da base de dados
        DB::exec("DELETE FROM photos WHERE event_id=?", [$id]);

        // 4. Apagar ficheiros físicos e pastas
        foreach ($photos as $p) {
            @\unlink(ROOT.'/imgs/thumbnails/'.$id.'/'.$p['filename']);
            @\unlink(ROOT.'/imgs/previews/'.$id.'/'.$p['filename']);
            @\unlink(ROOT.'/storage/originals/'.$id.'/'.$p['filename']);
        }
        // Apagar pastas do evento (ficavam vazias)
        @\rmdir(ROOT.'/imgs/thumbnails/'.$id);
        @\rmdir(ROOT.'/imgs/previews/'.$id);
        @\rmdir(ROOT.'/storage/originals/'.$id);

        // 5. Apagar o evento
        Event::delete($id);
        flash('success', 'Evento e todos os dados associados foram eliminados.');
        redirect(base_url('admin/eventos'));
    }
}

class PhotoController
{
    public function manage(int $id): void
    {
        $event  = Event::find($id);
        if (!$event) abort(404);
        $photos = Photo::forEvent($id, 1, 500);
        View::render('admin/events/photos', compact('event','photos'), 'admin');
    }

    public function upload(int $id): void
    {
        while (\ob_get_level()) \ob_end_clean();
        \header('Content-Type: application/json');

        $event = Event::find($id);
        if (!$event) { echo \json_encode(['ok'=>false,'error'=>'Evento não encontrado']); exit; }
        if (empty($_FILES['photo'])) { echo \json_encode(['ok'=>false,'error'=>'Nenhum ficheiro recebido']); exit; }

        $f = $_FILES['photo'];
        if ($f['error'] !== \UPLOAD_ERR_OK) {
            $msgs = [\UPLOAD_ERR_INI_SIZE=>'Ficheiro demasiado grande (upload_max_filesize)',
                     \UPLOAD_ERR_PARTIAL=>'Upload incompleto',\UPLOAD_ERR_NO_TMP_DIR=>'Pasta temp em falta',
                     \UPLOAD_ERR_CANT_WRITE=>'Sem permissão de escrita'];
            echo \json_encode(['ok'=>false,'error'=>$msgs[$f['error']]??"Erro {$f['error']}"]); exit;
        }

        $info = @\getimagesize($f['tmp_name']);
        if (!$info || !\in_array($info[2],[\IMAGETYPE_JPEG,\IMAGETYPE_PNG,\IMAGETYPE_WEBP])) {
            echo \json_encode(['ok'=>false,'error'=>'Formato inválido (usa JPEG, PNG ou WebP)']); exit;
        }
        // Verificar magic bytes reais (segurança extra)
        if (!\App\Core\Security::isValidImage($f['tmp_name'])) {
            echo \json_encode(['ok'=>false,'error'=>'Ficheiro rejeitado — conteúdo inválido']); exit;
        }
        if (!\function_exists('imagecreatefromjpeg')) {
            echo \json_encode(['ok'=>false,'error'=>'Extensão GD não instalada — ativa no cPanel']); exit;
        }

        $result = ImageProcessor::process($f['tmp_name'], $id, $f['name']);
        if (\is_string($result)) { echo \json_encode(['ok'=>false,'error'=>$result]); exit; }

        $photoId = Photo::create(['event_id'=>$id,...$result]);
        if (!$event['cover_photo']) Event::setCover($id, $result['thumb_path']);

        echo \json_encode([
            'ok'    => true,
            'id'    => $photoId,
            'thumb' => base_url('imgs/thumbnails/'.$id.'/'.$result['filename']),
        ]);
        exit;
    }

    public function setCover(int $eid, int $pid): void
    {
        $p = Photo::find($pid);
        if ($p && (int)$p['event_id'] === $eid) Event::setCover($eid, $p['thumb_path']);
        redirect(base_url("admin/eventos/$eid/fotos"));
    }

    public function delete(int $eid, int $pid): void
    {
        // Remover order_items antes de apagar a foto (chave estrangeira)
        DB::exec("DELETE FROM order_items WHERE photo_id=?", [$pid]);
        $p = Photo::delete($pid);
        if ($p) {
            @\unlink(ROOT.'/imgs/thumbnails/'.$eid.'/'.$p['filename']);
            @\unlink(ROOT.'/imgs/previews/'.$eid.'/'.$p['filename']);
            @\unlink(ROOT.'/storage/originals/'.$eid.'/'.$p['filename']);
        }
        \header('Content-Type: application/json');
        echo \json_encode(['ok'=>true]);
        exit;
    }
}

class OrderController
{
    public function index(): void
    {
        $filters = ['status'=>$_GET['status']??'','q'=>$_GET['q']??'','event_id'=>(int)($_GET['event_id']??0)];
        $orders  = Order::all($filters);
        $events  = Event::all();
        View::render('admin/orders/index', compact('orders','filters','events'), 'admin');
    }

    public function show(int $id): void
    {
        $order = Order::find($id);
        if (!$order) abort(404);
        $items       = Order::items($id);
        $events      = Event::all();
        // Fotos do evento para poder adicionar à encomenda
        $eventPhotos = $order['event_id']
            ? Photo::forEvent((int)$order['event_id'], 1, 9999)
            : [];
        View::render('admin/orders/show', compact('order','items','events','eventPhotos'), 'admin');
    }

    public function update(int $id): void
    {
        if (!csrf_ok()) abort(403);
        $order = Order::find($id);
        if (!$order) abort(404);

        // Guardar dados do cliente e estado
        $ship = \in_array($_POST['shipping_method'] ?? '', ['noivos','ctt','internacional'])
            ? $_POST['shipping_method']
            : $order['shipping_method'];

        Order::update($id, [
            'customer_name'  => \trim($_POST['customer_name']),
            'phone'          => \trim($_POST['phone'] ?? ''),
            'email'          => \trim($_POST['email'] ?? ''),
            'nif'            => \trim($_POST['nif'] ?? ''),
            'shipping_method'=> $ship,
            'address'        => \trim($_POST['address'] ?? ''),
            'payment_method' => \in_array($_POST['payment_method'] ?? '', ['dinheiro','mbway'])
                                 ? $_POST['payment_method'] : 'dinheiro',
            'status'         => $_POST['status'] ?? $order['status'],
            'notes'          => \trim($_POST['notes'] ?? ''),
            'total'          => $order['total'], // recalculado abaixo
        ]);

        // Recalcular totais com promoção e novo método de envio
        self::recalcOrder($id);

        flash('success', 'Encomenda atualizada.');
        redirect(base_url("admin/encomendas/$id"));
    }

    public function addPhoto(int $id): void
    {
        if (!csrf_ok()) abort(403);
        $order   = Order::find($id);
        if (!$order) abort(404);
        $photoId = (int)($_POST['photo_id'] ?? 0);
        $qty     = \max(1, (int)($_POST['qty'] ?? 1));
        $photo   = Photo::find($photoId);
        if (!$photo) { flash('error','Fotografia não encontrada.'); redirect(base_url("admin/encomendas/$id")); }

        // Verificar se já existe na encomenda
        $existing = DB::one("SELECT * FROM order_items WHERE order_id=? AND photo_id=?", [$id, $photoId]);
        $up = (float)config('app.preco_foto');

        if ($existing) {
            $newQty = $existing['quantity'] + $qty;
            DB::exec("UPDATE order_items SET quantity=?, subtotal=? WHERE id=?",
                [$newQty, $newQty * $up, $existing['id']]);
        } else {
            OrderItem::create([
                'order_id'   => $id,
                'photo_id'   => $photoId,
                'filename'   => $photo['filename'],
                'quantity'   => $qty,
                'unit_price' => $up,
                'subtotal'   => $qty * $up,
            ]);
        }

        self::recalcOrder($id);
        flash('success','Fotografia adicionada à encomenda.');
        redirect(base_url("admin/encomendas/$id"));
    }

    public function removePhoto(int $id): void
    {
        if (!csrf_ok()) abort(403);
        $itemId = (int)($_POST['item_id'] ?? 0);
        DB::exec("DELETE FROM order_items WHERE id=? AND order_id=?", [$itemId, $id]);
        self::recalcOrder($id);
        flash('success','Fotografia removida da encomenda.');
        redirect(base_url("admin/encomendas/$id"));
    }

    private static function recalcOrder(int $orderId): void
    {
        $order = Order::find($orderId);
        if (!$order) return;

        $items    = Order::items($orderId);
        $preco    = (float)config('app.preco_foto');
        $pc       = (int)config('app.promo_compra');
        $po       = (int)config('app.promo_oferta');
        $ciclo    = $pc + $po; // 11

        // Total de fotografias na encomenda
        $totalFotos = (int)\array_sum(\array_column($items, 'quantity'));

        // Calcular oferta (mesma lógica do carrinho)
        $free = ($ciclo > 0 && $totalFotos > 0) ? (int)\floor($totalFotos / $ciclo) * $po : 0;
        $pay  = $totalFotos - $free;

        // Subtotal (só fotos pagas)
        $subtotal = \round($pay * $preco, 2);

        // Custo de envio pelo método actual
        $shipCost = match($order['shipping_method']) {
            'ctt'           => (float)config('app.preco_ctt'),
            'internacional' => (float)config('app.preco_internacional'),
            default         => 0.0,
        };

        DB::exec(
            "UPDATE orders SET photos_free=?, subtotal=?, shipping_cost=?, total=? WHERE id=?",
            [$free, $subtotal, $shipCost, $subtotal + $shipCost, $orderId]
        );
    }
}

class SettingsController
{
    public function index(): void
    {
        $settings = \App\Core\Database::all("SELECT skey, svalue FROM _settings");
        $s = \array_column($settings, 'svalue', 'skey');
        $hasLogo    = \file_exists(ROOT.'/imgs/logo.png') || \file_exists(ROOT.'/imgs/logo.svg');
        $hasFavicon = \file_exists(ROOT.'/imgs/favicon.png') || \file_exists(ROOT.'/imgs/favicon.ico');
        \App\Core\View::render('admin/settings', compact('s','hasLogo','hasFavicon'), 'admin');
    }

    public function update(): void
    {
        if (!csrf_ok()) abort(403);
        $keys = ['contact_email','contact_phone','contact_whatsapp','contact_note'];
        foreach ($keys as $k) {
            $v = \trim($_POST[$k] ?? '');
            \App\Core\Database::exec("INSERT INTO _settings (skey, svalue) VALUES(?,?) ON DUPLICATE KEY UPDATE svalue=?", [$k,$v,$v]);
        }
        flash('success','Configurações guardadas.');
        redirect(base_url('admin/definicoes'));
    }

    public function uploadLogo(): void
    {
        if (!csrf_ok()) abort(403);
        $type = $_POST['logo_type'] ?? 'logo'; // 'logo' ou 'favicon'
        if (empty($_FILES['logo_file']) || $_FILES['logo_file']['error'] !== \UPLOAD_ERR_OK) {
            flash('error','Erro no upload do ficheiro.');
            redirect(base_url('admin/definicoes'));
        }
        $f    = $_FILES['logo_file'];
        $info = @\getimagesize($f['tmp_name']);
        if (!$info) { flash('error','Ficheiro inválido.'); redirect(base_url('admin/definicoes')); }

        $ext  = match($info[2]) {
            \IMAGETYPE_PNG  => 'png',
            \IMAGETYPE_JPEG => 'jpg',
            \IMAGETYPE_WEBP => 'webp',
            \IMAGETYPE_GIF  => 'gif',
            default         => null,
        };
        if (!$ext) { flash('error','Formato inválido (PNG, JPG, SVG ou GIF).'); redirect(base_url('admin/definicoes')); }

        $target = $type === 'favicon'
            ? ROOT . '/imgs/favicon.' . $ext
            : ROOT . '/imgs/logo.' . $ext;

        // Apagar logos/favicons anteriores
        foreach (['png','jpg','jpeg','webp','gif','svg','ico'] as $e) {
            @\unlink(ROOT . '/imgs/' . $type . '.' . $e);
        }

        if (\move_uploaded_file($f['tmp_name'], $target)) {
            flash('success', $type === 'favicon' ? 'Favicon actualizado.' : 'Logo actualizado.');
        } else {
            flash('error','Não foi possível guardar o ficheiro — verifica as permissões da pasta imgs/.');
        }
        redirect(base_url('admin/definicoes'));
    }

    public function updatePrecos(): void
    {
        if (!csrf_ok()) abort(403);

        $fields = [
            'PRECO_FOTO' => (float)(\str_replace(',', '.', $_POST['preco_foto'] ?? '5')),
            'PRECO_CTT'  => (float)(\str_replace(',', '.', $_POST['preco_ctt']  ?? '2')),
            'PRECO_INTL' => (float)(\str_replace(',', '.', $_POST['preco_intl'] ?? '5')),
        ];

        // Validar — não aceitar valores negativos ou absurdos
        foreach ($fields as $k => $v) {
            if ($v < 0 || $v > 9999) {
                flash('error', 'Valor inválido para ' . $k . '.');
                redirect(base_url('admin/definicoes'));
            }
        }

        // Actualizar .env linha a linha
        $envFile = ROOT . '/.env';
        $lines   = file_exists($envFile) ? file($envFile) : [];
        $updated = [];

        foreach ($lines as $line) {
            $found = false;
            foreach ($fields as $key => $val) {
                if (\preg_match('/^' . $key . '\s*=/i', $line)) {
                    $updated[] = $key . '=' . 
umber_format($val, 2, '.', '') . "
";
                    unset($fields[$key]);
                    $found = true;
                    break;
                }
            }
            if (!$found) $updated[] = $line;
        }

        // Adicionar chaves que não existiam ainda
        foreach ($fields as $key => $val) {
            $updated[] = $key . '=' . 
umber_format($val, 2, '.', '') . "
";
        }

        file_put_contents($envFile, \implode('', $updated));

        flash('success', 'Preços actualizados com sucesso.');
        redirect(base_url('admin/definicoes'));
    }

    public function changePassword(): void
    {
        if (!csrf_ok()) abort(403);
        $current = $_POST['current_pass'] ?? '';
        $new     = $_POST['new_pass']     ?? '';
        $confirm = $_POST['confirm_pass'] ?? '';

        if (!\password_verify($current, config('app.admin.hash'))) {
            flash('error','Password actual incorreta.');
            redirect(base_url('admin/definicoes'));
        }
        if (\strlen($new) < 8) {
            flash('error','A nova password deve ter pelo menos 8 caracteres.');
            redirect(base_url('admin/definicoes'));
        }
        if (!\preg_match('/[A-Z]/', $new)) {
            flash('error','A nova password deve ter pelo menos uma letra maiúscula.');
            redirect(base_url('admin/definicoes'));
        }
        if (!\preg_match('/[0-9]/', $new)) {
            flash('error','A nova password deve ter pelo menos um número.');
            redirect(base_url('admin/definicoes'));
        }
        if ($new !== $confirm) {
            flash('error','As passwords não coincidem.');
            redirect(base_url('admin/definicoes'));
        }

        $hash = \password_hash($new, PASSWORD_BCRYPT, ['cost' => 10]);

        // Escrever no .env linha a linha (sem preg_replace que corrompe $)
        $envFile = ROOT . '/.env';
        $lines   = \file_exists($envFile) ? \file($envFile) : [];
        $found   = false;
        $output  = [];
        foreach ($lines as $line) {
            if (\preg_match('/^ADMIN_HASH\s*=/i', $line)) {
                $output[] = 'ADMIN_HASH=' . $hash . "\n";
                $found    = true;
            } else {
                $output[] = $line;
            }
        }
        if (!$found) $output[] = 'ADMIN_HASH=' . $hash . "\n";
        \file_put_contents($envFile, \implode('', $output));

        flash('success','Password alterada com sucesso! Faz login novamente.');
        $_SESSION = []; \session_destroy();
        redirect(base_url('admin/login'));
    }
}
