<?php

namespace App\Services;

use App\Models\Commons\Menu;
use App\Models\Members\User;
use App\Models\Tenants\Department;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;

class MenuService
{
    protected static function tenantId(): ?int
    {
        return app('tenant_id');
    }

    protected static function actorId(): ?int
    {
        $uid = app('api_user');

        return $uid ? (int) $uid : null;
    }

    /**
     * 메뉴 목록 조회 (사용자 권한 기반 필터링)
     */
    public static function index(array $params)
    {
        $tenantId = self::tenantId();
        $userId = self::actorId();

        // 권한이 있는 메뉴 ID 목록 조회
        $allowedMenuIds = self::getAllowedMenuIds($userId, $tenantId);

        $q = Menu::query()->withShared($tenantId);

        // 권한 기반 필터링 적용
        if (! empty($allowedMenuIds)) {
            $q->whereIn('id', $allowedMenuIds);
        } else {
            // 권한이 없으면 빈 결과 반환
            $q->whereRaw('1 = 0');
        }

        if (array_key_exists('parent_id', $params)) {
            $q->where('parent_id', $params['parent_id']);
        }
        // is_active 기본값 true (파라미터 없으면 활성화된 메뉴만)
        $q->where('is_active', (int) ($params['is_active'] ?? 1));
        if (array_key_exists('hidden', $params)) {
            $q->where('hidden', (int) $params['hidden']);
        }

        $q->orderBy('parent_id')->orderBy('sort_order');

        // Builder 그대로 전달해야 쿼리로그/표준응답 형식 유지
        return $q->get();
    }

