<?php

namespace App\Services;

use App\Models\Tenants\Department;
use App\Models\Tenants\Pivots\DepartmentUser;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;

class DepartmentService extends Service
{
    /**
     * 공통 검증 헬퍼: 실패 시 ['error'=>..., 'code'=>...] 형태로 반환
     */
    protected function v(array $params, array $rules)
    {
        $v = Validator::make($params, $rules);
        if ($v->fails()) {
            return ['error' => $v->errors()->first(), 'code' => 422];
        }

        return $v->validated();
    }

    /** 목록 */
    public function index(array $params)
    {
        $p = $this->v($params, [
            'q' => 'nullable|string|max:100',
            'is_active' => 'nullable|in:0,1',
            'page' => 'nullable|integer|min:1',
            'per_page' => 'nullable|integer|min:1|max:200',
        ]);
        if ($p instanceof JsonResponse) {
            return $p;
        }
        if (isset($p['error'])) {
            return $p;
        }

        $q = Department::query();

        if (isset($p['is_active'])) {
            $q->where('is_active', (int) $p['is_active']);
        }
        if (! empty($p['q'])) {
            $q->where(function ($w) use ($p) {
                $w->where('name', 'like', '%'.$p['q'].'%')
                    ->orWhere('code', 'like', '%'.$p['q'].'%');
            });
        }

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

        $perPage = $p['per_page'] ?? 20;
        $page = $p['page'] ?? null;

        return $q->paginate($perPage, ['*'], 'page', $page);
    }

    /** 부서 트리 조회 */
    public function tree(array $params = []): array
    {
        $p = $this->v($params, [
            'with_users' => 'nullable|in:0,1,true,false',
        ]);
        if (isset($p['error'])) {
            return $p;
        }

        $withUsers = filter_var($p['with_users'] ?? false, FILTER_VALIDATE_BOOLEAN);

        // 최상위 부서 조회 (parent_id가 null인 부서)
        $query = Department::query()
            ->whereNull('parent_id')
            ->orderBy('sort_order')
            ->orderBy('name');

        // 재귀적으로 자식 부서 로드
        $query->with(['children' => function ($q) use ($withUsers) {
            $q->orderBy('sort_order')->orderBy('name');
            $this->loadChildrenRecursive($q, $withUsers);
        }]);

        // employees 관계 사용 (tenant_user_profiles 기반)
        if ($withUsers) {
            $query->with(['employees.user:id,name,email']);
        }

        $result = $query->get();

        // users 형태로 변환 (프론트엔드 호환성)
        if ($withUsers) {
            $result = $this->transformEmployeesToUsers($result);
        }

        return $result->toArray();
    }

    /** 재귀적으로 자식 부서 로드 */
    private function loadChildrenRecursive($query, bool $withUsers): void
    {
        $query->with(['children' => function ($q) use ($withUsers) {
            $q->orderBy('sort_order')->orderBy('name');
            $this->loadChildrenRecursive($q, $withUsers);
        }]);

        // employees 관계 사용 (tenant_user_profiles 기반)
        if ($withUsers) {
            $query->with(['employees.user:id,name,email']);
        }
    }

    /** employees를 users 형태로 변환 (재귀) */
    private function transformEmployeesToUsers($departments)
    {
        return $departments->map(function ($dept) {
            // employees → users 변환
            if ($dept->relationLoaded('employees')) {
                $users = $dept->employees->map(function ($profile) {
                    return $profile->user;
                })->filter(); // null 제거

                $dept->setRelation('users', $users);
                $dept->unsetRelation('employees');
            }

            // 자식 부서도 재귀 처리
            if ($dept->relationLoaded('children') && $dept->children->count() > 0) {
                $dept->setRelation('children', $this->transformEmployeesToUsers($dept->children));
            }

            return $dept;
        });
    }

