<?php
declare(strict_types=1);

/**
 * Phase 1 RBAC: global platform roles + company memberships.
 * MariaDB-style: global scope (root/platform) vs company scope (owner/admin/member).
 */
final class Rbac {
    /** Global roles stored on users.role */
    public const GLOBAL_ROOT = 'root';
    public const GLOBAL_PLATFORM_ADMIN = 'platform_admin';
    /** Legacy alias — treated as platform_admin */
    public const GLOBAL_ADMIN_LEGACY = 'admin';
    public const GLOBAL_VIEWER = 'viewer';

    public const COMPANY_OWNER = 'owner';
    public const COMPANY_ADMIN = 'admin';
    public const COMPANY_MEMBER = 'member';

    /** @var list<string> */
    private const GLOBAL_ADMIN_ROLES = [self::GLOBAL_ROOT, self::GLOBAL_PLATFORM_ADMIN, self::GLOBAL_ADMIN_LEGACY];

    /** Company role => granted actions (unless global admin). */
    /**
     * Named privilege sets stored in company_memberships.permissions_json as {"grants":[...]}.
     * Company role defaults still apply; grants extend (not replace) role actions.
     */
    public const PRIVILEGE_SET_NONE = '';
    public const PRIVILEGE_SET_REMOTE_ACCESS_USER = 'remote_access_user';
    public const PRIVILEGE_SET_REMOTE_ACCESS_ADMIN = 'remote_access_admin';
    public const PRIVILEGE_SET_SHORT_LINKS_VIEWER = 'short_links_viewer';
    public const PRIVILEGE_SET_SHORT_LINKS_USER = 'short_links_user';

    /** @var array<string, list<string>> */
    public const PRIVILEGE_SETS = [
        self::PRIVILEGE_SET_REMOTE_ACCESS_USER => [
            'remote_access_view',
            'remote_access_operate',
        ],
        self::PRIVILEGE_SET_REMOTE_ACCESS_ADMIN => [
            'remote_access_view',
            'remote_access_operate',
            'remote_access_admin',
        ],
        self::PRIVILEGE_SET_SHORT_LINKS_VIEWER => [
            'short_links_view',
        ],
        self::PRIVILEGE_SET_SHORT_LINKS_USER => [
            'short_links_view',
            'short_links_operate',
        ],
    ];

    private const COMPANY_ROLE_ACTIONS = [
        self::COMPANY_OWNER => [
            'view_reports',
            'edit_tags',
            'upload_reports',
            'manage_devices',
            'manage_company_users',
            'manage_company_settings',
            'manage_self',
            'remote_access_view',
            'remote_access_operate',
            'remote_access_admin',
            'short_links_view',
            'short_links_operate',
        ],
        self::COMPANY_ADMIN => [
            'view_reports',
            'edit_tags',
            'upload_reports',
            'manage_devices',
            'manage_company_users',
            'manage_self',
            'remote_access_view',
            'remote_access_operate',
            'remote_access_admin',
            'short_links_view',
            'short_links_operate',
        ],
        self::COMPANY_MEMBER => [
            'view_reports',
            'manage_self',
            'remote_access_view',
        ],
    ];

    public static function normalizeGlobalRole(string $role): string {
        $role = strtolower(trim($role));
        if ($role === self::GLOBAL_ADMIN_LEGACY) {
            return self::GLOBAL_PLATFORM_ADMIN;
        }
        return $role;
    }

    public static function isGlobalAdmin(?array $user = null): bool {
        $user ??= Auth::user();
        if (!$user) {
            return false;
        }
        $role = self::normalizeGlobalRole((string) ($user['role'] ?? ''));
        return in_array($role, self::GLOBAL_ADMIN_ROLES, true);
    }

    /** @return list<int>|null null = all companies (global admin) */
    public static function allowedCompanyIds(?array $user = null): ?array {
        if (self::isGlobalAdmin($user)) {
            return null;
        }
        $user ??= Auth::user();
        if (!$user) {
            return [];
        }
        $ids = [];
        foreach ($user['companies'] ?? [] as $c) {
            if (isset($c['id'])) {
                $ids[] = (int) $c['id'];
            }
        }
        return array_values(array_unique($ids));
    }

    public static function activeCompanyId(?array $user = null): ?int {
        $user ??= Auth::user();
        if (!$user) {
            return null;
        }
        $id = (int) ($user['active_company_id'] ?? 0);
        return $id > 0 ? $id : null;
    }

