<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Core\AccessControl;
use App\Core\Auth;
use App\Core\Csrf;
use App\Core\Request;
use App\Core\Session;
use App\Core\View;
use App\Repositories\AccessControlRepository;
use Throwable;

final class AccessControlController
{
    public function index(): void
    {
        AccessControl::requirePermission('security.access_control.manage');

        $repository = new AccessControlRepository();
        $schemaReady = false;
        $roleManagementReady = false;
        $roles = [];
        $roleOverrides = [];
        $users = [];
        $assignments = [];
        $accessCounts = [
            'site_admins' => 0,
            'general_managers' => 0,
            'configured_users' => 0,
            'active_roles' => 0,
            'custom_roles' => 0,
        ];
        $audit = [];
        $error = '';

        try {
            $schemaReady = $repository->schemaReady();

            if ($schemaReady) {
                $roleManagementReady = $repository->roleManagementReady();
                $roles = $repository->listRoles();
                $roleOverrides = $repository->allRoleOverrides();
                $users = $repository->listDashboardUsers();
                $assignments = $repository->userAssignments();
                $accessCounts = $repository->accessCounts();
                $audit = $repository->recentAudit(30);
            }
        } catch (Throwable $errorObject) {
            $error = $errorObject->getMessage();
            error_log('[AccessControlController] ' . $error);
        }

        View::render('access_control/index', [
            'title' => current_lang() === 'ar'
                ? 'إدارة المستخدمين والأدوار'
                : 'Users and Roles',
            'activePage' => 'access-control',
            'schemaReady' => $schemaReady,
            'roleManagementReady' => $roleManagementReady,
            'roles' => $roles,
            'roleOverrides' => $roleOverrides,
            'users' => $users,
            'assignments' => $assignments,
            'permissions' => AccessControl::permissions(),
            'accessCounts' => $accessCounts,
            'audit' => $audit,
            'error' => $error,
            'currentUsername' => Auth::name(),
            'successMessage' => Session::flash('acl_success'),
            'errorMessage' => Session::flash('acl_error'),
        ]);
    }

    public function save(): void
    {
        AccessControl::requirePermission('security.access_control.manage');

        if (!Csrf::validate((string)Request::post('_csrf', ''))) {
            Session::flash(
                'acl_error',
                current_lang() === 'ar'
                    ? 'انتهت صلاحية الجلسة.'
                    : 'Session expired.'
            );
            $this->redirect('users');
        }

        $action = trim((string)Request::post('acl_action', ''));
        $repository = new AccessControlRepository();

        try {
            if (!$repository->schemaReady()) {
                throw new \RuntimeException('ACCESS_CONTROL_SCHEMA_NOT_READY');
            }

            if ($action === 'save_user_access') {
                $this->saveUserAccess($repository);
                AccessControl::refresh();
                $this->redirect('users');
            }

            if ($action === 'save_role') {
                $roleCode = $this->saveRole($repository);
                AccessControl::refresh();
                $this->redirect('roles', ['role' => $roleCode]);
            }

            throw new \RuntimeException('INVALID_ACCESS_CONTROL_ACTION');
        } catch (Throwable $errorObject) {
            error_log('[AccessControlSave] ' . $errorObject->getMessage());

            Session::flash(
                'acl_error',
                $this->friendlyMessage($errorObject->getMessage())
            );

            $section = $action === 'save_role' ? 'roles' : 'users';
            $query = [];

            if ($section === 'roles') {
                $roleCode = strtoupper(trim((string)Request::post('role_code', '')));
                if ($roleCode !== '') {
                    $query['role'] = $roleCode;
                }
            }

            $this->redirect($section, $query);
        }
    }

    private function saveUserAccess(AccessControlRepository $repository): void
    {
        $username = trim((string)Request::post('username', ''));
        $roleCode = strtoupper(trim((string)Request::post('role_code', '')));
        $branchMode = strtoupper(trim((string)Request::post('branch_mode', 'ALL')));
        $branches = Request::post('branches', []);

        $selectedBranches = [];

        if ($roleCode !== '' && $roleCode !== 'SITE_ADMIN') {
            if ($branchMode === 'ALL') {
                $selectedBranches = ['*'];
            } elseif ($branchMode === 'SELECTED') {
                $selectedBranches = is_array($branches) ? $branches : [];

                if ($selectedBranches === []) {
                    throw new \RuntimeException('SELECT_AT_LEAST_ONE_BRANCH');
                }
            } else {
                throw new \RuntimeException('INVALID_BRANCH_MODE');
            }
        }

        $repository->replaceUserAccess(
            $username,
            $roleCode,
            $selectedBranches,
            Auth::name()
        );

        Session::flash(
            'acl_success',
            current_lang() === 'ar'
                ? 'تم حفظ دور المستخدم ونطاق الفروع.'
                : 'The user role and branch access were saved.'
        );
    }

    private function saveRole(AccessControlRepository $repository): string
    {
        if (!$repository->roleManagementReady()) {
            throw new \RuntimeException('ROLE_MANAGEMENT_UPGRADE_REQUIRED');
        }

        $isNew = (string)Request::post('is_new_role', '0') === '1';
        $roleCode = strtoupper(trim((string)Request::post('role_code', '')));
        $nameEn = trim((string)Request::post('role_name_en', ''));
        $nameAr = trim((string)Request::post('role_name_ar', ''));

        if ($isNew && $roleCode === '') {
            $roleCode = $this->generateRoleCode($nameEn);
        }

        $policyMode = strtoupper(trim((string)Request::post(
            'policy_mode',
            'EXPLICIT'
        )));

        $rawAllowed = Request::post('role_allowed', []);
        $allowed = is_array($rawAllowed) ? $rawAllowed : [];
        $effects = $this->permissionEffects($policyMode, $allowed);

        $repository->saveRole(
            $roleCode,
            $nameEn,
            $nameAr,
            trim((string)Request::post('role_description_en', '')),
            trim((string)Request::post('role_description_ar', '')),
            $policyMode,
            (string)Request::post('all_branches', '0') === '1',
            (string)Request::post('is_active', '1') === '1',
            $effects,
            Auth::name(),
            $isNew
        );

        Session::flash(
            'acl_success',
            current_lang() === 'ar'
                ? ($isNew ? 'تم إنشاء الدور الجديد.' : 'تم تحديث الدور.')
                : ($isNew ? 'The new role was created.' : 'The role was updated.')
        );

        return $roleCode;
    }