    /**
     * 사용자가 접근 가능한 메뉴 ID 목록 조회 (mng UserPermissionService와 동일한 로직)
     */
    protected static function getAllowedMenuIds(?int $userId, ?int $tenantId): array
    {
        if (! $userId || ! $tenantId) {
            return [];
        }

        $now = now();

        // 1. 역할 권한 (user_roles 테이블)
        $rolePermissions = DB::table('user_roles')
            ->join('role_has_permissions', 'user_roles.role_id', '=', 'role_has_permissions.role_id')
            ->join('permissions', 'role_has_permissions.permission_id', '=', 'permissions.id')
            ->where('user_roles.user_id', $userId)
            ->where('user_roles.tenant_id', $tenantId)
            ->whereNull('user_roles.deleted_at')
            ->where('permissions.name', 'like', 'menu:%.view')
            ->pluck('permissions.name')
            ->toArray();

        // 2. 부서 권한 (permission_overrides에서 Department 타입, effect=1)
        $deptPermissions = DB::table('department_user')
            ->join('permission_overrides', function ($join) use ($now) {
                $join->on('permission_overrides.model_id', '=', 'department_user.department_id')
                    ->where('permission_overrides.model_type', '=', Department::class)
                    ->whereNull('permission_overrides.deleted_at')
                    ->where('permission_overrides.effect', 1)
                    ->where(function ($q) use ($now) {
                        $q->whereNull('permission_overrides.effective_from')
                            ->orWhere('permission_overrides.effective_from', '<=', $now);
                    })
                    ->where(function ($q) use ($now) {
                        $q->whereNull('permission_overrides.effective_to')
                            ->orWhere('permission_overrides.effective_to', '>=', $now);
                    });
            })
            ->join('permissions', 'permissions.id', '=', 'permission_overrides.permission_id')
            ->whereNull('department_user.deleted_at')
            ->where('department_user.user_id', $userId)
            ->where('department_user.tenant_id', $tenantId)
            ->where('permission_overrides.tenant_id', $tenantId)
            ->where('permissions.name', 'like', 'menu:%.view')
            ->pluck('permissions.name')
            ->toArray();

        // 3. 개인 ALLOW 권한 (permission_overrides에서 User 타입, effect=1)
        // Note: mng는 App\Models\User를 사용하므로 하드코딩
        $personalAllows = DB::table('permission_overrides')
            ->join('permissions', 'permissions.id', '=', 'permission_overrides.permission_id')
            ->where('permission_overrides.model_type', 'App\\Models\\User')
            ->where('permission_overrides.model_id', $userId)
            ->where('permission_overrides.tenant_id', $tenantId)
            ->where('permission_overrides.effect', 1)
            ->whereNull('permission_overrides.deleted_at')
            ->where(function ($q) use ($now) {
                $q->whereNull('permission_overrides.effective_from')
                    ->orWhere('permission_overrides.effective_from', '<=', $now);
            })
            ->where(function ($q) use ($now) {
                $q->whereNull('permission_overrides.effective_to')
                    ->orWhere('permission_overrides.effective_to', '>=', $now);
            })
            ->where('permissions.name', 'like', 'menu:%.view')
            ->pluck('permissions.name')
            ->toArray();

        // 4. 개인 DENY 권한 (permission_overrides에서 User 타입, effect=0)
        // Note: mng는 App\Models\User를 사용하므로 하드코딩
        $personalDenies = DB::table('permission_overrides')
            ->join('permissions', 'permissions.id', '=', 'permission_overrides.permission_id')
            ->where('permission_overrides.model_type', 'App\\Models\\User')
            ->where('permission_overrides.model_id', $userId)
            ->where('permission_overrides.tenant_id', $tenantId)
            ->where('permission_overrides.effect', 0)
            ->whereNull('permission_overrides.deleted_at')
            ->where(function ($q) use ($now) {
                $q->whereNull('permission_overrides.effective_from')
                    ->orWhere('permission_overrides.effective_from', '<=', $now);
            })
            ->where(function ($q) use ($now) {
                $q->whereNull('permission_overrides.effective_to')
                    ->orWhere('permission_overrides.effective_to', '>=', $now);
            })
            ->where('permissions.name', 'like', 'menu:%.view')
            ->pluck('permissions.name')
            ->toArray();

        // 5. 최종 권한 계산: (역할 OR 부서 OR 개인ALLOW) - 개인DENY
        $allAllowed = array_unique(array_merge($rolePermissions, $deptPermissions, $personalAllows));
        $effectivePermissions = array_diff($allAllowed, $personalDenies);

        // 메뉴 ID 추출
        $allowedMenuIds = [];
        foreach ($effectivePermissions as $permName) {
            if (preg_match('/^menu:(\d+)\.view$/', $permName, $matches)) {
                $allowedMenuIds[] = (int) $matches[1];
            }
        }

        return $allowedMenuIds;
    }

    /**
     * 메뉴 단건 조회
     */
    public static function show(array $params)
    {
        $id = (int) ($params['id'] ?? 0);
        $tenantId = self::tenantId();

        if (! $id) {
            return ['error' => 'id가 필요합니다.', 'code' => 400];
        }

        $res = Menu::withShared($tenantId)->find($id);
        if (empty($res['data'])) {
            return ['error' => 'Menu not found', 'code' => 404];
        }

        return $res;
    }

    /**
     * 메뉴 생성
     */
    public static function store(array $params)
    {
        $tenantId = self::tenantId();
        $userId = self::actorId();

        $v = Validator::make($params, [
            'parent_id' => ['nullable', 'integer'],
            'name' => ['required', 'string', 'max:100'],
            'url' => ['nullable', 'string', 'max:255'],
            'is_active' => ['nullable', 'boolean'],
            'sort_order' => ['nullable', 'integer'],
            'hidden' => ['nullable', 'boolean'],
            'is_external' => ['nullable', 'boolean'],
            'external_url' => ['nullable', 'string', 'max:255'],
            'icon' => ['nullable', 'string', 'max:50'],
        ]);

        if ($v->fails()) {
            return ['error' => $v->errors()->first(), 'code' => 422];
        }
        $data = $v->validated();

        $menu = new Menu;
        $menu->tenant_id = $tenantId;
        $menu->parent_id = $data['parent_id'] ?? null;
        $menu->name = $data['name'];
        $menu->url = $data['url'] ?? null;
        $menu->is_active = (int) ($data['is_active'] ?? 1);
        $menu->sort_order = (int) ($data['sort_order'] ?? 0);
        $menu->hidden = (int) ($data['hidden'] ?? 0);
        $menu->is_external = (int) ($data['is_external'] ?? 0);
        $menu->external_url = $data['external_url'] ?? null;
        $menu->icon = $data['icon'] ?? null;
        $menu->created_by = $userId;
        $menu->updated_by = $userId;
        $menu->save();

        // 생성 결과를 그대로 전달
        return $menu->fresh();
    }