    public static function companyRole(int $userId, int $companyId): ?string {
        $stmt = Database::pdo()->prepare(
            'SELECT role FROM company_memberships WHERE user_id = ? AND company_id = ? LIMIT 1'
        );
        $stmt->execute([$userId, $companyId]);
        $role = $stmt->fetchColumn();
        return is_string($role) && $role !== '' ? $role : null;
    }

    public static function can(string $action, ?int $companyId = null, ?array $user = null): bool {
        $user ??= Auth::user();
        if (!$user) {
            return false;
        }
        if (self::isGlobalAdmin($user)) {
            return true;
        }
        if ($action === 'manage_platform') {
            return false;
        }
        if ($action === 'manage_self') {
            return true;
        }
        $companyId ??= self::activeCompanyId($user);
        if ($companyId === null || $companyId <= 0) {
            return false;
        }
        $role = self::membershipRoleForUser($user, $companyId);
        if ($role === null) {
            return false;
        }
        $grants = self::COMPANY_ROLE_ACTIONS[$role] ?? [];
        if (in_array($action, $grants, true)) {
            return true;
        }
        return self::hasPermissionOverride($user, $companyId, $action);
    }

    public static function canAccessCompany(int $companyId, ?array $user = null): bool {
        $allowed = self::allowedCompanyIds($user);
        if ($allowed === null) {
            return true;
        }
        return in_array($companyId, $allowed, true);
    }

    public static function canAccessReport(array $report, ?array $user = null): bool {
        $companyId = (int) ($report['company_id'] ?? 0);
        if ($companyId <= 0) {
            return self::isGlobalAdmin($user);
        }
        return self::canAccessCompany($companyId, $user);
    }

    public static function defaultCompanyId(): int {
        static $cached = 0;
        if ($cached > 0) {
            return $cached;
        }
        $fromCfg = (int) cfg('rbac.default_company_id', 0);
        if ($fromCfg > 0) {
            $cached = $fromCfg;
            return $cached;
        }
        $slug = (string) cfg('rbac.default_company_slug', 'default');
        $pdo = Database::pdo();
        $stmt = $pdo->prepare('SELECT id FROM companies WHERE slug = ? LIMIT 1');
        $stmt->execute([$slug]);
        $id = (int) $stmt->fetchColumn();
        if ($id > 0) {
            $cached = $id;
            return $cached;
        }
        $cached = 1;
        return $cached;
    }

    /**
     * Append company scope to WHERE fragments.
     *
     * @param list<string> $where
     * @param list<mixed> $params
     */
    public static function appendCompanyScope(string $column, array &$where, array &$params, ?array $user = null): void {
        $allowed = self::allowedCompanyIds($user);
        if ($allowed === null) {
            return;
        }
        if ($allowed === []) {
            $where[] = '0=1';
            return;
        }
        $placeholders = implode(',', array_fill(0, count($allowed), '?'));
        $where[] = "$column IN ($placeholders)";
        foreach ($allowed as $id) {
            $params[] = $id;
        }
    }

    /** Match fingerprint counts within the same company. */
    public static function fingerprintCountSql(string $rAlias = 'r', string $r2Alias = 'r2'): string {
        return " AND $r2Alias.company_id = $rAlias.company_id";
    }

    /**
     * @return list<array{id:int,slug:string,name:string,role:string}>
     */
    public static function fetchMemberships(int $userId): array {
        $stmt = Database::pdo()->prepare(
            'SELECT c.id, c.slug, c.name, m.role
             FROM company_memberships m
             INNER JOIN companies c ON c.id = m.company_id
             WHERE m.user_id = ?
             ORDER BY c.name ASC'
        );
        $stmt->execute([$userId]);
        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
        $out = [];
        foreach ($rows as $row) {
            $out[] = [
                'id' => (int) $row['id'],
                'slug' => (string) $row['slug'],
                'name' => (string) $row['name'],
                'role' => (string) $row['role'],
            ];
        }
        return $out;
    }

    /**
     * @param list<array{id:int,slug:string,name:string,role:string}> $memberships
     */
    public static function pickActiveCompanyId(array $memberships): int {
        if ($memberships === []) {
            return self::defaultCompanyId();
        }
        return (int) $memberships[0]['id'];
    }

