<?php

declare(strict_types=1);

namespace PpPhp\Modules\Notifications\Controllers;

use PpPhp\Core\Config;
use PpPhp\Core\DB;
use PpPhp\Core\ModuleState;
use PpPhp\Core\ProjectNav;
use PpPhp\Core\Security;

/**
 * Notification Rules — a no-code builder that turns "when a record is
 * added / edited / deleted, notify these people" into generated event code.
 *
 * Rules are stored per-project in ModuleState('notifRules') and compiled at
 * BUILD time into the table's afterAddSave / afterEditSave / afterDelete hooks
 * (see BuildWriter::mergeNotifRules), calling the Notification::AddFor* helpers.
 */
class RulesController
{
    private array $project = [];
    private string $slug   = '';

    public function __construct(private Config $config) {}

    public function index(string $slug): string
    {
        // Notification rules compile into table events — nothing to bind on a
        // database-free app. Redirect a direct hit to the Designer (no blank page).
        if (\PpPhp\Core\ProjectDatabaseProfile::isStandaloneSlug($slug)) {
            header('Location: ' . $this->config->get('base_url') . '/project/' . urlencode($slug) . '/designer');
            exit;
        }
        $this->initProject($slug);

        $project   = $this->project;
        $navItems  = ProjectNav::items('/notifications');
        $baseUrl   = $this->config->get('base_url');
        $assetsUrl = $this->config->get('assets_url');
        $user      = $_SESSION['user'] ?? Security::CurrentUserData();
        $pageTitle = 'Notifications';

        $tables      = $this->loadTables();
        $tableFields = [];
        foreach ($tables as $t) {
            $tableFields[$t['name']] = $this->tableFields($t['name']);
        }
        $roles  = $this->rolesList();
        $groups = $this->groupsList();
        $users  = $this->usersList();
        $rules  = array_values((array) (ModuleState::load($slug, 'notifRules')['rules'] ?? []));

        // Gate: the whole feature hinges on Notifications being enabled in
        // Misc → Advanced. If it's off, the view shows a disabled state + CTA.
        $misc         = ModuleState::load($slug, 'misc');
        $notifEnabled = !empty($misc['advanced']['notifEnabled']);
        $miscUrl      = rtrim((string) $baseUrl, '/') . '/project/' . $slug . '/misc#advancedPane';

        ob_start();
        include __DIR__ . '/../Views/index.php';
        return ob_get_clean();
    }

    public function save(string $slug): void
    {
        header('Content-Type: application/json');
        $this->initProject($slug);
        \PpPhp\Core\Security::RequireCsrf();

        $input = json_decode((string) file_get_contents('php://input'), true) ?: [];
        $rules = is_array($input['rules'] ?? null) ? $input['rules'] : [];

        // Sanitise each rule to a known shape before persisting.
        $types    = ['info', 'success', 'warning', 'error'];
        $triggers = ['add', 'edit', 'delete'];
        $auds     = ['everyone', 'role', 'group', 'users', 'owner', 'actor'];
        $links    = ['record', 'none', 'custom'];
        $clean    = [];
        foreach ($rules as $r) {
            if (!is_array($r)) continue;
            $aud = is_array($r['audience'] ?? null) ? $r['audience'] : [];
            $clean[] = [
                'id'       => preg_replace('/[^a-zA-Z0-9_-]/', '', (string) ($r['id'] ?? uniqid('nr_'))),
                'name'     => mb_substr((string) ($r['name'] ?? ''), 0, 120),
                'table'    => preg_replace('/[^a-zA-Z0-9_]/', '', (string) ($r['table'] ?? '')),
                'trigger'  => in_array(($r['trigger'] ?? 'add'), $triggers, true) ? (string) $r['trigger'] : 'add',
                'enabled'  => !isset($r['enabled']) || (bool) $r['enabled'],
                'audience' => [
                    'type'  => in_array(($aud['type'] ?? 'everyone'), $auds, true) ? (string) $aud['type'] : 'everyone',
                    'value' => is_array($aud['value'] ?? null)
                        ? array_values(array_map('intval', $aud['value']))
                        : mb_substr((string) ($aud['value'] ?? ''), 0, 120),
                ],
                'title'    => mb_substr((string) ($r['title'] ?? ''), 0, 255),
                'message'  => mb_substr((string) ($r['message'] ?? ''), 0, 500),
                'ntype'    => in_array(($r['ntype'] ?? 'info'), $types, true) ? (string) $r['ntype'] : 'info',
                'link'     => in_array(($r['link'] ?? 'record'), $links, true) ? (string) $r['link'] : 'record',
                'custom'   => mb_substr((string) ($r['custom'] ?? ''), 0, 500),
            ];
        }

        ModuleState::save($slug, 'notifRules', ['rules' => $clean]);
        echo json_encode(['success' => true, 'count' => count($clean)]);
        exit;
    }