    /**
     * 메뉴 수정
     * - global_menu_id가 있는 테넌트 메뉴 수정 시 is_customized = true 자동 설정
     */
    public static function update(array $params)
    {
        $id = (int) ($params['id'] ?? 0);
        $tenantId = self::tenantId();
        $userId = self::actorId();

        if (! $id) {
            return ['error' => 'id가 필요합니다.', 'code' => 400];
        }

        $v = Validator::make($params, [
            'parent_id' => ['nullable', 'integer'],
            'name' => ['nullable', 'string', 'max:100'],
            'url' => ['nullable', 'string', 'max:255'],
            'is_active' => ['nullable', 'boolean'],
            'sort_order' => ['nullable', 'integer'],
            'hidden' => ['nullable', 'boolean'],
            'is_external' => ['nullable', 'boolean'],
            'external_url' => ['nullable', 'string', 'max:255'],
            'icon' => ['nullable', 'string', 'max:50'],
        ]);

        if ($v->fails()) {
            return ['error' => $v->errors()->first(), 'code' => 422];
        }
        $data = $v->validated();

        $menu = Menu::withShared($tenantId)->where('id', $id)->first();
        if (! $menu) {
            return ['error' => 'Menu not found', 'code' => 404];
        }

        $update = Arr::only($data, [
            'parent_id', 'name', 'url', 'is_active', 'sort_order', 'hidden', 'is_external', 'external_url', 'icon',
        ]);
        $update = array_filter($update, fn ($v) => ! is_null($v));

        if (empty($update)) {
            return ['error' => '수정할 데이터가 없습니다.', 'code' => 400];
        }

        // 글로벌 메뉴에서 복제된 테넌트 메뉴 수정 시 커스터마이징 플래그 설정
        if ($menu->global_menu_id && ! $menu->is_customized) {
            $update['is_customized'] = true;
        }

        $update['updated_by'] = $userId;
        $menu->fill($update)->save();

        return $menu->fresh();
    }

    /**
     * 메뉴 삭제(소프트)
     */
    public static function destroy(array $params)
    {
        $id = (int) ($params['id'] ?? 0);
        $tenantId = self::tenantId();
        $userId = self::actorId();

        if (! $id) {
            return ['error' => 'id가 필요합니다.', 'code' => 400];
        }

        $menu = Menu::withShared($tenantId)->where('id', $id)->first();
        if (! $menu) {
            return ['error' => 'Menu not found', 'code' => 404];
        }

        $menu->deleted_by = $userId;
        $menu->save();
        $menu->delete();

        return 'success';
    }

    /**
     * 정렬 일괄 변경
     * $params = [ ['id'=>10, 'sort_order'=>1], ... ]
     */
    public static function reorder(array $params)
    {
        if (! is_array($params) || empty($params)) {
            return ['error' => '유효한 정렬 목록이 필요합니다.', 'code' => 422];
        }
        $tenantId = self::tenantId();

        DB::transaction(function () use ($params, $tenantId) {
            foreach ($params as $it) {
                if (! isset($it['id'], $it['sort_order'])) {
                    continue;
                }

                $menu = Menu::withShared($tenantId)->find((int) $it['id']);
                if ($menu) {
                    $menu->sort_order = (int) $it['sort_order'];
                    $menu->save();
                }
            }
        });

        return 'success';
    }

