<?php
declare(strict_types=1);

/**
 * Managed-tasks detail view: open tasks assigned to a specific user or
 * external participant within projects where the *current* user is owner or
 * manager. Drives the click-through path of the cmd-k people picker.
 *
 * Permission gate: at least one project must exist where the caller is
 * owner/manager AND the subject (user/external) has at least one task. If no
 * such overlap exists, return 403 — the page would otherwise be empty AND
 * confirm "this person exists in your reachable scope", which is exactly the
 * information we want to withhold from non-managers.
 */
class ManagedTasksController
{
    public static function forUser(int $userId): void
    {
        Auth::requireLogin();
        $db = Database::getInstance();

        $subject = $db->fetch(
            'SELECT id, username, email, avatar FROM users WHERE id = ? AND is_active = 1',
            [$userId]
        );
        if (!$subject) self::forbid();

        // Visibility check: subject must be reachable through the same
        // available_users_filter that gates the members picker (Provider
        // Edition isolates by group). Vanilla returns "always visible".
        if (!self::isUserVisibleToCurrentUser($userId, $db)) self::forbid();

        $tasks = self::loadTasksForUser($userId, $db);
        if (empty($tasks) && !self::hasManagedOverlapWithUser($userId, $db)) self::forbid();

        $subjectKind = 'user';
        $subjectName = (string)$subject['username'];
        $subjectMeta = (string)($subject['email'] ?? '');
        $subjectAvatar = $subject;

        self::renderView($tasks, $subjectKind, $subjectName, $subjectMeta, $subjectAvatar);
    }

    public static function forExternal(int $externalId): void
    {
        Auth::requireLogin();
        $db = Database::getInstance();

        $subject = $db->fetch(
            'SELECT e.id, e.name, e.email, e.project_id
             FROM project_externals e
             JOIN project_members pm ON pm.project_id = e.project_id AND pm.user_id = ?
             WHERE e.id = ?',
            [Auth::id(), $externalId]
        );
        if (!$subject) self::forbid();

        $tasks = self::loadTasksForExternal($externalId, $db);
        if (empty($tasks) && !self::hasManagedOverlapWithExternal($externalId, $db)) self::forbid();

        $subjectKind = 'external';
        $subjectName = (string)$subject['name'];
        $subjectMeta = (string)($subject['email'] ?? '');
        $subjectAvatar = null;

        self::renderView($tasks, $subjectKind, $subjectName, $subjectMeta, $subjectAvatar);
    }

    // ── Loaders ──────────────────────────────────────────────────────────────

