<?php

namespace App\Services\Authz;

use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;

class RolePermissionService
{
    protected static string $guard = 'api';

    /** 현재 테넌트 컨텍스트로 팀 고정 */
    protected static function setTeam(int $tenantId): void
    {
        app(PermissionRegistrar::class)->setPermissionsTeamId($tenantId);
    }

    /** 역할 로드 (테넌트/가드 검증) */
    protected static function loadRoleOrError(int $roleId, int $tenantId): ?Role
    {
        $role = Role::query()
            ->where('tenant_id', $tenantId)
            ->where('guard_name', self::$guard)
            ->find($roleId);

        return $role;
    }

    /** A) permission_names[] → 그대로 사용
     *  B) menus[] + actions[] → "menu:{id}.{act}" 배열로 변환(필요 시 Permission 생성)
     */
    protected static function resolvePermissionNames(int $tenantId, array $params): array
    {
        $names = [];

        if (! empty($params['permission_names']) && is_array($params['permission_names'])) {
            // 문자열 배열만 추림
            foreach ($params['permission_names'] as $n) {
                if (is_string($n) && $n !== '') {
                    $names[] = trim($n);
                }
            }
        }

        if (! empty($params['menus']) && is_array($params['menus']) &&
            ! empty($params['actions']) && is_array($params['actions'])) {

            $allowed = config('authz.menu_actions', ['view', 'create', 'update', 'delete', 'approve']);
            $acts = array_values(array_unique(array_filter(array_map('trim', $params['actions']))));
            $acts = array_intersect($acts, $allowed);

            $menuIds = array_values(array_unique(array_map('intval', $params['menus'])));

            foreach ($menuIds as $mid) {
                foreach ($acts as $act) {
                    $names[] = "menu:{$mid}.{$act}";
                }
            }
        }

        // 빈/중복 제거
        $names = array_values(array_unique(array_filter($names)));

        // 존재하지 않는 Permission은 생성(tenant+guard 포함)
        foreach ($names as $permName) {
            Permission::firstOrCreate([
                'tenant_id' => $tenantId,
                'guard_name' => self::$guard,
                'name' => $permName,
            ]);
        }

        return $names;
    }

    /** 역할의 퍼미션 목록 */
    public static function list(int $roleId)
    {
        $tenantId = (int) app('tenant_id');

        $role = self::loadRoleOrError($roleId, $tenantId);
        if (! $role) {
            return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
        }

        self::setTeam($tenantId);

        $perms = $role->permissions()
            ->where('tenant_id', $tenantId)
            ->where('guard_name', self::$guard)
            ->orderBy('name')
            ->get(['id', 'tenant_id', 'name', 'guard_name', 'created_at', 'updated_at']);

        return $perms;
    }

    /** 부여 (중복 무시) */
    public static function grant(int $roleId, array $params = [])
    {
        $tenantId = (int) app('tenant_id');

        $role = self::loadRoleOrError($roleId, $tenantId);
        if (! $role) {
            return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
        }

        // 유효성: 두 방식 중 하나만 요구하진 않지만, 최소 하나는 있어야 함
        $v = Validator::make($params, [
            'permission_names' => 'sometimes|array',
            'permission_names.*' => 'string|min:1',
            'menus' => 'sometimes|array',
            'menus.*' => 'integer|min:1',
            'actions' => 'sometimes|array',
            'actions.*' => [
                'string', Rule::in(config('authz.menu_actions', ['view', 'create', 'update', 'delete', 'approve'])),
            ],
        ]);
        if ($v->fails()) {
            return ['error' => $v->errors()->first(), 'code' => 422];
        }
        if (empty($params['permission_names']) && (empty($params['menus']) || empty($params['actions']))) {
            return ['error' => 'permission_names 또는 menus+actions 중 하나는 필요합니다.', 'code' => 422];
        }

        self::setTeam($tenantId);

        $names = self::resolvePermissionNames($tenantId, $params);
        if (empty($names)) {
            return ['error' => '유효한 퍼미션이 없습니다.', 'code' => 422];
        }

        // Spatie: 이름 배열 부여 OK (teams 컨텍스트 적용됨)
        $role->givePermissionTo($names);

        return 'success';
    }