    /**
     * 상태 토글: is_active / hidden / is_external
     */
    public static function toggle(array $params)
    {
        $id = (int) ($params['id'] ?? 0);
        $tenantId = self::tenantId();
        $userId = self::actorId();

        if (! $id) {
            return ['error' => 'id가 필요합니다.', 'code' => 400];
        }

        $payload = array_filter([
            'is_active' => array_key_exists('is_active', $params) ? (int) $params['is_active'] : null,
            'hidden' => array_key_exists('hidden', $params) ? (int) $params['hidden'] : null,
            'is_external' => array_key_exists('is_external', $params) ? (int) $params['is_external'] : null,
        ], fn ($v) => ! is_null($v));

        if (empty($payload)) {
            return ['error' => '변경할 필드가 없습니다.', 'code' => 422];
        }

        $menu = Menu::withShared($tenantId)->find($id);
        if (! $menu) {
            return ['error' => 'Menu not found', 'code' => 404];
        }

        $payload['updated_by'] = $userId;
        $menu->fill($payload)->save();

        return $menu->fresh();
    }

    /**
     * 삭제된 메뉴 복원
     */
    public static function restore(array $params)
    {
        $id = (int) ($params['id'] ?? 0);
        $tenantId = self::tenantId();
        $userId = self::actorId();

        if (! $id) {
            return ['error' => 'id가 필요합니다.', 'code' => 400];
        }

        // 삭제된 메뉴 포함하여 조회
        $menu = Menu::withTrashed()
            ->withoutGlobalScopes()
            ->where(function ($q) use ($tenantId) {
                $q->whereNull('tenant_id')
                    ->orWhere('tenant_id', $tenantId);
            })
            ->where('id', $id)
            ->first();

        if (! $menu) {
            return ['error' => 'Menu not found', 'code' => 404];
        }

        if (! $menu->trashed()) {
            return ['error' => '삭제되지 않은 메뉴입니다.', 'code' => 400];
        }

        $menu->restore();
        $menu->deleted_by = null;
        $menu->updated_by = $userId;
        $menu->save();

        return $menu->fresh();
    }

    /**
     * 삭제된 메뉴 목록 조회
     */
    public static function trashedList(array $params = [])
    {
        $tenantId = self::tenantId();

        return Menu::onlyTrashed()
            ->withoutGlobalScopes()
            ->where(function ($q) use ($tenantId) {
                $q->whereNull('tenant_id')
                    ->orWhere('tenant_id', $tenantId);
            })
            ->orderBy('deleted_at', 'desc')
            ->get();
    }

    // =========================================================================
    // 게시판 메뉴 연동 메서드 (테넌트 게시판 전용)
    // =========================================================================

    /**
     * 테넌트 게시판용 메뉴 생성
     *
     * @param  string  $boardCode  게시판 코드
     * @param  string  $boardName  게시판 이름
     * @param  int  $tenantId  테넌트 ID
     * @return Menu|null 생성된 메뉴 또는 null (중복 시)
     */
    public static function createMenuForBoard(string $boardCode, string $boardName, int $tenantId): ?Menu
    {
        $url = '/boards/'.$boardCode;
        $userId = self::actorId();

        // 중복 체크
        if (Menu::where('url', $url)->where('tenant_id', $tenantId)->exists()) {
            return null;
        }

        // 부모 메뉴 찾기
        $parentId = self::findParentMenuForBoard($tenantId);

        // 정렬 순서 계산
        $maxOrder = Menu::where('tenant_id', $tenantId)
            ->where('parent_id', $parentId)
            ->max('sort_order') ?? 0;

        return Menu::create([
            'tenant_id' => $tenantId,
            'parent_id' => $parentId,
            'name' => $boardName,
            'url' => $url,
            'icon' => 'document-text',
            'sort_order' => $maxOrder + 1,
            'is_active' => true,
            'hidden' => false,
            'is_external' => false,
            'created_by' => $userId,
            'updated_by' => $userId,
        ]);
    }

