<?php

namespace App\Services\Menu;

use App\Models\MenuGroup;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class MenuGroupService
{
    public function list(array $filters = [], ?User $actor = null, ?int $tenantId = null)
    {
        return MenuGroup::query()
            ->with('tenant')
            ->when(
                $actor && ! $actor->isMasterAdmin(),
                fn ($q) => $q->where('tenant_id', $tenantId)
            )
            ->when(
                ! empty($filters['search']),
                fn ($q) => $q->where(function ($x) use ($filters) {
                    $x->where('name', 'ILIKE', "%{$filters['search']}%")
                        ->orWhere('description', 'ILIKE', "%{$filters['search']}%");
                })
            )
            ->when(
                array_key_exists('is_active', $filters) && $filters['is_active'] !== null && $filters['is_active'] !== '',
                fn ($q) => $q->where('is_active', filter_var($filters['is_active'], FILTER_VALIDATE_BOOLEAN))
            )
            ->orderBy(
                $filters['sort_by'] ?? 'id',
                $filters['sort_direction'] ?? 'desc'
            )
            ->paginate($filters['per_page'] ?? 10);
    }

    public function create(array $data, User $actor, ?int $tenantId): MenuGroup
    {
        if (! array_key_exists('is_active', $data) || $data['is_active'] === null) {
            $data['is_active'] = true;
        }

        $scopeTenantId = $actor->isMasterAdmin()
            ? ($data['tenant_id'] ?? null)
            : $tenantId;

        if (! $actor->isMasterAdmin() && ! $scopeTenantId) {
            throw ValidationException::withMessages([
                'tenant_id' => 'Tenant aktif wajib tersedia untuk membuat Menu Group.',
            ]);
        }

        $data['tenant_id'] = $scopeTenantId;
        $data['is_system'] = $actor->isMasterAdmin() && ! $scopeTenantId;

        return MenuGroup::create($data)->load('tenant');
    }

    public function update(
        MenuGroup $menuGroup,
        array $data,
        User $actor,
        ?int $tenantId
    ): MenuGroup {
        if (array_key_exists('is_active', $data) && $data['is_active'] === null) {
            unset($data['is_active']);
        }

        if ($actor->isMasterAdmin()) {
            if (array_key_exists('tenant_id', $data)) {
                $data['is_system'] = ! $data['tenant_id'];
            }
        } else {
            unset($data['tenant_id'], $data['is_system']);
            if ((int) $menuGroup->tenant_id !== (int) $tenantId) {
                abort(403);
            }
        }

        $menuGroup->update($data);

        return $menuGroup->fresh('tenant');
    }

    public function delete(MenuGroup $menuGroup): bool
    {
        $hasMenuMappings = $menuGroup->menus()->exists();
        $hasUserMappings = $menuGroup->userMenuGroups()->exists();

        if ($hasMenuMappings || $hasUserMappings) {
            throw ValidationException::withMessages([
                'menu_group' => 'Menu Group tidak dapat dihapus karena masih memiliki relasi menu/user.',
            ]);
        }

        return $menuGroup->delete();
    }

    public function listMenus(MenuGroup $menuGroup): array
    {
        $rows = DB::table('menu_lists as m')
            ->leftJoin('menu_group_menus as mgm', function ($join) use ($menuGroup) {
                $join->on('mgm.menu_id', '=', 'm.id')
                    ->where('mgm.menu_group_id', '=', $menuGroup->id);
            })
            ->where('m.is_active', true)
            ->select(
                'm.id',
                'm.parent_id',
                'm.name',
                'm.slug',
                'm.url',
                'm.icon',
                'm.component',
                'm.sort_order',
                DB::raw('COALESCE(mgm.can_view, false) as can_view'),
                DB::raw('COALESCE(mgm.can_create, false) as can_create'),
                DB::raw('COALESCE(mgm.can_update, false) as can_update'),
                DB::raw('COALESCE(mgm.can_delete, false) as can_delete'),
                DB::raw('COALESCE(mgm.can_approve, false) as can_approve'),
                DB::raw('COALESCE(mgm.can_export, false) as can_export')
            )
            ->orderBy('m.sort_order')
            ->orderBy('m.id')
            ->get();

        return $rows->map(function ($row) {
            return [
                'menu_id' => (int) $row->id,
                'parent_id' => $row->parent_id ? (int) $row->parent_id : null,
                'name' => $row->name,
                'slug' => $row->slug,
                'url' => $row->url,
                'icon' => $row->icon,
                'component' => $row->component,
                'sort_order' => (int) $row->sort_order,
                'permissions' => [
                    'can_view' => (bool) $row->can_view,
                    'can_create' => (bool) $row->can_create,
                    'can_update' => (bool) $row->can_update,
                    'can_delete' => (bool) $row->can_delete,
                    'can_approve' => (bool) $row->can_approve,
                    'can_export' => (bool) $row->can_export,
                ],
                'available_permissions' => [
                    'can_view' => true,
                    'can_create' => true,
                    'can_update' => true,
                    'can_delete' => true,
                    'can_approve' => true,
                    'can_export' => true,
                ],
            ];
        })->all();
    }

    public function syncMenus(MenuGroup $menuGroup, array $menus, ?User $actor = null): void
    {
        if ($actor && ! $actor->isMasterAdmin()) {
            $allowed = $this->getAllowedMenuPermissionsForUser(
                $actor,
                request()->attributes->get('tenant_id')
            );

            foreach ($menus as $menu) {
                $actorPermissions = $allowed[(int) $menu['menu_id']] ?? null;

                if (! $actorPermissions) {
                    throw ValidationException::withMessages([
                        'menus' => 'Terdapat menu di luar hak akses user login.',
                    ]);
                }

                foreach (['view', 'create', 'update', 'delete', 'approve', 'export'] as $permission) {
                    if (($menu["can_$permission"] ?? false) && ! $actorPermissions["can_$permission"]) {
                        throw ValidationException::withMessages([
                            'menus' => "Permission can_$permission tidak boleh melebihi akses Anda.",
                        ]);
                    }
                }
            }
        }

        $syncData = [];

        foreach ($menus as $menu) {
            $syncData[$menu['menu_id']] = [
                'can_view' => (bool) ($menu['can_view'] ?? false),
                'can_create' => (bool) ($menu['can_create'] ?? false),
                'can_update' => (bool) ($menu['can_update'] ?? false),
                'can_delete' => (bool) ($menu['can_delete'] ?? false),
                'can_approve' => (bool) ($menu['can_approve'] ?? false),
                'can_export' => (bool) ($menu['can_export'] ?? false),
                'updated_at' => now(),
                'created_at' => now(),
            ];
        }

        $menuGroup->menus()->sync($syncData);
    }

    public function listAvailableMenusForGroup(MenuGroup $menuGroup, User $actor): array
    {
        if ($actor->isMasterAdmin()) {
            return $this->listMenus($menuGroup);
        }

        $allowedPermissions = $this->getAllowedMenuPermissionsForUser(
            $actor,
            request()->attributes->get('tenant_id')
        );
        $allowedMenuIds = array_keys($allowedPermissions);

        $rows = DB::table('menu_lists as m')
            ->leftJoin('menu_group_menus as mgm', function ($join) use ($menuGroup) {
                $join->on('mgm.menu_id', '=', 'm.id')
                    ->where('mgm.menu_group_id', '=', $menuGroup->id);
            })
            ->where('m.is_active', true)
            ->whereIn('m.id', $allowedMenuIds)
            ->select(
                'm.id',
                'm.parent_id',
                'm.name',
                'm.sort_order',
                DB::raw('COALESCE(mgm.can_view, false) as can_view'),
                DB::raw('COALESCE(mgm.can_create, false) as can_create'),
                DB::raw('COALESCE(mgm.can_update, false) as can_update'),
                DB::raw('COALESCE(mgm.can_delete, false) as can_delete'),
                DB::raw('COALESCE(mgm.can_approve, false) as can_approve'),
                DB::raw('COALESCE(mgm.can_export, false) as can_export')
            )
            ->orderBy('m.sort_order')
            ->orderBy('m.id')
            ->get();

        return $rows->map(function ($row) use ($allowedPermissions) {
            $available = $allowedPermissions[(int) $row->id];

            return [
                'menu_id' => (int) $row->id,
                'parent_id' => $row->parent_id ? (int) $row->parent_id : null,
                'name' => $row->name,
                'sort_order' => (int) $row->sort_order,
                'permissions' => [
                    'can_view' => (bool) $row->can_view,
                    'can_create' => (bool) $row->can_create,
                    'can_update' => (bool) $row->can_update,
                    'can_delete' => (bool) $row->can_delete,
                    'can_approve' => (bool) $row->can_approve,
                    'can_export' => (bool) $row->can_export,
                ],
                'available_permissions' => $available,
            ];
        })->all();
    }

    public function getAllowedMenuIdsForUser(User $user, ?int $tenantId = null): array
    {
        if ($user->isMasterAdmin()) {
            return DB::table('menu_lists')
                ->where('is_active', true)
                ->pluck('id')
                ->map(fn ($id) => (int) $id)
                ->all();
        }

        return DB::table('menu_group_menus as mgm')
            ->join('user_menu_groups as umg', 'umg.menu_group_id', '=', 'mgm.menu_group_id')
            ->join('menu_lists as m', 'm.id', '=', 'mgm.menu_id')
            ->where('umg.user_id', $user->id)
            ->where('umg.tenant_id', $tenantId)
            ->where('m.is_active', true)
            ->where(function ($q) {
                $q->where('mgm.can_view', true)
                    ->orWhere('mgm.can_create', true)
                    ->orWhere('mgm.can_update', true)
                    ->orWhere('mgm.can_delete', true)
                    ->orWhere('mgm.can_approve', true)
                    ->orWhere('mgm.can_export', true);
            })
            ->distinct()
            ->pluck('mgm.menu_id')
            ->map(fn ($id) => (int) $id)
            ->all();
    }

    public function getAllowedMenuPermissionsForUser(User $user, ?int $tenantId): array
    {
        return DB::table('menu_group_menus as mgm')
            ->join('user_menu_groups as umg', 'umg.menu_group_id', '=', 'mgm.menu_group_id')
            ->join('menu_groups as mg', 'mg.id', '=', 'mgm.menu_group_id')
            ->join('menu_lists as m', 'm.id', '=', 'mgm.menu_id')
            ->where('umg.user_id', $user->id)
            ->where('umg.tenant_id', $tenantId)
            ->where('mg.is_active', true)
            ->where('m.is_active', true)
            ->groupBy('mgm.menu_id')
            ->select(
                'mgm.menu_id',
                DB::raw('MAX(CASE WHEN mgm.can_view THEN 1 ELSE 0 END) as can_view'),
                DB::raw('MAX(CASE WHEN mgm.can_create THEN 1 ELSE 0 END) as can_create'),
                DB::raw('MAX(CASE WHEN mgm.can_update THEN 1 ELSE 0 END) as can_update'),
                DB::raw('MAX(CASE WHEN mgm.can_delete THEN 1 ELSE 0 END) as can_delete'),
                DB::raw('MAX(CASE WHEN mgm.can_approve THEN 1 ELSE 0 END) as can_approve'),
                DB::raw('MAX(CASE WHEN mgm.can_export THEN 1 ELSE 0 END) as can_export')
            )
            ->get()
            ->mapWithKeys(fn ($row) => [(int) $row->menu_id => [
                'can_view' => (bool) $row->can_view,
                'can_create' => (bool) $row->can_create,
                'can_update' => (bool) $row->can_update,
                'can_delete' => (bool) $row->can_delete,
                'can_approve' => (bool) $row->can_approve,
                'can_export' => (bool) $row->can_export,
            ]])
            ->all();
    }

    public function listUserAssignments(User $user, ?int $tenantId = null): array
    {
        return DB::table('user_menu_groups as umg')
            ->join('menu_groups as mg', 'mg.id', '=', 'umg.menu_group_id')
            ->leftJoin('tenants as t', 't.id', '=', 'umg.tenant_id')
            ->where('umg.user_id', $user->id)
            ->when($tenantId, fn ($q) => $q->where('umg.tenant_id', $tenantId))
            ->select(
                'umg.menu_group_id',
                'mg.name as menu_group_name',
                'mg.is_active as menu_group_is_active',
                'umg.tenant_id',
                't.tenant_name as tenant_name',
                'umg.created_at',
                'umg.updated_at'
            )
            ->orderBy('umg.menu_group_id')
            ->orderBy('umg.tenant_id')
            ->get()
            ->map(function ($row) {
                return [
                    'menu_group_id' => (int) $row->menu_group_id,
                    'menu_group_name' => $row->menu_group_name,
                    'menu_group_is_active' => (bool) $row->menu_group_is_active,
                    'tenant_id' => $row->tenant_id ? (int) $row->tenant_id : null,
                    'tenant_name' => $row->tenant_name,
                    'created_at' => $row->created_at,
                    'updated_at' => $row->updated_at,
                ];
            })
            ->all();
    }

    public function syncUserAssignments(User $user, array $assignments, ?int $tenantId = null): void
    {
        $rows = [];

        foreach ($assignments as $assignment) {
            if ($tenantId && (int) ($assignment['tenant_id'] ?? 0) !== $tenantId) {
                throw ValidationException::withMessages([
                    'assignments' => 'Assignment hanya boleh dibuat pada tenant aktif.',
                ]);
            }

            $group = MenuGroup::find($assignment['menu_group_id']);
            if ($tenantId && $group && (int) $group->tenant_id !== $tenantId) {
                throw ValidationException::withMessages([
                    'assignments' => 'Menu group bukan milik tenant aktif.',
                ]);
            }

            $rows[] = [
                'user_id' => $user->id,
                'menu_group_id' => (int) $assignment['menu_group_id'],
                'tenant_id' => $assignment['tenant_id'] ?? null,
                'created_at' => now(),
                'updated_at' => now(),
            ];
        }

        DB::transaction(function () use ($user, $rows) {
            DB::table('user_menu_groups')
                ->where('user_id', $user->id)
                ->when($tenantId, fn ($q) => $q->where('tenant_id', $tenantId))
                ->delete();

            if (! empty($rows)) {
                DB::table('user_menu_groups')->insert($rows);
            }
        });
    }
}