    /** 회수 (없는 건 무시) */
    public static function revoke(int $roleId, array $params = [])
    {
        $tenantId = (int) app('tenant_id');

        $role = self::loadRoleOrError($roleId, $tenantId);
        if (! $role) {
            return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
        }

        $v = Validator::make($params, [
            'permission_names' => 'sometimes|array',
            'permission_names.*' => 'string|min:1',
            'menus' => 'sometimes|array',
            'menus.*' => 'integer|min:1',
            'actions' => 'sometimes|array',
            'actions.*' => [
                'string', Rule::in(config('authz.menu_actions', ['view', 'create', 'update', 'delete', 'approve'])),
            ],
        ]);
        if ($v->fails()) {
            return ['error' => $v->errors()->first(), 'code' => 422];
        }
        if (empty($params['permission_names']) && (empty($params['menus']) || empty($params['actions']))) {
            return ['error' => 'permission_names 또는 menus+actions 중 하나는 필요합니다.', 'code' => 422];
        }

        self::setTeam($tenantId);

        $names = self::resolvePermissionNames($tenantId, $params);
        if (empty($names)) {
            return ['error' => '유효한 퍼미션이 없습니다.', 'code' => 422];
        }

        $role->revokePermissionTo($names);

        return 'success';
    }

    /** 동기화(완전 교체) */
    public static function sync(int $roleId, array $params = [])
    {
        $tenantId = (int) app('tenant_id');

        $role = self::loadRoleOrError($roleId, $tenantId);
        if (! $role) {
            return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
        }

        $v = Validator::make($params, [
            'permission_names' => 'sometimes|array',
            'permission_names.*' => 'string|min:1',
            'menus' => 'sometimes|array',
            'menus.*' => 'integer|min:1',
            'actions' => 'sometimes|array',
            'actions.*' => [
                'string', Rule::in(config('authz.menu_actions', ['view', 'create', 'update', 'delete', 'approve'])),
            ],
        ]);
        if ($v->fails()) {
            return ['error' => $v->errors()->first(), 'code' => 422];
        }
        if (empty($params['permission_names']) && (empty($params['menus']) || empty($params['actions']))) {
            return ['error' => 'permission_names 또는 menus+actions 중 하나는 필요합니다.', 'code' => 422];
        }

        self::setTeam($tenantId);

        $names = self::resolvePermissionNames($tenantId, $params); // 존재하지 않으면 생성
        // 동기화
        $role->syncPermissions($names);

        return 'success';
    }

    /** 권한 유형 목록 */
    protected static function getPermissionTypes(): array
    {
        return config('authz.menu_actions', ['view', 'create', 'update', 'delete', 'approve', 'export', 'manage']);
    }

    /** 역할의 권한 매트릭스 조회 */
    public static function matrix(int $roleId)
    {
        $tenantId = (int) app('tenant_id');

        $role = self::loadRoleOrError($roleId, $tenantId);
        if (! $role) {
            return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
        }

        self::setTeam($tenantId);

        // 역할에 부여된 권한 조회
        $rolePermissions = \Illuminate\Support\Facades\DB::table('role_has_permissions')
            ->join('permissions', 'role_has_permissions.permission_id', '=', 'permissions.id')
            ->where('role_has_permissions.role_id', $roleId)
            ->where('permissions.guard_name', self::$guard)
            ->where('permissions.name', 'like', 'menu:%')
            ->pluck('permissions.name')
            ->toArray();

        $permissions = [];
        foreach ($rolePermissions as $permName) {
            if (preg_match('/^menu:(\d+)\.(\w+)$/', $permName, $matches)) {
                $menuId = (int) $matches[1];
                $type = $matches[2];

                if (! isset($permissions[$menuId])) {
                    $permissions[$menuId] = [];
                }

                $permissions[$menuId][$type] = true;
            }
        }

        return [
            'role' => [
                'id' => $role->id,
                'name' => $role->name,
                'description' => $role->description,
            ],
            'permission_types' => self::getPermissionTypes(),
            'permissions' => $permissions,
        ];
    }

