<?php

declare(strict_types=1);

namespace App\Core;

use PDO;

final class Auth
{
    private const SESSION_USER = 'auth_user_id';
    private const LAST_ACTIVITY = 'auth_last_activity';
    private const TIMEOUT_SECONDS = 1800;

    public static function attempt(string $email, string $password): bool
    {
        $pdo = Database::connection();
        $stmt = $pdo->prepare('SELECT id, full_name, email, password_hash, status FROM users WHERE email = :email LIMIT 1');
        $stmt->execute(['email' => strtolower(trim($email))]);
        $user = $stmt->fetch();

        if (!is_array($user) || $user['status'] !== 'active' || !password_verify($password, (string) $user['password_hash'])) {
            return false;
        }

        Session::regenerate();
        Session::put(self::SESSION_USER, (int) $user['id']);
        Session::put(self::LAST_ACTIVITY, time());

        $update = $pdo->prepare('UPDATE users SET last_login_at = NOW() WHERE id = :id');
        $update->execute(['id' => (int) $user['id']]);
        self::audit((int) $user['id'], 'login', 'user', (int) $user['id']);
        return true;
    }

    public static function hasSessionUser(): bool
    {
        Session::start();
        $userId = Session::get(self::SESSION_USER);
        return is_int($userId) || ctype_digit((string) $userId);
    }

    public static function check(): bool
    {
        Session::start();
        $userId = Session::get(self::SESSION_USER);
        if (!is_int($userId) && !ctype_digit((string) $userId)) {
            return false;
        }

        $last = (int) Session::get(self::LAST_ACTIVITY, 0);
        if ($last > 0 && (time() - $last) > self::TIMEOUT_SECONDS) {
            self::logout(false);
            return false;
        }

        Session::put(self::LAST_ACTIVITY, time());
        return self::user() !== null;
    }

    public static function id(): ?int
    {
        if (!self::check()) {
            return null;
        }
        return (int) Session::get(self::SESSION_USER);
    }

    public static function user(): ?array
    {
        $id = Session::get(self::SESSION_USER);
        if ($id === null) {
            return null;
        }

        $pdo = Database::connection();
        $stmt = $pdo->prepare('SELECT id, full_name, email, phone, status, last_login_at, created_at FROM users WHERE id = :id LIMIT 1');
        $stmt->execute(['id' => (int) $id]);
        $user = $stmt->fetch();
        if (!is_array($user)) {
            return null;
        }
        $roleStmt = $pdo->prepare('SELECT r.id, r.name, r.display_name FROM user_roles ur INNER JOIN roles r ON r.id = ur.role_id WHERE ur.user_id = :user_id ORDER BY r.display_name');
        $roleStmt->execute(['user_id' => (int) $id]);
        $user['roles'] = $roleStmt->fetchAll();
        return $user;
    }

    public static function logout(bool $audit = true): void
    {
        $id = Session::get(self::SESSION_USER);
        if ($audit && $id !== null) {
            self::audit((int) $id, 'logout', 'user', (int) $id);
        }
        Session::destroy();
    }

    public static function hasRole(string $role): bool
    {
        $id = self::id();
        if ($id === null) return false;
        $stmt = Database::connection()->prepare('SELECT 1 FROM user_roles ur INNER JOIN roles r ON r.id = ur.role_id WHERE ur.user_id = :user_id AND r.name = :role LIMIT 1');
        $stmt->execute(['user_id' => $id, 'role' => $role]);
        return (bool) $stmt->fetchColumn();
    }

    public static function hasPermission(string $permission): bool
    {
        $id = self::id();
        if ($id === null) return false;

        $stmt = Database::connection()->prepare(
            'SELECT 1
             FROM user_roles ur
             INNER JOIN role_permissions rp ON rp.role_id = ur.role_id
             INNER JOIN permissions p ON p.id = rp.permission_id
             WHERE ur.user_id = :user_id AND p.name = :permission
             LIMIT 1'
        );
        $stmt->execute(['user_id' => $id, 'permission' => $permission]);
        if ((bool) $stmt->fetchColumn()) {
            return true;
        }

        // The initial Super Admin is created during /setup. Older Phase 2
        // databases may have the SUPER_ADMIN role but may not have had the
        // Phase 5 role-permission seed applied. Keep the technical Super
        // Admin permissions available while the database seed is repaired.
        if (self::hasRole('SUPER_ADMIN')) {
            return in_array($permission, [
                'users.view',
                'users.create',
                'users.edit',
                'users.disable',
                'settings.manage',
                'audit.view',
                'therapists.view',
                'therapists.create',
                'therapists.edit',
                'patients.view',
                'patients.create',
                'patients.edit',
                'patients.delete',
                'therapists.delete',
                'patients.assign',
                'sessions.view', 'sessions.create', 'sessions.edit',
                'appointments.view','appointments.status',
                'assessments.view','assessments.create','assessments.edit','assessments.submit','assessments.review','assessments.approve',
                'treatment_plans.view','treatment_plans.create','treatment_plans.edit','treatment_plans.manage',
                'reports.view','reports.create','reports.edit','reports.submit','reports.review','reports.approve','reports.generate',
                'services.view','services.manage',
                'invoice.view','invoice.create',
                'payment.view','payment.create','payment.void','receipt.view',
            ], true);
        }

        if (self::hasRole('THERAPIST')) {
            return in_array($permission, [
                'patients.view',
                'sessions.view', 'sessions.create', 'sessions.edit',
                'appointments.view','appointments.create','appointments.edit','appointments.status',
                'assessments.view','assessments.create','assessments.edit','assessments.submit',
                'treatment_plans.view','treatment_plans.create','treatment_plans.edit',
            ], true);
        }

        if (self::hasRole('ADMIN')) {
            return in_array($permission, [
                'therapists.view',
                'therapists.create',
                'therapists.edit',
                'patients.view',
                'patients.create',
                'patients.edit',
                'patients.delete',
                'therapists.delete',
                'patients.assign',
                'sessions.view', 'sessions.create', 'sessions.edit',
                'appointments.view','appointments.create','appointments.edit','appointments.status',
                'assessments.view','assessments.create','assessments.edit','assessments.review','assessments.approve',
                'reports.view','reports.create','reports.edit','reports.submit','reports.review','reports.approve','reports.generate',
                'services.view','services.manage',
                'invoice.view','invoice.create',
                'payment.view','payment.create','payment.void','receipt.view',
            ], true);
        }

        return false;
    }

    private static function audit(int $userId, string $action, string $entityType, int $entityId): void
    {
        try {
            $stmt = Database::connection()->prepare('INSERT INTO audit_logs (user_id, action, entity_type, entity_id, ip_address, metadata, created_at) VALUES (:user_id, :action, :entity_type, :entity_id, :ip, :metadata, NOW())');
            $stmt->execute([
                'user_id' => $userId,
                'action' => $action,
                'entity_type' => $entityType,
                'entity_id' => $entityId,
                'ip' => $_SERVER['REMOTE_ADDR'] ?? null,
                'metadata' => json_encode([], JSON_THROW_ON_ERROR),
            ]);
        } catch (\Throwable) {
            // Authentication must not fail because audit logging is unavailable.
        }
    }
}
