<?php

namespace App\Support;

use App\Models\AccessGroup;
use App\Models\User;

class AccessControl
{
    /**
     * @var array<string, array<string, array<string, bool>>>|null
     */
    private static ?array $defaultMatrices = null;

    public const ROLE_ADMIN = 'admin';

    public const ROLE_MODERATOR = 'moderator';

    public const ROLE_USER = 'user';

    public const ROLE_UNKNOWN = '__unknown__';

    /**
     * @var array<string, string>
     */
    private const LEGACY_ROLE_MAP = [
        'manager' => self::ROLE_MODERATOR,
        'sales' => self::ROLE_USER,
    ];

    /**
     * @var array<string, string>
     */
    private const ROLE_LABELS = [
        self::ROLE_ADMIN => 'Admin',
        self::ROLE_MODERATOR => 'Moderator',
        self::ROLE_USER => 'User',
    ];

    /**
     * @var array<string, string>
     */
    private const ACTION_LABELS = [
        'read' => 'Reading',
        'create' => 'Creation',
        'update' => 'Change',
        'delete' => 'Removal',
    ];

    /**
     * @var array<string, string>
     */
    private const ENTITY_LABELS = [
        'tasks' => 'Tasks',
        'deals' => 'Transactions',
        'products' => 'Products',
        'warehouses' => 'Warehouses',
        'hr' => 'HR Service',
        'onec' => '1C Integration',
        'telephony' => 'Telephony',
        'messengers' => 'Messengers',
        'mail' => 'Mail Service',
        'updates' => 'Updates',
        'companies' => 'Companies',
        'contacts' => 'Contacts',
        'projects' => 'Projects',
        'forms' => 'Forms',
        'disks' => 'Disk',
        'calendar' => 'Calendar',
        'activities' => 'Activities',
        'pipelines' => 'Funnels',
        'reports' => 'Reports',
        'api' => 'API',
    ];

    /**
     * @return array<string, string>
     */
    public static function roles(): array
    {
        return self::ROLE_LABELS;
    }

    /**
     * @return array<string, string>
     */
    public static function actions(): array
    {
        return self::ACTION_LABELS;
    }

    /**
     * @return array<string, string>
     */
    public static function entities(): array
    {
        return self::ENTITY_LABELS;
    }

    public static function normalizeRole(?string $role): string
    {
        $rawRole = trim((string) $role);
        if ($rawRole === '') {
            return self::ROLE_USER;
        }

        if (isset(self::LEGACY_ROLE_MAP[$rawRole])) {
            return self::LEGACY_ROLE_MAP[$rawRole];
        }

        if (isset(self::ROLE_LABELS[$rawRole])) {
            return $rawRole;
        }

        return self::ROLE_UNKNOWN;
    }

    public static function isAdministrator(User $user): bool
    {
        return self::normalizeRole((string) $user->role) === self::ROLE_ADMIN;
    }

    public static function isElevated(User $user): bool
    {
        return in_array(self::normalizeRole((string) $user->role), [self::ROLE_ADMIN, self::ROLE_MODERATOR], true);
    }

    public static function canManageAccess(User $user): bool
    {
        return self::isAdministrator($user);
    }

    public static function permissionKey(string $entity, string $action): string
    {
        return $entity.'.'.$action;
    }

    public static function permissionState(User $user, string $entity, string $action): ?bool
    {
        if (!isset(self::ENTITY_LABELS[$entity]) || !isset(self::ACTION_LABELS[$action])) {
            return null;
        }

        $permissions = is_array($user->permissions) ? $user->permissions : [];
        $key = self::permissionKey($entity, $action);

        if (!array_key_exists($key, $permissions)) {
            return null;
        }

        return (bool) $permissions[$key];
    }

    public static function overridePermissionState(User $user, string $entity, string $action): ?bool
    {
        $state = self::permissionState($user, $entity, $action);
        if ($state !== null) {
            return $state;
        }

        return self::groupPermissionState($user, $entity, $action);
    }

    public static function roleAllows(User $user, string $entity, string $action): bool
    {
        if (!isset(self::ENTITY_LABELS[$entity]) || !isset(self::ACTION_LABELS[$action])) {
            return false;
        }

        return (bool) (self::defaultMatrixForRole(self::normalizeRole((string) $user->role))[$entity][$action] ?? false);
    }

    public static function allows(User $user, string $entity, string $action): bool
    {
        $state = self::overridePermissionState($user, $entity, $action);
        if ($state !== null) {
            return $state;
        }

        return self::roleAllows($user, $entity, $action);
    }