    /** 메뉴 트리 조회 (권한 매트릭스 표시용) */
    public static function menus()
    {
        $tenantId = (int) app('tenant_id');

        $menus = \App\Models\Commons\Menu::where('tenant_id', $tenantId)
            ->where('is_active', true)
            ->orderBy('sort_order', 'asc')
            ->orderBy('id', 'asc')
            ->get(['id', 'parent_id', 'name', 'url', 'icon', 'sort_order', 'is_active']);

        // 트리 구조를 플랫한 배열로 변환 (depth 정보 포함)
        $flatMenus = self::flattenMenuTree($menus->toArray(), null, 0);

        return [
            'menus' => $flatMenus,
            'permission_types' => self::getPermissionTypes(),
        ];
    }

    /** 트리 구조를 플랫한 배열로 변환 (depth 정보 포함) */
    protected static function flattenMenuTree(array $menus, ?int $parentId = null, int $depth = 0): array
    {
        $result = [];

        $filteredMenus = array_filter($menus, fn ($m) => $m['parent_id'] === $parentId);
        usort($filteredMenus, fn ($a, $b) => ($a['sort_order'] ?? 0) <=> ($b['sort_order'] ?? 0));

        foreach ($filteredMenus as $menu) {
            $menu['depth'] = $depth;
            $menu['has_children'] = count(array_filter($menus, fn ($m) => $m['parent_id'] === $menu['id'])) > 0;
            $result[] = $menu;

            // 자식 메뉴 재귀적으로 추가
            $children = self::flattenMenuTree($menus, $menu['id'], $depth + 1);
            $result = array_merge($result, $children);
        }

        return $result;
    }

    /** 특정 메뉴의 특정 권한 토글 */
    public static function toggle(int $roleId, array $params = [])
    {
        $tenantId = (int) app('tenant_id');

        $role = self::loadRoleOrError($roleId, $tenantId);
        if (! $role) {
            return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
        }

        $v = Validator::make($params, [
            'menu_id' => 'required|integer|min:1',
            'permission_type' => ['required', 'string', Rule::in(self::getPermissionTypes())],
        ]);
        if ($v->fails()) {
            return ['error' => $v->errors()->first(), 'code' => 422];
        }

        $menuId = (int) $params['menu_id'];
        $permissionType = $params['permission_type'];

        self::setTeam($tenantId);

        $permissionName = "menu:{$menuId}.{$permissionType}";

        // 권한 생성 또는 조회
        $permission = Permission::firstOrCreate([
            'name' => $permissionName,
            'guard_name' => self::$guard,
            'tenant_id' => $tenantId,
        ]);

        // 현재 권한 상태 확인
        $exists = \Illuminate\Support\Facades\DB::table('role_has_permissions')
            ->where('role_id', $roleId)
            ->where('permission_id', $permission->id)
            ->exists();

        if ($exists) {
            // 권한 제거
            \Illuminate\Support\Facades\DB::table('role_has_permissions')
                ->where('role_id', $roleId)
                ->where('permission_id', $permission->id)
                ->delete();
            $newValue = false;
        } else {
            // 권한 부여
            \Illuminate\Support\Facades\DB::table('role_has_permissions')->insert([
                'role_id' => $roleId,
                'permission_id' => $permission->id,
            ]);
            $newValue = true;
        }

        // 하위 메뉴에 권한 전파
        self::propagateToChildren($roleId, $menuId, $permissionType, $newValue, $tenantId);

        return [
            'menu_id' => $menuId,
            'permission_type' => $permissionType,
            'granted' => $newValue,
        ];
    }

    /** 하위 메뉴에 권한 전파 */
    protected static function propagateToChildren(int $roleId, int $parentMenuId, string $permissionType, bool $value, int $tenantId): void
    {
        $children = \App\Models\Commons\Menu::where('parent_id', $parentMenuId)
            ->where('tenant_id', $tenantId)
            ->get();

        foreach ($children as $child) {
            $permissionName = "menu:{$child->id}.{$permissionType}";
            $permission = Permission::firstOrCreate([
                'name' => $permissionName,
                'guard_name' => self::$guard,
                'tenant_id' => $tenantId,
            ]);

            if ($value) {
                // 권한 부여
                $exists = \Illuminate\Support\Facades\DB::table('role_has_permissions')
                    ->where('role_id', $roleId)
                    ->where('permission_id', $permission->id)
                    ->exists();

                if (! $exists) {
                    \Illuminate\Support\Facades\DB::table('role_has_permissions')->insert([
                        'role_id' => $roleId,
                        'permission_id' => $permission->id,
                    ]);
                }
            } else {
                // 권한 제거
                \Illuminate\Support\Facades\DB::table('role_has_permissions')
                    ->where('role_id', $roleId)
                    ->where('permission_id', $permission->id)
                    ->delete();
            }

            // 재귀적으로 하위 메뉴 처리
            self::propagateToChildren($roleId, $child->id, $permissionType, $value, $tenantId);
        }
    }