    private static function loadTasksForUser(int $userId, Database $db): array
    {
        return $db->fetchAll(
            'SELECT t.*, ts.name AS status_name, ts.color AS status_color, ts.is_done,
                    p.name AS project_name, p.color AS project_color, p.id AS project_id
             FROM tasks t
             LEFT JOIN task_statuses ts ON ts.id = t.status_id
             JOIN projects p ON p.id = t.project_id
             JOIN project_members pm ON pm.project_id = p.id AND pm.user_id = ? AND pm.role IN (\'owner\', \'manager\')
             WHERE p.status != \'archived\'
               AND COALESCE(ts.is_done, 0) = 0
               AND EXISTS (SELECT 1 FROM task_assignees ta WHERE ta.task_id = t.id AND ta.user_id = ?)
             ORDER BY p.name,
                      CASE WHEN t.due_date IS NULL THEN 1 ELSE 0 END,
                      t.due_date ASC,
                      CASE t.priority WHEN \'urgent\' THEN 0 WHEN \'high\' THEN 1 WHEN \'medium\' THEN 2 ELSE 3 END',
            [Auth::id(), $userId]
        );
    }

    private static function loadTasksForExternal(int $externalId, Database $db): array
    {
        return $db->fetchAll(
            'SELECT t.*, ts.name AS status_name, ts.color AS status_color, ts.is_done,
                    p.name AS project_name, p.color AS project_color, p.id AS project_id
             FROM tasks t
             LEFT JOIN task_statuses ts ON ts.id = t.status_id
             JOIN projects p ON p.id = t.project_id
             JOIN project_members pm ON pm.project_id = p.id AND pm.user_id = ? AND pm.role IN (\'owner\', \'manager\')
             WHERE p.status != \'archived\'
               AND COALESCE(ts.is_done, 0) = 0
               AND EXISTS (SELECT 1 FROM task_assignees ta WHERE ta.task_id = t.id AND ta.external_id = ?)
             ORDER BY p.name,
                      CASE WHEN t.due_date IS NULL THEN 1 ELSE 0 END,
                      t.due_date ASC,
                      CASE t.priority WHEN \'urgent\' THEN 0 WHEN \'high\' THEN 1 WHEN \'medium\' THEN 2 ELSE 3 END',
            [Auth::id(), $externalId]
        );
    }

    // ── Permission helpers ───────────────────────────────────────────────────

    /**
     * Mirrors the available_users_filter gate. If the plugin returns an unsafe
     * fragment, fall back to "user is reachable" (rather than locking out the
     * caller because of a buggy plugin).
     */
    private static function isUserVisibleToCurrentUser(int $targetUserId, Database $db): bool
    {
        $isAdmin = Auth::isAdmin();
        $userFilter = PluginManager::filter(
            'available_users_filter',
            ['extra_where' => '', 'extra_params' => []],
            ['is_admin' => $isAdmin]
        );
        $extraWhere = (string)($userFilter['extra_where'] ?? '');
        if ($extraWhere !== '' && !ProjectController::isSafeWhereFragment($extraWhere)) {
            $extraWhere = '';
            $userFilter['extra_params'] = [];
        }
        $params = array_merge($userFilter['extra_params'] ?? [], [$targetUserId]);
        $row = $db->fetch(
            "SELECT id FROM users WHERE is_active = 1{$extraWhere} AND id = ?",
            $params
        );
        return $row !== false && $row !== null;
    }

    private static function hasManagedOverlapWithUser(int $userId, Database $db): bool
    {
        $row = $db->fetch(
            "SELECT 1 FROM project_members pm_me
             WHERE pm_me.user_id = ? AND pm_me.role IN ('owner','manager')
               AND EXISTS (
                 SELECT 1 FROM project_members pm_them
                 WHERE pm_them.project_id = pm_me.project_id AND pm_them.user_id = ?
               ) LIMIT 1",
            [Auth::id(), $userId]
        );
        return (bool)$row;
    }

    private static function hasManagedOverlapWithExternal(int $externalId, Database $db): bool
    {
        $row = $db->fetch(
            "SELECT 1 FROM project_externals e
             JOIN project_members pm ON pm.project_id = e.project_id AND pm.user_id = ? AND pm.role IN ('owner','manager')
             WHERE e.id = ? LIMIT 1",
            [Auth::id(), $externalId]
        );
        return (bool)$row;
    }

    // ── Rendering ────────────────────────────────────────────────────────────

    private static function renderView(
        array $tasks,
        string $subjectKind,
        string $subjectName,
        string $subjectMeta,
        ?array $subjectAvatar
    ): void {
        $db = Database::getInstance();
        Helper::loadAssignees($db, $tasks);

        $grouped = [];
        foreach ($tasks as $task) {
            $pid = (int)$task['project_id'];
            $grouped[$pid]['project'] = [
                'id'    => $pid,
                'name'  => $task['project_name'],
                'color' => $task['project_color'],
            ];
            $grouped[$pid]['tasks'][] = $task;
        }

        $pageTitle = $subjectName . ' – ' . t('managed_tasks_title');
        view('users/managed-tasks', compact(
            'tasks', 'grouped', 'subjectKind', 'subjectName', 'subjectMeta', 'subjectAvatar', 'pageTitle'
        ));
    }

    /**
     * 404 instead of 403 on purpose. A 403 confirms "this id exists but you
     * cannot see it"; a 404 hides whether the id exists at all. The cmd-k
     * search is the only legitimate entry point and already filters by the
     * caller's permissions, so a 404 here only fires on hand-crafted URLs.
     */
    private static function forbid(): never
    {
        http_response_code(404);
        view('errors/404', [], 'base');
        exit;
    }
}