    public static function groupPermissionState(User $user, string $entity, string $action): ?bool
    {
        if (! isset(self::ENTITY_LABELS[$entity]) || ! isset(self::ACTION_LABELS[$action])) {
            return null;
        }

        $permissions = self::groupPermissions($user);
        $key = self::permissionKey($entity, $action);

        if (! array_key_exists($key, $permissions)) {
            return null;
        }

        return (bool) $permissions[$key];
    }

    /**
     * @return array<string, array<string, bool>>
     */
    public static function resolvedPermissions(User $user): array
    {
        $resolved = [];

        foreach (array_keys(self::ENTITY_LABELS) as $entity) {
            foreach (array_keys(self::ACTION_LABELS) as $action) {
                $resolved[$entity][$action] = self::allows($user, $entity, $action);
            }
        }

        return $resolved;
    }

    /**
     * @param  array<string, mixed>  $matrixInput
     * @return array<string, bool>
     */
    public static function flattenPermissionMatrix(array $matrixInput): array
    {
        $flattened = [];
        foreach (array_keys(self::ENTITY_LABELS) as $entity) {
            foreach (array_keys(self::ACTION_LABELS) as $action) {
                $flattened[self::permissionKey($entity, $action)] = (bool) ($matrixInput[$entity][$action] ?? false);
            }
        }

        return $flattened;
    }

    /**
     * @return array<string, mixed>
     */
    private static function groupPermissions(User $user): array
    {
        $groupId = (int) ($user->access_group_id ?? 0);
        if ($groupId <= 0) {
            return [];
        }

        $user->loadMissing('accessGroup:id,permissions');

        if ($user->relationLoaded('accessGroup') && $user->accessGroup instanceof AccessGroup) {
            return is_array($user->accessGroup->permissions) ? $user->accessGroup->permissions : [];
        }

        $group = AccessGroup::query()->find($groupId, ['id', 'permissions']);

        return $group && is_array($group->permissions) ? $group->permissions : [];
    }

    /**
     * @return array<string, array<string, bool>>
     */
    private static function defaultMatrixForRole(string $role): array
    {
        $matrices = self::defaultMatrices();

        return match ($role) {
            self::ROLE_ADMIN => $matrices[self::ROLE_ADMIN],
            self::ROLE_MODERATOR => $matrices[self::ROLE_MODERATOR],
            self::ROLE_USER => $matrices[self::ROLE_USER],
            default => $matrices[self::ROLE_UNKNOWN],
        };
    }

    /**
     * @return array<string, array<string, array<string, bool>>>
     */
    private static function defaultMatrices(): array
    {
        if (self::$defaultMatrices !== null) {
            return self::$defaultMatrices;
        }

        $allTrue = [];
        $userDefaults = [];
        $moderatorDefaults = [];

        foreach (array_keys(self::ENTITY_LABELS) as $entity) {
            $allTrue[$entity] = [];
            $userDefaults[$entity] = [];
            $moderatorDefaults[$entity] = [];
            foreach (array_keys(self::ACTION_LABELS) as $action) {
                $allTrue[$entity][$action] = true;
                $userDefaults[$entity][$action] = false;
                $moderatorDefaults[$entity][$action] = false;
            }
        }

        $deniedDefaults = $userDefaults;

        foreach (['tasks', 'deals', 'products', 'warehouses', 'hr', 'onec', 'telephony', 'messengers', 'mail', 'companies', 'contacts', 'projects', 'forms', 'disks', 'activities'] as $entity) {
            foreach (['read', 'create', 'update', 'delete'] as $action) {
                $moderatorDefaults[$entity][$action] = true;
            }
        }

        foreach (['read', 'create', 'update', 'delete'] as $action) {
            $userDefaults['tasks'][$action] = true;
        }

        $moderatorDefaults['calendar']['read'] = true;

        foreach (['read', 'create', 'update', 'delete'] as $action) {
            $moderatorDefaults['pipelines'][$action] = true;
        }

        $moderatorDefaults['reports']['read'] = true;
        $moderatorDefaults['api']['read'] = true;

        self::$defaultMatrices = [
            self::ROLE_ADMIN => $allTrue,
            self::ROLE_MODERATOR => $moderatorDefaults,
            self::ROLE_USER => $userDefaults,
            self::ROLE_UNKNOWN => $deniedDefaults,
        ];

        return self::$defaultMatrices;
    }
}