    /** 모든 권한 허용 */
    public static function allowAll(int $roleId)
    {
        $tenantId = (int) app('tenant_id');

        $role = self::loadRoleOrError($roleId, $tenantId);
        if (! $role) {
            return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
        }

        self::setTeam($tenantId);

        $menus = \App\Models\Commons\Menu::where('tenant_id', $tenantId)
            ->where('is_active', true)
            ->get();

        $permissionTypes = self::getPermissionTypes();

        foreach ($menus as $menu) {
            foreach ($permissionTypes as $type) {
                $permissionName = "menu:{$menu->id}.{$type}";
                $permission = Permission::firstOrCreate([
                    'name' => $permissionName,
                    'guard_name' => self::$guard,
                    'tenant_id' => $tenantId,
                ]);

                // 권한 부여
                $exists = \Illuminate\Support\Facades\DB::table('role_has_permissions')
                    ->where('role_id', $roleId)
                    ->where('permission_id', $permission->id)
                    ->exists();

                if (! $exists) {
                    \Illuminate\Support\Facades\DB::table('role_has_permissions')->insert([
                        'role_id' => $roleId,
                        'permission_id' => $permission->id,
                    ]);
                }
            }
        }

        return 'success';
    }

    /** 모든 권한 거부 */
    public static function denyAll(int $roleId)
    {
        $tenantId = (int) app('tenant_id');

        $role = self::loadRoleOrError($roleId, $tenantId);
        if (! $role) {
            return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
        }

        self::setTeam($tenantId);

        $menus = \App\Models\Commons\Menu::where('tenant_id', $tenantId)
            ->where('is_active', true)
            ->get();

        $permissionTypes = self::getPermissionTypes();

        foreach ($menus as $menu) {
            foreach ($permissionTypes as $type) {
                $permissionName = "menu:{$menu->id}.{$type}";
                $permission = Permission::where('name', $permissionName)
                    ->where('guard_name', self::$guard)
                    ->where('tenant_id', $tenantId)
                    ->first();

                if ($permission) {
                    \Illuminate\Support\Facades\DB::table('role_has_permissions')
                        ->where('role_id', $roleId)
                        ->where('permission_id', $permission->id)
                        ->delete();
                }
            }
        }

        return 'success';
    }

    /** 기본 권한으로 초기화 (view만 허용) */
    public static function reset(int $roleId)
    {
        $tenantId = (int) app('tenant_id');

        $role = self::loadRoleOrError($roleId, $tenantId);
        if (! $role) {
            return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
        }

        self::setTeam($tenantId);

        // 1. 먼저 모든 권한 제거
        self::denyAll($roleId);

        // 2. view 권한만 허용
        $menus = \App\Models\Commons\Menu::where('tenant_id', $tenantId)
            ->where('is_active', true)
            ->get();

        foreach ($menus as $menu) {
            $permissionName = "menu:{$menu->id}.view";
            $permission = Permission::firstOrCreate([
                'name' => $permissionName,
                'guard_name' => self::$guard,
                'tenant_id' => $tenantId,
            ]);

            // 권한 부여
            $exists = \Illuminate\Support\Facades\DB::table('role_has_permissions')
                ->where('role_id', $roleId)
                ->where('permission_id', $permission->id)
                ->exists();

            if (! $exists) {
                \Illuminate\Support\Facades\DB::table('role_has_permissions')->insert([
                    'role_id' => $roleId,
                    'permission_id' => $permission->id,
                ]);
            }
        }

        return 'success';
    }
}
