<?php

/**
 * AdminAuth - File-based authentication for CodePress Admin
 */
class AdminAuth
{
    private array $config;
    private array $adminConfig;
    private string $lockFile;

    /**
     * Role definitions with permissions.
     * Each role maps to a list of allowed route prefixes.
     * 'admin' has wildcard '*' access.
     */
    public const ROLE_PERMISSIONS = [
        'admin' => ['*'],
        'content-manager' => ['dashboard', 'content', 'content-edit', 'content-new', 'content-delete', 'content-dir-create', 'content-dir-rename', 'content-dir-delete', 'content-move', 'content-backup', 'content-restore', 'content-git-init', 'content-git-commit', 'content-git-restore', 'guide', 'logout'],
        'bi-manager' => ['dashboard', 'statistics', 'logs', 'guide', 'logout'],
        'site-admin' => ['dashboard', 'theme', 'theme-new', 'plugins', 'plugins-new', 'plugins-edit', 'plugins-config', 'plugins-toggle', 'plugins-delete', 'statistics', 'logs', 'update', 'guide', 'logout'],
    ];

    /**
     * Human-readable role labels.
     */
    public const ROLE_LABELS = [
        'admin' => 'Admin',
        'content-manager' => 'Content Beheerder',
        'bi-manager' => 'BI Beheerder',
        'site-admin' => 'Site Admin',
    ];

    public function __construct(array $appConfig)
    {
        $this->config = $appConfig;
        $this->adminConfig = $this->loadAdminConfig();
        $this->lockFile = dirname($appConfig['log_file']) . '/login_attempts.json';
        $this->startSession();
    }