    /** @return array<string, mixed> session user payload */
    public static function buildSessionUser(array $dbUser): array {
        $uid = (int) $dbUser['id'];
        $memberships = self::fetchMemberships($uid);
        return [
            'id' => $uid,
            'username' => (string) $dbUser['username'],
            'role' => self::normalizeGlobalRole((string) ($dbUser['role'] ?? self::GLOBAL_VIEWER)),
            'companies' => $memberships,
            'active_company_id' => self::pickActiveCompanyId($memberships),
        ];
    }

    public static function seedMembershipsForUser(int $userId, string $globalRole): void {
        $pdo = Database::pdo();
        $companyId = self::defaultCompanyId();
        $globalRole = self::normalizeGlobalRole($globalRole);
        $companyRole = self::COMPANY_MEMBER;
        if (in_array($globalRole, self::GLOBAL_ADMIN_ROLES, true)) {
            $companyRole = self::COMPANY_OWNER;
        }
        if (Database::isMysql()) {
            $sql = 'INSERT IGNORE INTO company_memberships (user_id, company_id, role) VALUES (?, ?, ?)';
        } else {
            $sql = 'INSERT OR IGNORE INTO company_memberships (user_id, company_id, role) VALUES (?, ?, ?)';
        }
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$userId, $companyId, $companyRole]);
    }

    private static function membershipRoleForUser(array $user, int $companyId): ?string {
        foreach ($user['companies'] ?? [] as $c) {
            if ((int) ($c['id'] ?? 0) === $companyId) {
                return (string) ($c['role'] ?? '');
            }
        }
        return null;
    }

    /** @return list<string> */
    public static function privilegeSetKeys(): array {
        return array_keys(self::PRIVILEGE_SETS);
    }

    /** @return list<string> */
    public static function grantsForPrivilegeSet(string $setKey): array {
        $setKey = trim($setKey);
        if ($setKey === '' || $setKey === self::PRIVILEGE_SET_NONE) {
            return [];
        }
        return self::PRIVILEGE_SETS[$setKey] ?? [];
    }

    public static function encodePermissionsJson(?string $privilegeSetKey): ?string {
        $grants = self::grantsForPrivilegeSet((string) $privilegeSetKey);
        if ($grants === []) {
            return null;
        }
        return json_encode(['grants' => $grants], JSON_UNESCAPED_UNICODE);
    }

    public static function privilegeSetFromPermissionsJson(?string $raw): string {
        if (!is_string($raw) || trim($raw) === '') {
            return self::PRIVILEGE_SET_NONE;
        }
        $decoded = json_decode($raw, true);
        if (!is_array($decoded)) {
            return self::PRIVILEGE_SET_NONE;
        }
        $grants = $decoded['grants'] ?? $decoded;
        if (!is_array($grants)) {
            return self::PRIVILEGE_SET_NONE;
        }
        sort($grants);
        foreach (self::PRIVILEGE_SETS as $key => $want) {
            $sorted = $want;
            sort($sorted);
            if ($grants === $sorted) {
                return $key;
            }
        }
        return 'custom';
    }

    public static function isRoot(?array $user = null): bool {
        $user ??= Auth::user();
        if (!$user) {
            return false;
        }
        return self::normalizeGlobalRole((string) ($user['role'] ?? '')) === self::GLOBAL_ROOT;
    }

    public static function canManageRbac(?array $user = null): bool {
        $user ??= Auth::user();
        if (!$user) {
            return false;
        }
        if (self::isGlobalAdmin($user)) {
            return true;
        }
        foreach ($user['companies'] ?? [] as $c) {
            $role = (string) ($c['role'] ?? '');
            if ($role === self::COMPANY_OWNER || $role === self::COMPANY_ADMIN) {
                return true;
            }
        }
        return false;
    }

    private static function hasPermissionOverride(array $user, int $companyId, string $action): bool {
        $pdo = Database::pdo();
        $stmt = $pdo->prepare(
            'SELECT permissions_json FROM company_memberships WHERE user_id = ? AND company_id = ? LIMIT 1'
        );
        $stmt->execute([(int) $user['id'], $companyId]);
        $raw = $stmt->fetchColumn();
        if (!is_string($raw) || $raw === '') {
            return false;
        }
        $decoded = json_decode($raw, true);
        if (!is_array($decoded)) {
            return false;
        }
        $grants = $decoded['grants'] ?? $decoded;
        if (!is_array($grants)) {
            return false;
        }
        return in_array($action, $grants, true);
    }
}