    // ── helpers (mirror the other project controllers) ──

    /** @return array<int, array{name:string,type:string}> */
    private function loadTables(): array
    {
        $tablesState = ModuleState::load($this->slug, 'tables');
        $selected    = $tablesState['selectedTables'] ?? [];
        if (empty($selected)) {
            $rows = DB::Query('SHOW FULL TABLES')->fetchAll(\PDO::FETCH_NUM);
            $tables = [];
            foreach ($rows as $row) {
                if (($row[1] ?? '') === 'VIEW') continue;
                $tables[] = ['name' => $row[0], 'type' => 'TABLE'];
            }
            usort($tables, fn($a, $b) => $a['name'] <=> $b['name']);
            return $tables;
        }
        return array_map(fn($n) => ['name' => $n, 'type' => 'TABLE'], $selected);
    }

    /** @return array<int, string> */
    private function tableFields(string $table): array
    {
        try {
            $columns = DB::FetchAll("SHOW COLUMNS FROM `{$table}`");
        } catch (\Throwable $e) {
            return [];
        }
        return is_array($columns)
            ? array_values(array_filter(array_map('strval', array_column($columns, 'Field')), static fn($f) => $f !== ''))
            : [];
    }

    /** @return array<int, string> */
    private function rolesList(): array
    {
        try {
            $rows = DB::FetchAll("SELECT DISTINCT `role` FROM `users` WHERE `role` IS NOT NULL AND `role` <> ''") ?: [];
            $roles = array_values(array_filter(array_map(static fn($r) => (string) ($r['role'] ?? ''), $rows)));
            natcasesort($roles);
            return array_values($roles);
        } catch (\Throwable $e) {
            return ['admin', 'user'];
        }
    }

    /** @return array<int, array{id:int,name:string}> */
    private function groupsList(): array
    {
        try {
            $rows = DB::FetchAll("SELECT `id`, `name` FROM `groups` ORDER BY `name` ASC LIMIT 500") ?: [];
            return array_map(static fn($r) => ['id' => (int) $r['id'], 'name' => (string) $r['name']], $rows);
        } catch (\Throwable $e) {
            return [];
        }
    }

    /** @return array<int, array{id:int,name:string}> */
    private function usersList(): array
    {
        try {
            $rows = DB::FetchAll("SELECT `id`, `username` FROM `users` ORDER BY `username` ASC LIMIT 500") ?: [];
            return array_map(static fn($r) => ['id' => (int) $r['id'], 'name' => (string) $r['username']], $rows);
        } catch (\Throwable $e) {
            return [];
        }
    }

    private function initProject(string $slug): void
    {
        Security::RequireAuth();
        DB::Init();

        $project = DB::Select('projects', ['slug' => $slug, 'created_by' => Security::CurrentUserData()['id']]);
        if (empty($project) && Security::IsAdmin()) {
            $project = DB::Select('projects', ['slug' => $slug]);
        }
        if (empty($project)) {
            http_response_code(404);
            echo 'Project not found.';
            exit;
        }

        $this->project = $project[0];
        $this->slug    = $slug;
        DB::Init($slug);
        DB::SetConnection($slug);
    }
}
