<?php

namespace App\Models;

/**
 * Write-once audit trail. Phase 1's UI for this is bare-bones (see
 * ActivityLogController) but every write always carries a `module` value
 * so Phase 4 can filter by it retroactively without back-filling data.
 */
class ActivityLogModel extends TenantModel
{
    protected $table         = 'activity_log';
    protected $primaryKey    = 'id';
    protected $returnType    = 'object';
    protected $useTimestamps = true;
    protected $createdField  = 'created_at';
    protected $updatedField  = ''; // this table is never updated in place

    protected $allowedFields = [
        'user_id',
        'module',
        'action',
        'ip_address',
        'created_at',
    ];

    /**
     * Convenience wrapper used throughout the app instead of ->insert()
     * directly, so every call site logs consistently. $ipAddress is
     * optional/nullable — see AddIpAddressToActivityLog's migration
     * comment for why old rows and any caller that doesn't have a real
     * request context (CLI jobs, etc.) simply store null rather than a
     * fabricated address.
     */
    public function log(?int $userId, string $module, string $action, ?string $ipAddress = null): void
    {
        $this->insert([
            'user_id'    => $userId,
            'module'     => $module,
            'action'     => $action,
            'ip_address' => $ipAddress,
        ]);
    }

    /**
     * Short list for the dashboard's activity widget — ActivityLogController
     * ::index() has its own paginated, user-joined query for the full log
     * page; this is the same join, just capped and unpaginated.
     */
    public function recent(int $limit = 10): array
    {
        return $this->select('activity_log.*, users.full_name as user_name')
            ->join('users', 'users.id = activity_log.user_id', 'left')
            ->orderBy('activity_log.created_at', 'desc')
            ->limit($limit)
            ->findAll();
    }

    /**
     * Exactly the retroactive filter the class doc comment anticipated —
     * used by StaffPerformanceController for "bookings created" (module
     * 'bookings', prefix 'Booked room') and "check-ins processed" (module
     * 'checkin', prefix 'Checked in'). Real activity already being
     * logged, just grouped by who did it.
     */
    public function countByModuleAndPrefix(string $module, string $actionPrefix, string $from, string $to): array
    {
        $rows = $this->select('user_id, COUNT(*) as action_count')
            ->where('module', $module)
            ->like('action', $actionPrefix, 'after')
            ->where('created_at >=', $from . ' 00:00:00')
            ->where('created_at <=', $to . ' 23:59:59')
            ->where('user_id IS NOT NULL')
            ->groupBy('user_id')
            ->findAll();

        $byUser = [];

        foreach ($rows as $row) {
            $byUser[$row->user_id] = (int) $row->action_count;
        }

        return $byUser;
    }

    /**
     * The Staff Profile page's Activity Log tab — same join as recent(),
     * scoped to one user instead of tenant-wide.
     */
    public function forUser(int $userId, int $limit = 20): array
    {
        return $this->select('activity_log.*, users.full_name as user_name')
            ->join('users', 'users.id = activity_log.user_id', 'left')
            ->where('activity_log.user_id', $userId)
            ->orderBy('activity_log.created_at', 'desc')
            ->limit($limit)
            ->findAll();
    }

    /**
     * The Staff Users list's "Last Login" column — derived from the same
     * "{full_name} logged in." rows AuthController::attemptLogin() already
     * writes on every successful login, not a new tracking column.
     *
     * @return array<int, string> user_id => most recent login's created_at
     */
    public function lastLoginByUser(): array
    {
        $rows = $this->select('user_id, MAX(created_at) as last_login')
            ->where('module', 'auth')
            ->like('action', 'logged in.', 'before')
            ->where('user_id IS NOT NULL')
            ->groupBy('user_id')
            ->findAll();

        $byUser = [];

        foreach ($rows as $row) {
            $byUser[$row->user_id] = $row->last_login;
        }

        return $byUser;
    }

    /**
     * A short "Action" tag distinct from the full "Details" sentence
     * (Activity Log page) — almost every logActivity() call site writes
     * a sentence starting with a past-tense verb ("Created ...", "Updated
     * ...", "Checked in ..."), so the tag is just that sentence's own
     * leading word, not an invented category. A handful of 'auth' rows
     * instead start with the actor's own name ("Ama Owusu logged in.") —
     * $userName (the same name already joined in from `users` everywhere
     * this is called) lets those be stripped first so the tag is still
     * the real verb ("Logged"), not the name. Falls back to "Activity"
     * for the rare row that doesn't start with a word character.
     */
    public static function actionTag(string $action, ?string $userName = null): string
    {
        $text = trim($action);

        if ($userName !== null && $userName !== '' && str_starts_with($text, $userName)) {
            $text = trim(substr($text, strlen($userName)));
        }

        if (preg_match('/^([A-Za-z][A-Za-z\'-]*)/', $text, $m)) {
            return ucfirst($m[1]);
        }

        return 'Activity';
    }

    /**
     * Real, observed tags for the Activity Log page's "All Actions"
     * filter dropdown — every distinct leading verb actually present in
     * the data right now, for every module except 'auth'. 'auth' is
     * excluded from the raw SQL computation and handled separately
     * (AUTH_TAGS below) because several of its action sentences start
     * with the actor's own name rather than a verb ("Ama Owusu logged
     * in.") — see actionTag()'s doc comment. AUTH_TAGS are grounded
     * directly in AuthController/ForgotPasswordController's actual
     * logActivity() calls (Logged in/out, Requested a reset code, Reset
     * their password) — a real, closed set of sentence patterns those
     * two controllers write, not a guess.
     */
    private const AUTH_TAGS = ['Logged', 'Requested', 'Reset'];

    public function distinctActionTags(): array
    {
        $rows = $this->select("SUBSTRING_INDEX(TRIM(action), ' ', 1) as tag", false)
            ->where('module !=', 'auth')
            ->distinct()
            ->orderBy('tag', 'asc')
            ->findAll();

        $tags = array_values(array_filter(array_column($rows, 'tag')));
        $tags = array_unique(array_merge($tags, self::AUTH_TAGS));
        sort($tags);

        return $tags;
    }
}