    /**
     * @param array<string,mixed> $allowed
     * @return array<string,string>
     */
    private function permissionEffects(string $policyMode, array $allowed): array
    {
        $permissions = AccessControl::permissions();
        $businessCategories = (array)config(
            'access_control.business_categories',
            ['business', 'reporting', 'workflow']
        );
        $effects = [];

        foreach ($permissions as $permissionCode => $contract) {
            $category = (string)($contract['category'] ?? 'business');
            $baseAllowed = $policyMode === 'ALL_BUSINESS'
                && in_array($category, $businessCategories, true);
            $desiredAllowed = array_key_exists($permissionCode, $allowed);

            if ($desiredAllowed !== $baseAllowed) {
                $effects[$permissionCode] = $desiredAllowed ? 'ALLOW' : 'DENY';
            }
        }

        return $effects;
    }

    private function generateRoleCode(string $name): string
    {
        $name = strtoupper(trim($name));
        $name = preg_replace('/[^A-Z0-9]+/', '_', $name);
        $name = trim((string)$name, '_');

        if ($name === '') {
            $name = 'CUSTOM_ROLE_' . date('YmdHis');
        }

        if (!preg_match('/^[A-Z]/', $name)) {
            $name = 'ROLE_' . $name;
        }

        return substr($name, 0, 50);
    }

    private function friendlyMessage(string $key): string
    {
        $messages = [
            'ACCESS_CONTROL_SCHEMA_NOT_READY' => [
                'The access-control schema is not installed.',
                'لم يتم تثبيت مخطط الصلاحيات.',
            ],
            'ROLE_MANAGEMENT_UPGRADE_REQUIRED' => [
                'Run the Role Management schema upgrade first.',
                'شغّل ترقية مخطط إدارة الأدوار أولًا.',
            ],
            'USER_REQUIRED' => [
                'Select a user first.',
                'اختر مستخدمًا أولًا.',
            ],
            'DASHBOARD_USER_NOT_FOUND' => [
                'The selected dashboard user was not found.',
                'لم يتم العثور على المستخدم المحدد.',
            ],
            'ROLE_NOT_AVAILABLE' => [
                'The selected role is not active or no longer exists.',
                'الدور المحدد غير فعال أو لم يعد موجودًا.',
            ],
            'ROLE_BRANCH_SCOPE_REQUIRED' => [
                'Choose all active branches or select at least one branch.',
                'اختر جميع الفروع النشطة أو فرعًا واحدًا على الأقل.',
            ],
            'SELECT_AT_LEAST_ONE_BRANCH' => [
                'Select at least one branch.',
                'اختر فرعًا واحدًا على الأقل.',
            ],
            'AT_LEAST_ONE_SITE_ADMIN_REQUIRED' => [
                'The system must keep at least one Site Administrator.',
                'يجب أن يبقى مدير موقع واحد على الأقل.',
            ],
            'INVALID_ROLE_CODE' => [
                'Use an English role code with letters, numbers and underscores.',
                'استخدم رمز دور إنجليزيًا من الحروف والأرقام والشرطة السفلية.',
            ],
            'ROLE_NAME_REQUIRED' => [
                'Enter the English role name.',
                'أدخل اسم الدور باللغة الإنجليزية.',
            ],
            'ROLE_CODE_ALREADY_EXISTS' => [
                'This role code already exists.',
                'رمز الدور مستخدم بالفعل.',
            ],
            'PROTECTED_ROLE_CODE_RESERVED' => [
                'This role code is reserved for a protected system role.',
                'رمز الدور محجوز لدور نظام محمي.',
            ],
            'ROLE_NOT_FOUND' => [
                'The selected role was not found.',
                'لم يتم العثور على الدور المحدد.',
            ],
            'SITE_ADMIN_POLICY_IS_IMMUTABLE' => [
                'Site Administrator is protected and cannot be reduced.',
                'دور مدير الموقع محمي ولا يمكن تقليل صلاحياته.',
            ],
            'ROLE_HAS_ASSIGNED_USERS' => [
                'Move users to another role before deactivating this role.',
                'انقل المستخدمين إلى دور آخر قبل إيقاف هذا الدور.',
            ],
            'INVALID_ROLE_POLICY' => [
                'The selected role access template is invalid.',
                'نموذج صلاحيات الدور غير صحيح.',
            ],
        ];

        $pair = $messages[$key] ?? [
            'The change was not saved.',
            'لم يتم حفظ التغيير.',
        ];

        return current_lang() === 'ar' ? $pair[1] : $pair[0];
    }

    /**
     * @param array<string,string> $query
     */
    private function redirect(string $section, array $query = []): void
    {
        $url = 'index.php?page=access-control&lang=' . current_lang();

        foreach ($query as $key => $value) {
            $url .= '&' . rawurlencode($key) . '=' . rawurlencode($value);
        }

        $url .= '#access-' . $section;
        \redirect($url);
    }
}