    /** 생성 */
    public function store(array $params)
    {
        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        $p = $this->v($params, [
            'parent_id' => 'nullable|integer|min:1',
            'code' => 'nullable|string|max:50',
            'name' => 'required|string|max:100',
            'description' => 'nullable|string|max:255',
            'is_active' => 'nullable|in:0,1',
            'sort_order' => 'nullable|integer',
            'created_by' => 'nullable|integer|min:1',
        ]);
        if ($p instanceof JsonResponse) {
            return $p;
        }
        if (isset($p['error'])) {
            return $p;
        }

        // parent_id 유효성 검사
        if (! empty($p['parent_id'])) {
            $parent = Department::query()->find($p['parent_id']);
            if (! $parent) {
                return ['error' => '상위 부서를 찾을 수 없습니다.', 'code' => 404];
            }
        }

        if (! empty($p['code'])) {
            $exists = Department::query()->where('code', $p['code'])->exists();
            if ($exists) {
                return ['error' => '이미 존재하는 부서 코드입니다.', 'code' => 409];
            }
        }

        $dept = Department::create([
            'tenant_id' => $tenantId,
            'parent_id' => $p['parent_id'] ?? null,
            'code' => $p['code'] ?? null,
            'name' => $p['name'],
            'description' => $p['description'] ?? null,
            'is_active' => isset($p['is_active']) ? (int) $p['is_active'] : 1,
            'sort_order' => $p['sort_order'] ?? 0,
            'created_by' => $userId ?? null,
            'updated_by' => $userId ?? null,
        ]);

        return $dept->fresh();
    }

    /** 단건 */
    public function show(int $id, array $params = [])
    {
        if (! $id) {
            return ['error' => 'id가 필요합니다.', 'code' => 400];
        }

        $res = Department::query()->find($id);
        if (! $res) {
            return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404];
        }