    private function loadAdminConfig(): array
    {
        $path = $this->config['admin_config'];
        $examplePath = dirname($path) . '/admin.json.example';

        if (!file_exists($path)) {
            if (file_exists($examplePath)) {
                @copy($examplePath, $path);
            } else {
                $defaultAdminConfig = [
                    'users' => [
                        [
                            'username' => 'admin',
                            'password_hash' => password_hash('admin', PASSWORD_BCRYPT),
                            'role' => 'admin',
                            'created' => date('Y-m-d'),
                        ]
                    ],
                    'security' => [
                        'session_timeout' => 1800,
                        'max_login_attempts' => 5,
                        'lockout_duration' => 900,
                    ]
                ];
                $dir = dirname($path);
                if (!is_dir($dir)) {
                    @mkdir($dir, 0755, true);
                }
                @file_put_contents($path, json_encode($defaultAdminConfig, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
            }
        }
        $data = file_exists($path) ? json_decode(file_get_contents($path), true) : null;
        return is_array($data) ? $data : ['users' => [], 'security' => []];
    }

    public function saveAdminConfig(): void
    {
        file_put_contents(
            $this->config['admin_config'],
            json_encode($this->adminConfig, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
        );
    }

    private function startSession(): void
    {
        if (session_status() === PHP_SESSION_NONE) {
            $timeout = $this->adminConfig['security']['session_timeout'] ?? 1800;
            $isHttps = !empty($_SERVER['HTTPS'])
                || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
                || (isset($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] === 'on');
            session_set_cookie_params([
                'lifetime' => $timeout,
                'path' => '/',
                'secure' => $isHttps,
                'httponly' => true,
                'samesite' => 'Strict'
            ]);
            session_start();
        }

        // Check session timeout
        if (isset($_SESSION['admin_last_activity'])) {
            $timeout = $this->adminConfig['security']['session_timeout'] ?? 1800;
            if (time() - $_SESSION['admin_last_activity'] > $timeout) {
                $this->logout();
                return;
            }
        }

        if ($this->isAuthenticated()) {
            $_SESSION['admin_last_activity'] = time();
        }
    }

    public function login(string $username, string $password): array
    {
        // Check brute-force lockout
        $lockout = $this->checkLockout($username);
        if ($lockout['locked']) {
            return [
                'success' => false,
                'message' => 'Account tijdelijk vergrendeld. Probeer over ' . $lockout['remaining'] . ' seconden opnieuw.'
            ];
        }

        // Find user
        $user = $this->findUser($username);
        if (!$user || !password_verify($password, $user['password_hash'])) {
            $this->recordFailedAttempt($username);
            $this->log('warning', "Mislukte inlogpoging: {$username}");
            return ['success' => false, 'message' => 'Onjuiste gebruikersnaam of wachtwoord.'];
        }

        // Success - clear failed attempts
        $this->clearFailedAttempts($username);

        // Set session
        $_SESSION['admin_user'] = $username;
        $_SESSION['admin_role'] = $user['role'] ?? 'admin';
        $_SESSION['admin_last_activity'] = time();
        $_SESSION['admin_csrf_token'] = bin2hex(random_bytes(32));

        $this->log('info', "Ingelogd: {$username}");
        return ['success' => true, 'message' => 'Ingelogd.'];
    }

    public function logout(): void
    {
        $user = $_SESSION['admin_user'] ?? 'unknown';
        $_SESSION = [];
        if (ini_get('session.use_cookies')) {
            $params = session_get_cookie_params();
            setcookie(session_name(), '', time() - 42000,
                $params['path'], $params['domain'],
                $params['secure'], $params['httponly']
            );
        }
        session_destroy();
        $this->log('info', "Uitgelogd: {$user}");
    }

    public function isAuthenticated(): bool
    {
        return isset($_SESSION['admin_user']);
    }

    public function getCurrentUser(): ?array
    {
        if (!$this->isAuthenticated()) {
            return null;
        }
        $username = $_SESSION['admin_user'];
        $userData = [
            'username' => $username,
            'role' => $_SESSION['admin_role'] ?? 'admin'
        ];
        // Enrich with profile fields from admin.json
        $userEntry = $this->findUser($username);
        if ($userEntry) {
            $userData['email'] = $userEntry['email'] ?? '';
            $userData['author_name'] = $userEntry['author_name'] ?? '';
            $userData['author_email'] = $userEntry['author_email'] ?? '';
        }
        return $userData;
    }

    /**
     * Get the role of the current user.
     */
    public function getCurrentRole(): string
    {
        return $_SESSION['admin_role'] ?? 'admin';
    }

    /**
     * Check if the current user has permission to access a route.
     */
    public function hasPermission(string $route): bool
    {
        $role = $this->getCurrentRole();
        $permissions = self::ROLE_PERMISSIONS[$role] ?? ['dashboard', 'logout'];

        if (in_array('*', $permissions, true)) {
            return true;
        }

        return in_array($route, $permissions, true);
    }

    /**
     * Get available roles.
     */
    public static function getRoles(): array
    {
        return self::ROLE_LABELS;
    }

    /**
     * Get role label.
     */
    public static function getRoleLabel(string $role): string
    {
        return self::ROLE_LABELS[$role] ?? $role;
    }

    public function getCsrfToken(): string
    {
        if (!isset($_SESSION['admin_csrf_token'])) {
            $_SESSION['admin_csrf_token'] = bin2hex(random_bytes(32));
        }
        return $_SESSION['admin_csrf_token'];
    }

    public function verifyCsrf(string $token): bool
    {
        return isset($_SESSION['admin_csrf_token']) && hash_equals($_SESSION['admin_csrf_token'], $token);
    }

    public function regenerateCsrfToken(): void
    {
        $_SESSION['admin_csrf_token'] = bin2hex(random_bytes(32));
    }

    // --- User Management ---

    public function getUsers(): array
    {
        $users = [];
        foreach ($this->adminConfig['users'] ?? [] as $u) {
            $role = $u['role'] ?? 'admin';
            $users[$u['username']] = [
                'username' => $u['username'],
                'role' => $role,
                'role_label' => self::getRoleLabel($role),
                'created' => $u['created'] ?? '',
                'email' => $u['email'] ?? '',
                'author_name' => $u['author_name'] ?? '',
                'author_email' => $u['author_email'] ?? '',
            ];
        }
        return $users;
    }

    public function addUser(string $username, string $password, string $role = 'admin', string $email = '', string $authorName = '', string $authorEmail = ''): array
    {
        if ($this->findUser($username)) {
            return ['success' => false, 'message' => 'Gebruiker bestaat al.'];
        }
        if (strlen($password) < 8) {
            return ['success' => false, 'message' => 'Wachtwoord moet minimaal 8 tekens zijn.'];
        }
        if (!isset(self::ROLE_PERMISSIONS[$role])) {
            return ['success' => false, 'message' => 'Ongeldige rol.'];
        }

        $this->adminConfig['users'][] = [
            'username' => $username,
            'password_hash' => password_hash($password, PASSWORD_DEFAULT),
            'role' => $role,
            'email' => $email,
            'author_name' => $authorName,
            'author_email' => $authorEmail,
            'created' => date('Y-m-d')
        ];
        $this->saveAdminConfig();
        $this->log('info', "Gebruiker aangemaakt: {$username} (rol: {$role})");
        return ['success' => true, 'message' => 'Gebruiker aangemaakt.'];
    }

    /**
     * Update the profile (email, author_name, author_email) of a user.
     */
    public function updateUserProfile(string $username, string $email = '', string $authorName = '', string $authorEmail = ''): array
    {
        foreach ($this->adminConfig['users'] as &$userEntry) {
            if ($userEntry['username'] === $username) {
                $userEntry['email'] = $email;
                $userEntry['author_name'] = $authorName;
                $userEntry['author_email'] = $authorEmail;
                $this->saveAdminConfig();
                $this->log('info', "Profiel bijgewerkt: {$username}");
                return ['success' => true, 'message' => 'Profiel opgeslagen.'];
            }
        }
        return ['success' => false, 'message' => 'Gebruiker niet gevonden.'];
    }

    /**
     * Change the role of an existing user.
     */
    public function changeRole(string $username, string $role): array
    {
        if (!isset(self::ROLE_PERMISSIONS[$role])) {
            return ['success' => false, 'message' => 'Ongeldige rol.'];
        }
        foreach ($this->adminConfig['users'] as &$user) {
            if ($user['username'] === $username) {
                $user['role'] = $role;
                $this->saveAdminConfig();
                $this->log('info', "Rol gewijzigd: {$username} -> {$role}");
                return ['success' => true, 'message' => 'Rol gewijzigd.'];
            }
        }
        return ['success' => false, 'message' => 'Gebruiker niet gevonden.'];
    }

    public function deleteUser(string $username): array
    {
        if ($username === ($_SESSION['admin_user'] ?? '')) {
            return ['success' => false, 'message' => 'Je kunt jezelf niet verwijderen.'];
        }

        $this->adminConfig['users'] = array_values(array_filter(
            $this->adminConfig['users'],
            fn($u) => $u['username'] !== $username
        ));
        $this->saveAdminConfig();
        $this->log('info', "Gebruiker verwijderd: {$username}");
        return ['success' => true, 'message' => 'Gebruiker verwijderd.'];
    }

    public function changePassword(string $username, string $newPassword): array
    {
        if (strlen($newPassword) < 8) {
            return ['success' => false, 'message' => 'Wachtwoord moet minimaal 8 tekens zijn.'];
        }
        foreach ($this->adminConfig['users'] as &$user) {
            if ($user['username'] === $username) {
                $user['password_hash'] = password_hash($newPassword, PASSWORD_DEFAULT);
                $this->saveAdminConfig();
                $this->log('info', "Wachtwoord gewijzigd: {$username}");
                return ['success' => true, 'message' => 'Wachtwoord gewijzigd.'];
            }
        }
        return ['success' => false, 'message' => 'Gebruiker niet gevonden.'];
    }

    public function changeOwnPassword(string $username, string $currentPassword, string $newPassword): array
    {
        $user = $this->findUser($username);
        if (!$user) {
            return ['success' => false, 'message' => 'Gebruiker niet gevonden.'];
        }
        if (!password_verify($currentPassword, $user['password_hash'])) {
            return ['success' => false, 'message' => 'Huidig wachtwoord is onjuist.'];
        }
        if (strlen($newPassword) < 8) {
            return ['success' => false, 'message' => 'Nieuw wachtwoord moet minimaal 8 tekens zijn.'];
        }
        foreach ($this->adminConfig['users'] as &$u) {
            if ($u['username'] === $username) {
                $u['password_hash'] = password_hash($newPassword, PASSWORD_DEFAULT);
                $this->saveAdminConfig();
                $this->log('info', "Eigen wachtwoord gewijzigd: {$username}");
                return ['success' => true, 'message' => 'Wachtwoord gewijzigd.'];
            }
        }
        return ['success' => false, 'message' => 'Fout bij wijzigen wachtwoord.'];
    }

    // --- Private helpers ---

    private function findUser(string $username): ?array
    {
        foreach ($this->adminConfig['users'] ?? [] as $user) {
            if ($user['username'] === $username) {
                return $user;
            }
        }
        return null;
    }

    private function checkLockout(string $username): array
    {
        $attempts = $this->getFailedAttempts();
        $maxAttempts = $this->adminConfig['security']['max_login_attempts'] ?? 5;
        $lockoutDuration = $this->adminConfig['security']['lockout_duration'] ?? 900;

        if (!isset($attempts[$username])) {
            return ['locked' => false];
        }

        $record = $attempts[$username];
        if ($record['count'] >= $maxAttempts) {
            $elapsed = time() - $record['last_attempt'];
            if ($elapsed < $lockoutDuration) {
                return ['locked' => true, 'remaining' => $lockoutDuration - $elapsed];
            }
            // Lockout expired
            $this->clearFailedAttempts($username);
        }

        return ['locked' => false];
    }

    private function recordFailedAttempt(string $username): void
    {
        $attempts = $this->getFailedAttempts();
        if (!isset($attempts[$username])) {
            $attempts[$username] = ['count' => 0, 'last_attempt' => 0];
        }
        $attempts[$username]['count']++;
        $attempts[$username]['last_attempt'] = time();
        file_put_contents($this->lockFile, json_encode($attempts));
    }

    private function clearFailedAttempts(string $username): void
    {
        $attempts = $this->getFailedAttempts();
        unset($attempts[$username]);
        file_put_contents($this->lockFile, json_encode($attempts));
    }

    private function getFailedAttempts(): array
    {
        if (!file_exists($this->lockFile)) {
            return [];
        }
        $data = json_decode(file_get_contents($this->lockFile), true);
        return is_array($data) ? $data : [];
    }

    private function log(string $level, string $message): void
    {
        $logFile = $this->config['log_file'];
        $dir = dirname($logFile);
        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }
        $timestamp = date('Y-m-d H:i:s');
        if (!class_exists('RequestLogger')) {
            $loggerClass = __DIR__ . '/../../cms/core/class/RequestLogger.php';
            if (file_exists($loggerClass)) {
                require_once $loggerClass;
            }
        }
        $ip = class_exists('RequestLogger') ? RequestLogger::getClientIp() : ($_SERVER['REMOTE_ADDR'] ?? '127.0.0.1');
        file_put_contents($logFile, "[{$timestamp}] [{$level}] [{$ip}] {$message}\n", FILE_APPEND);
    }
}