    /**
     * 테넌트 게시판 메뉴의 부모 메뉴 ID 찾기
     * 우선순위: /board → /boards → /customer-center → null (최상위)
     *
     * @param  int  $tenantId  테넌트 ID
     * @return int|null 부모 메뉴 ID
     */
    protected static function findParentMenuForBoard(int $tenantId): ?int
    {
        $priorityUrls = ['/board', '/boards', '/customer-center'];

        foreach ($priorityUrls as $url) {
            $menu = Menu::where('tenant_id', $tenantId)
                ->where('url', $url)
                ->first();

            if ($menu) {
                return $menu->id;
            }
        }

        return null;
    }

    /**
     * 테넌트 게시판용 메뉴 수정 (코드/이름 변경 시)
     *
     * @param  string  $oldCode  기존 게시판 코드
     * @param  string  $newCode  새 게시판 코드
     * @param  string  $newName  새 게시판 이름
     * @param  int  $tenantId  테넌트 ID
     * @return bool 수정 성공 여부
     */
    public static function updateMenuForBoard(string $oldCode, string $newCode, string $newName, int $tenantId): bool
    {
        $oldUrl = '/boards/'.$oldCode;
        $newUrl = '/boards/'.$newCode;

        $menu = Menu::where('url', $oldUrl)
            ->where('tenant_id', $tenantId)
            ->first();

        if (! $menu) {
            return false;
        }

        $menu->url = $newUrl;
        $menu->name = $newName;
        $menu->updated_by = self::actorId();
        $menu->save();

        return true;
    }

    /**
     * 테넌트 게시판용 메뉴 삭제 (Soft Delete)
     *
     * @param  string  $boardCode  게시판 코드
     * @param  int  $tenantId  테넌트 ID
     * @param  bool  $forceDelete  영구 삭제 여부
     * @return bool 삭제 성공 여부
     */
    public static function deleteMenuForBoard(string $boardCode, int $tenantId, bool $forceDelete = false): bool
    {
        $url = '/boards/'.$boardCode;

        $query = $forceDelete
            ? Menu::withTrashed()->where('url', $url)->where('tenant_id', $tenantId)
            : Menu::where('url', $url)->where('tenant_id', $tenantId);

        $menu = $query->first();

        if (! $menu) {
            return false;
        }

        if (! $forceDelete) {
            $menu->deleted_by = self::actorId();
            $menu->save();
        }

        return $forceDelete ? $menu->forceDelete() : $menu->delete();
    }

    /**
     * 테넌트 게시판용 메뉴 복원
     *
     * @param  string  $boardCode  게시판 코드
     * @param  string  $boardName  게시판 이름
     * @param  int  $tenantId  테넌트 ID
     * @return bool 복원 성공 여부
     */
    public static function restoreMenuForBoard(string $boardCode, string $boardName, int $tenantId): bool
    {
        $url = '/boards/'.$boardCode;

        // 1. soft-deleted 메뉴 확인
        $trashedMenu = Menu::onlyTrashed()
            ->where('url', $url)
            ->where('tenant_id', $tenantId)
            ->first();

        if ($trashedMenu) {
            $trashedMenu->restore();
            $trashedMenu->deleted_by = null;
            $trashedMenu->updated_by = self::actorId();
            $trashedMenu->save();

            return true;
        }

        // 2. 이미 활성 메뉴가 있는지 확인
        if (Menu::where('url', $url)->where('tenant_id', $tenantId)->exists()) {
            return true; // 이미 존재하면 성공으로 처리
        }

        // 3. 둘 다 없으면 새로 생성
        $newMenu = self::createMenuForBoard($boardCode, $boardName, $tenantId);

        return $newMenu !== null;
    }
}