        return $res;
    }

    /** 수정 */
    public function update(int $id, array $params)
    {
        if (! $id) {
            return ['error' => 'id가 필요합니다.', 'code' => 400];
        }

        $p = $this->v($params, [
            'parent_id' => 'nullable|integer|min:0', // 0이면 최상위로 이동
            'code' => 'nullable|string|max:50',
            'name' => 'nullable|string|max:100',
            'description' => 'nullable|string|max:255',
            'is_active' => 'nullable|in:0,1',
            'sort_order' => 'nullable|integer',
            'updated_by' => 'nullable|integer|min:1',
        ]);
        if ($p instanceof JsonResponse) {
            return $p;
        }
        if (isset($p['error'])) {
            return $p;
        }

        $dept = Department::query()->find($id);
        if (! $dept) {
            return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404];
        }

        // parent_id 유효성 검사
        if (array_key_exists('parent_id', $p)) {
            $parentId = $p['parent_id'];
            if ($parentId === 0) {
                $parentId = null; // 0이면 최상위로 이동
            } elseif ($parentId) {
                if ($parentId === $id) {
                    return ['error' => '자기 자신을 상위 부서로 설정할 수 없습니다.', 'code' => 422];
                }
                $parent = Department::query()->find($parentId);
                if (! $parent) {
                    return ['error' => '상위 부서를 찾을 수 없습니다.', 'code' => 404];
                }
            }
        }

        if (array_key_exists('code', $p) && ! is_null($p['code'])) {
            $exists = Department::query()
                ->where('code', $p['code'])
                ->where('id', '!=', $id)
                ->exists();
            if ($exists) {
                return ['error' => '이미 존재하는 부서 코드입니다.', 'code' => 409];
            }
        }

        $fillData = [
            'code' => array_key_exists('code', $p) ? $p['code'] : $dept->code,
            'name' => $p['name'] ?? $dept->name,
            'description' => $p['description'] ?? $dept->description,
            'is_active' => isset($p['is_active']) ? (int) $p['is_active'] : $dept->is_active,
            'sort_order' => $p['sort_order'] ?? $dept->sort_order,
            'updated_by' => $p['updated_by'] ?? $dept->updated_by,
        ];

        // parent_id 업데이트
        if (array_key_exists('parent_id', $p)) {
            $fillData['parent_id'] = $p['parent_id'] === 0 ? null : $p['parent_id'];
        }

        $dept->fill($fillData)->save();

        return $dept->fresh();
    }

    /** 삭제(soft) */
    public function destroy(int $id, array $params)
    {
        if (! $id) {
            return ['error' => 'id가 필요합니다.', 'code' => 400];
        }

        $p = $this->v($params, [
            'deleted_by' => 'nullable|integer|min:1',
        ]);
        if ($p instanceof JsonResponse) {
            return $p;
        }
        if (isset($p['error'])) {
            return $p;
        }

        $dept = Department::query()->find($id);
        if (! $dept) {
            return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404];
        }

        if (! empty($p['deleted_by'])) {
            $dept->deleted_by = $p['deleted_by'];
            $dept->save();
        }
        $dept->delete();

        return ['id' => $id, 'deleted_at' => now()->toDateTimeString()];
    }

    /** 부서 사용자 목록 */
    public function listUsers(int $deptId, array $params)
    {
        $p = $this->v($params, [
            'page' => 'nullable|integer|min:1',
            'per_page' => 'nullable|integer|min:1|max:200',
        ]);
        if ($p instanceof JsonResponse) {
            return $p;
        }
        if (isset($p['error'])) {
            return $p;
        }

        $dept = Department::query()->find($deptId);
        if (! $dept) {
            return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404];
        }

        $builder = $dept->departmentUsers()->with('user')
            ->orderByDesc('is_primary')->orderBy('id');

        $perPage = $p['per_page'] ?? 20;
        $page = $p['page'] ?? null;

        return $builder->paginate($perPage, ['*'], 'page', $page);
    }

    /** 사용자 배정 (단건) */
    public function attachUser(int $deptId, array $params)
    {
        $p = $this->v($params, [
            'user_id' => 'required|integer|min:1',
            'is_primary' => 'nullable|in:0,1',
            'joined_at' => 'nullable|date',
        ]);
        if ($p instanceof JsonResponse) {
            return $p;
        }
        if (isset($p['error'])) {
            return $p;
        }

        $dept = Department::query()->find($deptId);
        if (! $dept) {
            return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404];
        }

        $result = DB::transaction(function () use ($dept, $p) {
            $tenantId = $this->tenantId();

            $du = DepartmentUser::withTrashed()
                ->where('department_id', $dept->id)
                ->where('user_id', $p['user_id'])
                ->first();

            if ($du && is_null($du->deleted_at)) {
                return ['error' => '이미 배정된 사용자입니다.', 'code' => 409];
            }

            if (! empty($p['is_primary']) && (int) $p['is_primary'] === 1) {
                DepartmentUser::whereNull('deleted_at')
                    ->where('user_id', $p['user_id'])
                    ->update(['is_primary' => 0]);
            }

            $payload = [
                'department_id' => $dept->id,
                'tenant_id' => $tenantId,
                'user_id' => $p['user_id'],
                'is_primary' => isset($p['is_primary']) ? (int) $p['is_primary'] : 0,
                'joined_at' => ! empty($p['joined_at']) ? Carbon::parse($p['joined_at']) : now(),
            ];

            if ($du) {
                $du->fill($payload);
                $du->restore();
                $du->save();
            } else {
                DepartmentUser::create($payload);
            }

            return ['department_id' => $dept->id, 'user_id' => $p['user_id']];
        });

        if ($result instanceof JsonResponse) {
            return $result;
        }

        return $result;
    }

    /** 사용자 제거(soft) */
    public function detachUser(int $deptId, int $userId, array $params)
    {
        $du = DepartmentUser::whereNull('deleted_at')
            ->where('department_id', $deptId)
            ->where('user_id', $userId)
            ->first();

        if (! $du) {
            return ['error' => '배정된 사용자를 찾을 수 없습니다.', 'code' => 404];
        }

        $du->delete();

        return [
            'user_id' => $userId,
            'deleted_at' => now()->toDateTimeString(),
        ];
    }

    /** 주부서 설정/해제 */
    public function setPrimary(int $deptId, int $userId, array $params)
    {
        $p = $this->v($params, [
            'is_primary' => 'required|in:0,1',
        ]);
        if ($p instanceof JsonResponse) {
            return $p;
        }
        if (isset($p['error'])) {
            return $p;
        }

        $result = DB::transaction(function () use ($deptId, $userId, $p) {
            $du = DepartmentUser::whereNull('deleted_at')
                ->where('department_id', $deptId)
                ->where('user_id', $userId)
                ->first();

            if (! $du) {
                return ['error' => '배정된 사용자를 찾을 수 없습니다.', 'code' => 404];
            }

            if ((int) $p['is_primary'] === 1) {
                DepartmentUser::whereNull('deleted_at')
                    ->where('user_id', $userId)
                    ->update(['is_primary' => 0]);
            }

            $du->is_primary = (int) $p['is_primary'];
            $du->save();

            return ['user_id' => $userId, 'department_id' => $deptId, 'is_primary' => $du->is_primary];
        });

        if ($result instanceof JsonResponse) {
            return $result;
        }

        return $result;
    }

    /** 부서 권한 목록 */
    public function listPermissions(int $deptId, array $params)
    {
        // 1) 파라미터 검증
        $p = $this->v($params, [
            'menu_id' => 'nullable|integer|min:1',
            'is_allowed' => 'nullable|in:0,1',
            'page' => 'nullable|integer|min:1',
            'per_page' => 'nullable|integer|min:1|max:200',
        ]);
        if ($p instanceof JsonResponse) {
            return $p;
        }
        if (isset($p['error'])) {
            return $p;
        }

        // 2) 부서 확인
        $dept = Department::query()->find($deptId);
        if (! $dept) {
            return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404];
        }

        $tenantId = (int) $dept->tenant_id;
        $modelType = Department::class;

        // 3) ALLOW/DENY 서브쿼리 준비 (컬럼 형태 통일)
        $allowSub = DB::table('model_has_permissions')
            ->select([
                'permission_id',
                DB::raw('1 as is_allowed'),
                DB::raw('NULL as reason'),
                DB::raw('NULL as effective_from'),
                DB::raw('NULL as effective_to'),
                DB::raw('NULL as override_id'),
                DB::raw('NULL as override_updated_at'),
            ])
            ->where([
                'model_type' => $modelType,
                'model_id' => $deptId,
                'tenant_id' => $tenantId,
            ]);

        $denySub = DB::table('permission_overrides')
            ->select([
                'permission_id',
                DB::raw('0 as is_allowed'),
                'reason',
                'effective_from',
                'effective_to',
                'id as override_id',
                'updated_at as override_updated_at',
            ])
            ->where([
                'tenant_id' => $tenantId,
                'model_type' => $modelType,
                'model_id' => $deptId,
            ])
            ->where('effect', -1);

        // 4) 합치고 permissions 조인
        $q = DB::query()
            ->fromSub(function ($sub) use ($allowSub, $denySub) {
                $sub->from($allowSub->unionAll($denySub), 'u');
            }, 'u')
            ->join('permissions', 'permissions.id', '=', 'u.permission_id')
            ->select([
                'u.permission_id',
                'u.is_allowed',
                'permissions.name as permission_code',
                'permissions.guard_name',
                'u.reason',
                'u.effective_from',
                'u.effective_to',
                'u.override_id',
                'u.override_updated_at',
            ]);

        // 5) 필터
        if (isset($p['is_allowed'])) {
            $q->where('u.is_allowed', (int) $p['is_allowed']);
        }
        if (isset($p['menu_id'])) {
            // 권한코드가 menu.{id}.xxx 형태라는 전제
            $q->where('permissions.name', 'like', 'menu.'.(int) $p['menu_id'].'.%');
        }

        // 6) 정렬(ALLOW 우선, 그 다음 permission_id 오름차순)
        $q->orderByDesc('u.is_allowed')->orderBy('u.permission_id');

        // 7) 페이지네이션
        $perPage = $p['per_page'] ?? 20;
        $page = $p['page'] ?? null;

        return $q->paginate($perPage, ['*'], 'page', $page);
    }

    /** 권한 부여/차단 upsert */
    public function upsertPermissions(int $deptId, array $payload): array
    {
        // 단일이면 items로 감싸기
        $items = isset($payload['permission_id'])
            ? [$payload]
            : ($payload['items'] ?? $payload);

        $v = Validator::make(['items' => $items], [
            'items' => 'required|array|max:1000',
            'items.*.permission_id' => 'required|integer|min:1',
            'items.*.is_allowed' => 'nullable|in:0,1', // 생략 시 1(허용)
        ]);
        if ($v->fails()) {
            return ['error' => $v->errors()->first(), 'code' => 422];
        }

        $dept = Department::query()->find($deptId);
        if (! $dept) {
            return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404];
        }

        $tenantId = (int) $dept->tenant_id;
        $modelType = Department::class;

        $ok = 0;
        $failed = [];

        DB::transaction(function () use ($items, $tenantId, $deptId, $modelType, &$ok, &$failed) {
            foreach ($items as $i => $r) {
                try {
                    $permissionId = (int) $r['permission_id'];
                    $isAllowed = array_key_exists('is_allowed', $r) ? (int) $r['is_allowed'] : 1;

                    if ($isAllowed === 1) {
                        // ALLOW: Spatie 표준에 넣고, 동일 권한의 DENY 제거
                        $exists = DB::table('model_has_permissions')->where([
                            'permission_id' => $permissionId,
                            'model_type' => $modelType,
                            'model_id' => $deptId,
                            'tenant_id' => $tenantId,
                        ])->exists();

                        if (! $exists) {
                            DB::table('model_has_permissions')->insert([
                                'permission_id' => $permissionId,
                                'model_type' => $modelType,
                                'model_id' => $deptId,
                                'tenant_id' => $tenantId,
                            ]);
                        }

                        DB::table('permission_overrides')->where([
                            'tenant_id' => $tenantId,
                            'model_type' => $modelType,
                            'model_id' => $deptId,
                            'permission_id' => $permissionId,
                            'effect' => -1,
                        ])->delete();
                    } else {
                        // DENY: overrides(effect=-1) upsert, 그리고 ALLOW 제거
                        $exists = DB::table('permission_overrides')->where([
                            'tenant_id' => $tenantId,
                            'model_type' => $modelType,
                            'model_id' => $deptId,
                            'permission_id' => $permissionId,
                        ])->exists();

                        if ($exists) {
                            DB::table('permission_overrides')->where([
                                'tenant_id' => $tenantId,
                                'model_type' => $modelType,
                                'model_id' => $deptId,
                                'permission_id' => $permissionId,
                            ])->update(['effect' => -1, 'updated_at' => now()]);
                        } else {
                            DB::table('permission_overrides')->insert([
                                'tenant_id' => $tenantId,
                                'model_type' => $modelType,
                                'model_id' => $deptId,
                                'permission_id' => $permissionId,
                                'effect' => -1,
                                'created_at' => now(),
                                'updated_at' => now(),
                            ]);
                        }

                        DB::table('model_has_permissions')->where([
                            'permission_id' => $permissionId,
                            'model_type' => $modelType,
                            'model_id' => $deptId,
                            'tenant_id' => $tenantId,
                        ])->delete();
                    }

                    $ok++;
                } catch (\Throwable $e) {
                    $failed[] = [
                        'index' => $i,
                        'permission_id' => $r['permission_id'] ?? null,
                        'message' => $e->getMessage(),
                    ];
                }
            }
        });

        return [
            'processed' => count($items),
            'succeeded' => $ok,
            'failed' => $failed,
        ];
    }

    /** 권한 제거 (menu_id 없으면 전체 제거) */
    public function revokePermissions(int $deptId, array $payload): array
    {
        $items = isset($payload['permission_id'])
            ? [$payload]
            : ($payload['items'] ?? $payload);

        $v = Validator::make(['items' => $items], [
            'items' => 'required|array|max:1000',
            'items.*.permission_id' => 'required|integer|min:1',
        ]);
        if ($v->fails()) {
            return ['error' => $v->errors()->first(), 'code' => 422];
        }

        $dept = Department::query()->find($deptId);
        if (! $dept) {
            return ['error' => '부서를 찾을 수 없습니다.', 'code' => 404];
        }

        $tenantId = (int) $dept->tenant_id;
        $modelType = Department::class;

        $ok = 0;
        $failed = [];

        DB::transaction(function () use ($items, $tenantId, $deptId, $modelType, &$ok, &$failed) {
            foreach ($items as $i => $r) {
                try {
                    $permissionId = (int) $r['permission_id'];

                    // ALLOW 제거
                    DB::table('model_has_permissions')->where([
                        'permission_id' => $permissionId,
                        'model_type' => $modelType,
                        'model_id' => $deptId,
                        'tenant_id' => $tenantId,
                    ])->delete();

                    // DENY/임시허용 오버라이드 제거
                    DB::table('permission_overrides')->where([
                        'tenant_id' => $tenantId,
                        'model_type' => $modelType,
                        'model_id' => $deptId,
                        'permission_id' => $permissionId,
                    ])->delete();

                    $ok++;
                } catch (\Throwable $e) {
                    $failed[] = [
                        'index' => $i,
                        'permission_id' => $r['permission_id'] ?? null,
                        'message' => $e->getMessage(),
                    ];
                }
            }
        });

        return [
            'processed' => count($items),
            'succeeded' => $ok,
            'failed' => $failed,
        ];
    }
}
