<?php

namespace App\Services\HR;

use App\Models\Approvals\Approval;
use App\Models\Approvals\ApprovalForm;
use App\Models\Approvals\ApprovalLine;
use App\Models\HR\Attendance;
use App\Models\HR\Employee;
use App\Models\HR\Leave;
use App\Models\HR\LeaveBalance;
use App\Models\HR\LeavePolicy;
use App\Models\Tenants\Department;
use App\Services\ApprovalService;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class LeaveService
{
    /**
     * 휴가 목록 조회 (필터 + 페이지네이션)
     */
    public function getLeaves(array $filters = [], int $perPage = 20): LengthAwarePaginator
    {
        $tenantId = session('selected_tenant_id');

        $query = Leave::query()
            ->with([
                'user',
                'user.tenantProfiles' => fn ($q) => $q->where('tenant_id', $tenantId),
                'user.tenantProfiles.department',
                'approver',
                'approval.steps.approver',
            ])
            ->forTenant($tenantId);

        if (! empty($filters['q'])) {
            $search = $filters['q'];
            $query->whereHas('user', fn ($q) => $q->where('name', 'like', "%{$search}%"));
        }

        if (! empty($filters['user_id'])) {
            $query->where('user_id', $filters['user_id']);
        }

        if (! empty($filters['department_id'])) {
            $deptId = $filters['department_id'];
            $query->whereHas('user.tenantProfiles', function ($q) use ($tenantId, $deptId) {
                $q->where('tenant_id', $tenantId)->where('department_id', $deptId);
            });
        }

        if (! empty($filters['leave_type'])) {
            $query->where('leave_type', $filters['leave_type']);
        }

        if (! empty($filters['status'])) {
            $query->where('status', $filters['status']);
        }

        if (! empty($filters['date_from']) && ! empty($filters['date_to'])) {
            $query->betweenDates($filters['date_from'], $filters['date_to']);
        } elseif (! empty($filters['date_from'])) {
            $query->where('start_date', '>=', $filters['date_from']);
        } elseif (! empty($filters['date_to'])) {
            $query->where('end_date', '<=', $filters['date_to']);
        }

        return $query
            ->orderByRaw("FIELD(status, 'pending', 'approved', 'rejected', 'cancelled')")
            ->orderBy('created_at', 'desc')
            ->paginate($perPage);
    }

    /**
     * 휴가/근태신청/사유서 등록 → 결재 자동 생성 + 상신
     */
    public function storeLeave(array $data): Leave
    {
        $tenantId = session('selected_tenant_id');
        $leaveType = $data['leave_type'];

        // 사유서는 days=0, 그 외는 자동 계산
        if (in_array($leaveType, Leave::REASON_REPORT_TYPES)) {
            $days = 0;
        } else {
            $days = $this->calculateDays($leaveType, $data['start_date'], $data['end_date']);
        }

        // 연차 차감 대상이면 잔여일수 검증
        if (in_array($leaveType, Leave::DEDUCTIBLE_TYPES)) {
            $balance = LeaveBalance::query()
                ->forTenant($tenantId)
                ->forUser($data['user_id'])
                ->forYear(now()->year)
                ->first();

            if (! $balance || ! $balance->canUse($days)) {
                throw new \RuntimeException('잔여 연차가 부족합니다.');
            }
        }

        return DB::transaction(function () use ($data, $tenantId, $days, $leaveType) {
            $leave = Leave::create([
                'tenant_id' => $tenantId,
                'user_id' => $data['user_id'],
                'leave_type' => $leaveType,
                'start_date' => $data['start_date'],
                'end_date' => $data['end_date'],
                'days' => $days,
                'reason' => $data['reason'] ?? null,
                'status' => 'pending',
                'created_by' => auth()->id(),
                'updated_by' => auth()->id(),
            ]);

            // 결재 자동 생성 + 상신 (유형에 맞는 결재양식 자동 선택)
            $approval = $this->createLeaveApproval($leave, $tenantId, $data['approval_line_id'] ?? null);
            $leave->update(['approval_id' => $approval->id]);

            return $leave;
        });
    }

    /**
     * 승인 → LeaveBalance 차감 → 유형별 Attendance 반영
     */
    public function approve(int $id): ?Leave
    {
        $tenantId = session('selected_tenant_id');

        $leave = Leave::query()
            ->forTenant($tenantId)
            ->withStatus('pending')
            ->find($id);

        if (! $leave) {
            return null;
        }

        return DB::transaction(function () use ($leave, $tenantId) {
            $leave->update([
                'status' => 'approved',
                'approved_by' => auth()->id(),
                'approved_at' => now(),
                'updated_by' => auth()->id(),
            ]);

            // 연차 차감 대상이면 LeaveBalance 차감
            if ($leave->is_deductible) {
                $balance = LeaveBalance::query()
                    ->forTenant($tenantId)
                    ->forUser($leave->user_id)
                    ->forYear($leave->start_date->year)
                    ->first();

                if ($balance) {
                    $balance->useLeave($leave->days);
                }
            }

            // 유형별 Attendance 반영
            $this->applyAttendanceByType($leave, $tenantId);

            return $leave->fresh(['user', 'approver']);
        });
    }

    /**
     * 반려
     */
    public function reject(int $id, ?string $reason = null): ?Leave
    {
        $tenantId = session('selected_tenant_id');

        $leave = Leave::query()
            ->forTenant($tenantId)
            ->withStatus('pending')
            ->find($id);

        if (! $leave) {
            return null;
        }

        $leave->update([
            'status' => 'rejected',
            'approved_by' => auth()->id(),
            'approved_at' => now(),
            'reject_reason' => $reason,
            'updated_by' => auth()->id(),
        ]);

        return $leave->fresh(['user', 'approver']);
    }

    /**
     * 취소 → LeaveBalance 복원 → Attendance 삭제 → 결재 취소
     */
    public function cancel(int $id): ?Leave
    {
        $tenantId = session('selected_tenant_id');

        $leave = Leave::query()
            ->forTenant($tenantId)
            ->withStatus('approved')
            ->find($id);

        if (! $leave) {
            return null;
        }

        return DB::transaction(function () use ($leave, $tenantId) {
            $leave->update([
                'status' => 'cancelled',
                'updated_by' => auth()->id(),
            ]);

            // 연차 차감 대상이면 LeaveBalance 복원
            if ($leave->is_deductible) {
                $balance = LeaveBalance::query()
                    ->forTenant($tenantId)
                    ->forUser($leave->user_id)
                    ->forYear($leave->start_date->year)
                    ->first();

                if ($balance) {
                    $balance->restoreLeave($leave->days);
                }
            }

            // 해당 기간 vacation Attendance soft delete
            $this->deleteAttendanceRecords($leave, $tenantId);

            return $leave->fresh(['user']);
        });
    }

    /**
     * pending 상태 휴가 삭제 → 연결된 결재 취소
     */
    public function deletePendingLeave(int $id): ?Leave
    {
        $tenantId = session('selected_tenant_id');

        $leave = Leave::query()
            ->forTenant($tenantId)
            ->withStatus('pending')
            ->find($id);

        if (! $leave) {
            return null;
        }

        return DB::transaction(function () use ($leave) {
            // 연결된 결재가 있고 아직 진행 중이면 취소
            if ($leave->approval_id) {
                $approval = Approval::find($leave->approval_id);
                if ($approval && in_array($approval->status, ['draft', 'pending'])) {
                    try {
                        app(ApprovalService::class)->cancel($approval->id);
                    } catch (\Throwable $e) {
                        // 결재 취소 실패해도 휴가 삭제는 진행
                        report($e);
                    }
                }
            }

            $leave->update(['deleted_by' => auth()->id()]);
            $leave->delete();

            return $leave;
        });
    }

    /**
     * 휴가/신청 삭제 (슈퍼관리자 전용 — 모든 상태 허용)
     */
    public function deleteLeave(int $id): ?Leave
    {
        $tenantId = session('selected_tenant_id');

        $leave = Leave::query()
            ->forTenant($tenantId)
            ->find($id);

        if (! $leave) {
            return null;
        }

        return DB::transaction(function () use ($leave, $tenantId) {
            // 연결된 결재 취소 시도
            if ($leave->approval_id) {
                $approval = Approval::find($leave->approval_id);
                if ($approval && in_array($approval->status, ['draft', 'pending'])) {
                    try {
                        app(ApprovalService::class)->cancel($approval->id);
                    } catch (\Throwable $e) {
                        report($e);
                    }
                }
            }

            // 승인된 연차 차감 복원
            if ($leave->status === 'approved' && $leave->is_deductible) {
                $balance = LeaveBalance::query()
                    ->forTenant($tenantId)
                    ->forUser($leave->user_id)
                    ->forYear($leave->start_date->year)
                    ->first();
                $balance?->restoreLeave($leave->days);
            }

            $leave->update(['deleted_by' => auth()->id()]);
            $leave->delete();

            return $leave;
        });
    }

    /**
     * 휴가/신청 영구삭제 (슈퍼관리자 전용)
     */
    public function forceDeleteLeave(int $id): ?Leave
    {
        $tenantId = session('selected_tenant_id');

        $leave = Leave::withTrashed()
            ->forTenant($tenantId)
            ->find($id);

        if (! $leave) {
            return null;
        }

        return DB::transaction(function () use ($leave, $tenantId) {
            // 승인된 연차 차감 복원 (아직 soft-deleted 아닌 경우)
            if ($leave->status === 'approved' && $leave->is_deductible && ! $leave->trashed()) {
                $balance = LeaveBalance::query()
                    ->forTenant($tenantId)
                    ->forUser($leave->user_id)
                    ->forYear($leave->start_date->year)
                    ->first();
                $balance?->restoreLeave($leave->days);
            }

            // 연결된 결재 정리
            if ($leave->approval_id) {
                $approval = Approval::withTrashed()->find($leave->approval_id);
                $approval?->forceDelete();
            }

            $leave->forceDelete();

            return $leave;
        });
    }

    /**
     * 결재 승인에 의한 휴가/근태신청/사유서 자동 승인
     */
    public function approveByApproval(Leave $leave, Approval $approval): Leave
    {
        $tenantId = $leave->tenant_id;

        // 최종 결재자 ID 찾기
        $lastApprover = $approval->steps()
            ->where('status', 'approved')
            ->reorder('step_order', 'desc')
            ->first();

        $leave->update([
            'status' => 'approved',
            'approved_by' => $lastApprover?->approver_id ?? auth()->id(),
            'approved_at' => now(),
            'updated_by' => auth()->id(),
        ]);

        // 연차 차감 (DEDUCTIBLE_TYPES만)
        if ($leave->is_deductible) {
            $balance = LeaveBalance::query()
                ->where('tenant_id', $tenantId)
                ->where('user_id', $leave->user_id)
                ->where('year', $leave->start_date->year)
                ->first();

            if ($balance) {
                $balance->useLeave($leave->days);
            }
        }

        // 유형별 Attendance 반영
        $this->applyAttendanceByType($leave, $tenantId);

        return $leave->fresh(['user', 'approver']);
    }

    /**
     * 유형별 Attendance 반영 분기
     */
    private function applyAttendanceByType(Leave $leave, int $tenantId): void
    {
        $attendanceStatus = Leave::ATTENDANCE_STATUS_MAP[$leave->leave_type] ?? null;

        if ($attendanceStatus) {
            // 휴가/출장/재택/외근 → Attendance 자동 생성
            $this->createAttendanceRecords($leave, $tenantId, $attendanceStatus);
        } elseif ($leave->leave_type === 'early_leave') {
            // 조퇴 → 기존 Attendance에 비고 기록
            $this->markEarlyLeave($leave, $tenantId);
        } elseif (in_array($leave->leave_type, Leave::REASON_REPORT_TYPES)) {
            // 지각/결근 사유서 → 기존 Attendance에 사유 기록
            $this->addReasonToAttendance($leave, $tenantId);
        }
    }

    /**
     * 조퇴 승인 시 기존 Attendance에 비고 기록
     */
    private function markEarlyLeave(Leave $leave, int $tenantId): void
    {
        $attendance = Attendance::query()
            ->where('tenant_id', $tenantId)
            ->where('user_id', $leave->user_id)
            ->where('base_date', $leave->start_date->toDateString())
            ->first();

        if ($attendance) {
            $remarks = $attendance->remarks;
            $reason = $leave->reason ? " ({$leave->reason})" : '';
            $attendance->update([
                'remarks' => trim(($remarks ? $remarks.' / ' : '').'조퇴'.$reason),
                'updated_by' => auth()->id(),
            ]);
        }
    }

    /**
     * 지각/결근 사유서 승인 시 기존 Attendance에 사유 기록
     */
    private function addReasonToAttendance(Leave $leave, int $tenantId): void
    {
        $attendance = Attendance::query()
            ->where('tenant_id', $tenantId)
            ->where('user_id', $leave->user_id)
            ->where('base_date', $leave->start_date->toDateString())
            ->first();

        if ($attendance) {
            $typeName = $leave->leave_type === 'late_reason' ? '지각사유' : '결근사유';
            $reason = $leave->reason ?? '';
            $remarks = $attendance->remarks;
            $attendance->update([
                'remarks' => trim(($remarks ? $remarks.' / ' : '')."[{$typeName}] {$reason}"),
                'updated_by' => auth()->id(),
            ]);
        }
    }

    /**
     * 결재 반려에 의한 휴가 자동 반려
     */
    public function rejectByApproval(Leave $leave, string $comment, int $rejecterId): Leave
    {
        $leave->update([
            'status' => 'rejected',
            'reject_reason' => $comment,
            'approved_by' => $rejecterId,
            'approved_at' => now(),
            'updated_by' => auth()->id(),
        ]);

        return $leave->fresh(['user', 'approver']);
    }

    /**
     * 전체 사원 잔여연차 요약
     *
     * 사원관리의 모든 재직/휴직 직원을 표시하며,
     * balance 레코드가 없는 직원은 자동 생성한다.
     */
    public function getBalanceSummary(?int $year = null, ?string $sort = null, ?string $direction = null, ?string $empStatus = null): Collection
    {
        $tenantId = session('selected_tenant_id');
        $year = $year ?? now()->year;

        // (1) 테넌트 연차 정책 조회
        $policy = LeavePolicy::forTenant($tenantId)->first();

        // (2) 재직상태 필터에 따른 직원 조회
        $statusFilter = match ($empStatus) {
            'active' => ['active', 'leave'],
            'resigned' => ['resigned'],
            default => ['active', 'leave', 'resigned'],
        };

        $employees = Employee::query()
            ->with(['user:id,name', 'department:id,name'])
            ->forTenant($tenantId)
            ->whereIn('employee_status', $statusFilter)
            ->where(function ($q) {
                $q->whereDoesntHave('department', function ($dq) {
                    $dq->where('name', 'like', '%영업팀%');
                })->orWhereNull('department_id');
            })
            ->where(function ($q) {
                $q->whereNull('json_extra->is_excluded')
                    ->orWhere('json_extra->is_excluded', false);
            })
            ->get();

        // (3) 기존 balance 일괄 조회
        $existingBalances = LeaveBalance::query()
            ->forTenant($tenantId)
            ->forYear($year)
            ->get()
            ->keyBy('user_id');

        // (4) balance 없는 직원 → insertOrIgnore로 자동 생성
        $newRecords = [];
        foreach ($employees as $employee) {
            if (! $existingBalances->has($employee->user_id)) {
                $newRecords[] = [
                    'tenant_id' => $tenantId,
                    'user_id' => $employee->user_id,
                    'year' => $year,
                    'total_days' => $this->calculateAnnualLeaveDays($employee, $year, $policy),
                    'used_days' => 0,
                    'created_at' => now(),
                    'updated_at' => now(),
                ];
            }
        }

        if (! empty($newRecords)) {
            LeaveBalance::insertOrIgnore($newRecords);

            // 새로 생성된 레코드 포함하여 다시 조회
            $existingBalances = LeaveBalance::query()
                ->forTenant($tenantId)
                ->forYear($year)
                ->get()
                ->keyBy('user_id');
        }

        // (5) Employee 정보를 balance에 연결
        $employeesByUserId = $employees->keyBy('user_id');

        // (6) 현재연도 + 1년 미만 직원은 매번 재계산 (월별 발생 방식)
        //     퇴사자는 퇴사일 기준으로 확정되므로 재계산 대상에서 제외
        if ($year === (int) now()->year) {
            foreach ($existingBalances as $balance) {
                $employee = $employeesByUserId->get($balance->user_id);
                if (! $employee || ! $employee->hire_date) {
                    continue;
                }

                if ($employee->employee_status === 'resigned') {
                    continue;
                }

                $hire = Carbon::parse($employee->hire_date);
                if ($hire->diffInMonths(today()) < 12) {
                    $recalculated = $this->calculateAnnualLeaveDays($employee, $year, $policy);
                    if (abs($balance->total_days - $recalculated) > 0.001) {
                        $balance->update(['total_days' => $recalculated]);
                    }
                }
            }
        }

        $result = $existingBalances
            ->filter(fn ($balance) => $employeesByUserId->has($balance->user_id))
            ->map(function ($balance) use ($employeesByUserId) {
                $employee = $employeesByUserId->get($balance->user_id);
                $balance->employee = $employee;

                return $balance;
            });

        // 정렬 (기본: 입사일 오름차순)
        $sortField = $sort ?? 'hire_date';
        $isDesc = ($direction ?? 'asc') === 'desc';

        $sortCallback = match ($sortField) {
            'name' => fn ($b) => $b->employee?->display_name ?? '',
            'department' => fn ($b) => $b->employee?->department?->name ?? '',
            'hire_date' => fn ($b) => $b->employee?->hire_date ?? '9999-12-31',
            'status' => fn ($b) => match ($b->employee?->employee_status) {
                'active' => 1, 'leave' => 2, 'resigned' => 3, default => 9,
            },
            'resign_date' => fn ($b) => $b->employee?->resign_date ?? '9999-12-31',
            'total_days' => fn ($b) => $b->total_days,
            'used_days' => fn ($b) => $b->used_days,
            'remaining' => fn ($b) => $b->total_days - $b->used_days,
            'rate' => fn ($b) => $b->total_days > 0 ? $b->used_days / $b->total_days : 0,
            default => fn ($b) => $b->employee?->hire_date ?? '9999-12-31',
        };

        return ($isDesc ? $result->sortByDesc($sortCallback) : $result->sortBy($sortCallback))->values();
    }

    /**
     * 입사일 기반 연차일수 자동 산출 (근로기준법 제60조)
     *
     * - 입사일 없음 → default_annual_leave (기본 15일)
     * - 1년 미만 → 입사일~기준일 완료 월수 (최대 11일, 월별 발생)
     * - 1년 이상 → 15일 + 매 2년마다 +1일 (최대 max_annual_leave)
     */
    private function calculateAnnualLeaveDays(Employee $employee, int $year, ?LeavePolicy $policy): float
    {
        $defaultDays = $policy->default_annual_leave ?? 15;
        $maxDays = $policy->max_annual_leave ?? 25;

        $hireDate = $employee->hire_date;
        if (! $hireDate) {
            return (float) $defaultDays;
        }

        $hire = Carbon::parse($hireDate);

        // 기준일: 현재연도면 오늘, 과거연도면 연말
        $referenceDate = $year === (int) now()->year
            ? today()
            : Carbon::create($year, 12, 31);

        // 퇴사자: 기준일을 퇴사일로 제한
        $resignDate = $employee->resign_date;
        if ($resignDate) {
            $resign = Carbon::parse($resignDate);
            if ($resign->lessThan($referenceDate)) {
                $referenceDate = $resign;
            }
        }

        // 아직 입사 전이면 0일
        if ($hire->greaterThan($referenceDate)) {
            return 0;
        }

        // 입사일~기준일 완료 월수
        $totalMonthsWorked = (int) $hire->diffInMonths($referenceDate);

        // 1년 미만: 완료된 월수 × 1일 (최대 11일)
        if ($totalMonthsWorked < 12) {
            return (float) min($totalMonthsWorked, 11);
        }

        // 1년 이상: 15일 + 매 2년마다 +1일
        $yearsWorked = (int) $hire->diffInYears($referenceDate);
        $additionalDays = (int) floor(($yearsWorked - 1) / 2);
        $totalDays = $defaultDays + $additionalDays;

        return (float) min($totalDays, $maxDays);
    }

    /**
     * 개별 사원 잔여연차
     */
    public function getUserBalance(int $userId, ?int $year = null): ?LeaveBalance
    {
        $tenantId = session('selected_tenant_id');
        $year = $year ?? now()->year;

        return LeaveBalance::query()
            ->forTenant($tenantId)
            ->forUser($userId)
            ->forYear($year)
            ->first();
    }

    /**
     * 유형별/사원별 사용 통계
     */
    public function getUsageStats(?int $year = null): array
    {
        $tenantId = session('selected_tenant_id');
        $year = $year ?? now()->year;

        // 유형별 집계
        $byType = Leave::query()
            ->forTenant($tenantId)
            ->forYear($year)
            ->withStatus('approved')
            ->select('leave_type', DB::raw('COUNT(*) as count'), DB::raw('SUM(days) as total_days'))
            ->groupBy('leave_type')
            ->get()
            ->keyBy('leave_type');

        // 사원별 유형 크로스 테이블
        $byUser = Leave::query()
            ->forTenant($tenantId)
            ->forYear($year)
            ->withStatus('approved')
            ->with([
                'user',
                'user.tenantProfiles' => fn ($q) => $q->where('tenant_id', $tenantId),
                'user.tenantProfiles.department',
            ])
            ->select('user_id', 'leave_type', DB::raw('SUM(days) as total_days'))
            ->groupBy('user_id', 'leave_type')
            ->get()
            ->groupBy('user_id');

        return [
            'by_type' => $byType,
            'by_user' => $byUser,
            'year' => $year,
        ];
    }

    /**
     * 일수 자동 계산 (반차 = 0.5, 연차/기타 = 영업일수)
     */
    public function calculateDays(string $type, string $start, string $end): float
    {
        if (in_array($type, ['half_am', 'half_pm'])) {
            return 0.5;
        }

        $period = CarbonPeriod::create($start, $end);
        $businessDays = 0;

        foreach ($period as $date) {
            if (! $date->isWeekend()) {
                $businessDays++;
            }
        }

        return (float) $businessDays;
    }

    /**
     * CSV 내보내기용 데이터
     */
    public function getExportData(array $filters = []): Collection
    {
        $tenantId = session('selected_tenant_id');

        $query = Leave::query()
            ->with([
                'user',
                'user.tenantProfiles' => fn ($q) => $q->where('tenant_id', $tenantId),
                'user.tenantProfiles.department',
                'approver',
            ])
            ->forTenant($tenantId);

        if (! empty($filters['status'])) {
            $query->withStatus($filters['status']);
        }

        if (! empty($filters['leave_type'])) {
            $query->where('leave_type', $filters['leave_type']);
        }

        if (! empty($filters['date_from']) && ! empty($filters['date_to'])) {
            $query->betweenDates($filters['date_from'], $filters['date_to']);
        }

        return $query->orderBy('start_date', 'desc')->get();
    }

    /**
     * 부서 목록
     */
    public function getDepartments(): \Illuminate\Database\Eloquent\Collection
    {
        $tenantId = session('selected_tenant_id');

        return Department::query()
            ->where('is_active', true)
            ->when($tenantId, fn ($q) => $q->where('tenant_id', $tenantId))
            ->where('name', 'not like', '%영업팀%')
            ->orderBy('sort_order')
            ->orderBy('name')
            ->get(['id', 'name', 'code']);
    }

    /**
     * 활성 사원 목록 (드롭다운용) — 영업팀 + 강제 제외 사원 제외
     */
    public function getActiveEmployees(): \Illuminate\Database\Eloquent\Collection
    {
        $tenantId = session('selected_tenant_id');

        return \App\Models\HR\Employee::query()
            ->with(['user:id,name', 'department:id,name'])
            ->forTenant($tenantId)
            ->activeEmployees()
            ->where(function ($q) {
                $q->whereDoesntHave('department', function ($dq) {
                    $dq->where('name', 'like', '%영업팀%');
                })->orWhereNull('department_id');
            })
            ->where(function ($q) {
                $q->whereNull('json_extra->is_excluded')
                    ->orWhere('json_extra->is_excluded', false);
            })
            ->orderBy('display_name')
            ->get(['id', 'user_id', 'display_name', 'department_id']);
    }

    // =========================================================================
    // 연차촉진 관리
    // =========================================================================

    /**
     * 연차촉진 대상자 조회 — 잔여 연차가 있는 직원 + 통지 발송 현황
     */
    public function getPromotionCandidates(int $year): Collection
    {
        $tenantId = session('selected_tenant_id');

        // 1. 연차 현황 조회 (잔여 연차 > 0인 직원)
        $balances = $this->getBalanceSummary($year, 'name', 'asc', 'active');

        // 2. 기존 발송된 통지서 조회 (approvals 테이블)
        $promotionApprovals = Approval::query()
            ->where('tenant_id', $tenantId)
            ->whereHas('form', function ($q) {
                $q->whereIn('code', ['leave_promotion_1st', 'leave_promotion_2nd']);
            })
            ->with('form:id,code')
            ->get(['id', 'form_id', 'content', 'status', 'created_at']);

        // 3. employee_id 기준으로 발송 현황 매핑
        $sentMap = [];
        foreach ($promotionApprovals as $approval) {
            $employeeId = $approval->content['employee_id'] ?? null;
            $noticeYear = $approval->content['year'] ?? null;
            if (! $employeeId || $noticeYear != $year) {
                continue;
            }
            $code = $approval->form?->code;
            if (! isset($sentMap[$employeeId])) {
                $sentMap[$employeeId] = ['1st' => null, '2nd' => null];
            }
            if ($code === 'leave_promotion_1st') {
                $sentMap[$employeeId]['1st'] = $approval;
            } elseif ($code === 'leave_promotion_2nd') {
                $sentMap[$employeeId]['2nd'] = $approval;
            }
        }

        // 4. 잔여일수 > 0 필터링 + 통지 상태 결합
        return $balances
            ->filter(fn ($b) => ($b->total_days - $b->used_days) > 0)
            ->map(function ($balance) use ($sentMap) {
                $userId = $balance->user_id;
                $sent = $sentMap[$userId] ?? ['1st' => null, '2nd' => null];

                $balance->first_notice = $sent['1st'];
                $balance->second_notice = $sent['2nd'];
                $balance->promotion_status = match (true) {
                    $sent['2nd'] !== null => 'completed',
                    $sent['1st'] !== null => 'first_sent',
                    default => 'not_sent',
                };

                return $balance;
            })
            ->values();
    }

    /**
     * 연차촉진 통지서 일괄 생성 (결재 문서로 생성 + 자동 상신)
     */
    public function sendPromotionNotices(array $employeeIds, string $noticeType, int $year, ?string $deadline = null, array $designatedDates = []): array
    {
        $tenantId = session('selected_tenant_id');
        $formCode = $noticeType === '1st' ? 'leave_promotion_1st' : 'leave_promotion_2nd';

        $form = ApprovalForm::where('code', $formCode)
            ->where('tenant_id', $tenantId)
            ->where('is_active', true)
            ->first();

        if (! $form) {
            throw new \RuntimeException('연차촉진 통지서 결재 양식이 등록되지 않았습니다.');
        }

        // 기본 결재선
        $line = ApprovalLine::where('tenant_id', $tenantId)
            ->where('is_default', true)
            ->first();

        if (! $line) {
            throw new \RuntimeException('기본 결재선을 설정해주세요.');
        }

        // 대상 직원 + 잔여연차 조회
        $balances = LeaveBalance::query()
            ->forTenant($tenantId)
            ->forYear($year)
            ->whereIn('user_id', $employeeIds)
            ->get()
            ->keyBy('user_id');

        $employees = Employee::query()
            ->with(['user:id,name', 'department:id,name'])
            ->forTenant($tenantId)
            ->whereIn('user_id', $employeeIds)
            ->get()
            ->keyBy('user_id');

        // 이미 발송된 직원 확인
        $existingApprovals = Approval::query()
            ->where('tenant_id', $tenantId)
            ->where('form_id', $form->id)
            ->get(['id', 'content'])
            ->filter(function ($a) use ($year) {
                return ($a->content['year'] ?? null) == $year;
            })
            ->pluck('content.employee_id')
            ->filter()
            ->toArray();

        $approvalService = app(\App\Services\ApprovalService::class);
        $steps = collect($line->steps)->map(fn ($s) => [
            'user_id' => $s['user_id'],
            'step_type' => $s['step_type'] ?? $s['type'] ?? 'approval',
        ])->toArray();

        $created = [];
        $skipped = [];

        foreach ($employeeIds as $userId) {
            // 이미 발송된 직원 스킵
            if (in_array($userId, $existingApprovals)) {
                $skipped[] = $userId;

                continue;
            }

            $employee = $employees->get($userId);
            $balance = $balances->get($userId);

            if (! $employee || ! $balance) {
                $skipped[] = $userId;

                continue;
            }

            $remaining = $balance->total_days - $balance->used_days;
            $empName = $employee->display_name ?? $employee->user?->name ?? '';
            $deptName = $employee->department?->name ?? '';

            // 통지서 content 구성
            $content = [
                'employee_id' => $userId,
                'employee_name' => $empName,
                'department' => $deptName,
                'position' => $employee->position_key ?? '',
                'year' => $year,
                'total_days' => $balance->total_days,
                'used_days' => $balance->used_days,
                'remaining_days' => $remaining,
            ];

            if ($noticeType === '1st') {
                $content['deadline'] = $deadline;
                $title = "연차촉진 1차 통지 - {$empName} ({$year}년 잔여 {$remaining}일)";
            } else {
                $content['designated_dates'] = $designatedDates;
                $title = "연차촉진 2차 통지 - {$empName} ({$year}년 잔여 {$remaining}일)";
            }

            $approval = $approvalService->createApproval([
                'form_id' => $form->id,
                'line_id' => $line->id,
                'title' => $title,
                'content' => $content,
                'body' => '',
                'is_urgent' => false,
                'steps' => $steps,
            ]);

            $approvalService->submit($approval->id);
            $created[] = $userId;
        }

        return ['created' => $created, 'skipped' => $skipped];
    }

    // =========================================================================
    // Private
    // =========================================================================

    /**
     * 휴가/근태신청/사유서 결재 자동 생성 + 상신
     */
    private function createLeaveApproval(Leave $leave, int $tenantId, ?int $approvalLineId = null): Approval
    {
        $approvalService = app(ApprovalService::class);

        // 1. 유형에 맞는 결재양식 조회 (FORM_CODE_MAP으로 동적 결정)
        $formCode = Leave::FORM_CODE_MAP[$leave->leave_type] ?? 'leave';
        $form = ApprovalForm::where('code', $formCode)
            ->where('tenant_id', $tenantId)
            ->where('is_active', true)
            ->first();

        if (! $form) {
            $formNames = ['leave' => '휴가신청', 'attendance_request' => '근태신청', 'reason_report' => '사유서'];
            $formName = $formNames[$formCode] ?? $formCode;
            throw new \RuntimeException("{$formName} 결재 양식이 등록되지 않았습니다.");
        }

        // 2. 결재선 조회: 지정된 ID 우선, 없으면 기본결재선
        $line = null;
        if ($approvalLineId) {
            $line = ApprovalLine::where('tenant_id', $tenantId)->find($approvalLineId);
        }
        if (! $line) {
            $line = ApprovalLine::where('tenant_id', $tenantId)
                ->where('is_default', true)
                ->first();
        }

        if (! $line) {
            throw new \RuntimeException('결재선을 찾을 수 없습니다. 기본결재선을 설정하거나 결재선을 선택해주세요.');
        }

        // 3. 결재 본문 생성
        $body = $this->buildLeaveApprovalBody($leave, $tenantId);

        // 4. steps 변환
        $steps = collect($line->steps)->map(fn ($s) => [
            'user_id' => $s['user_id'],
            'step_type' => $s['step_type'] ?? $s['type'] ?? 'approval',
        ])->toArray();

        // 5. 결재 제목 생성 (유형별 차별화)
        $typeName = Leave::TYPE_MAP[$leave->leave_type] ?? $leave->leave_type;
        $userName = $leave->user->name ?? '';
        $period = $leave->start_date->format('n/j').'~'.$leave->end_date->format('n/j');

        $titlePrefix = match ($formCode) {
            'attendance_request' => '근태신청',
            'reason_report' => '사유서',
            default => '휴가신청',
        };

        $approval = $approvalService->createApproval([
            'form_id' => $form->id,
            'line_id' => $line->id,
            'title' => "{$titlePrefix} - {$userName} ({$typeName} {$period})",
            'body' => $body,
            'content' => [
                'leave_id' => $leave->id,
                'user_name' => $userName,
                'leave_type' => $typeName,
                'start_date' => $leave->start_date->toDateString(),
                'end_date' => $leave->end_date->toDateString(),
                'days' => $leave->days,
                'reason' => $leave->reason,
            ],
            'is_urgent' => false,
            'steps' => $steps,
        ]);

        // 6. 자동 상신
        $approvalService->submit($approval->id);

        return $approval->fresh();
    }

    /**
     * 결재 본문 HTML 생성 (유형별 차별화)
     */
    private function buildLeaveApprovalBody(Leave $leave, int $tenantId): string
    {
        $user = $leave->user;
        $typeName = Leave::TYPE_MAP[$leave->leave_type] ?? $leave->leave_type;
        $formCode = Leave::FORM_CODE_MAP[$leave->leave_type] ?? 'leave';

        // 부서 정보
        $profile = $user?->tenantProfiles?->where('tenant_id', $tenantId)->first();
        $deptName = $profile?->department?->name ?? '';

        // 공통 행
        $rows = [['신청자', e($user->name ?? '')]];
        if ($deptName) {
            $rows[] = ['부서', e($deptName)];
        }
        $rows[] = ['유형', $typeName];

        // 사유서: 대상일 + 사유
        if ($formCode === 'reason_report') {
            $rows[] = ['대상일', $leave->start_date->format('Y-m-d')];
            if ($leave->reason) {
                $rows[] = ['사유', e($leave->reason)];
            }

            $intro = '아래와 같이 사유서를 제출합니다.';
        } else {
            // 휴가/근태신청: 기간 + 일수
            $period = $leave->start_date->format('Y-m-d').' ~ '.$leave->end_date->format('Y-m-d');
            $daysStr = ($leave->days == (int) $leave->days) ? (int) $leave->days.'일' : $leave->days.'일';
            $rows[] = ['기간', $period.' ('.$daysStr.')'];

            if ($leave->reason) {
                $rows[] = ['사유', e($leave->reason)];
            }

            // 잔여연차 (연차 차감 대상만)
            if (in_array($leave->leave_type, Leave::DEDUCTIBLE_TYPES)) {
                $balance = LeaveBalance::query()
                    ->where('tenant_id', $tenantId)
                    ->where('user_id', $leave->user_id)
                    ->where('year', now()->year)
                    ->first();

                if ($balance) {
                    $rows[] = ['잔여연차', $balance->remaining.'일 (부여: '.$balance->total_days.' / 사용: '.$balance->used_days.')'];
                }
            }

            $intro = match ($formCode) {
                'attendance_request' => '아래와 같이 근태를 신청합니다.',
                default => '아래와 같이 휴가를 신청합니다.',
            };
        }

        // HTML 테이블 생성
        $html = "<p>{$intro}</p>";
        $html .= '<table style="border-collapse:collapse; width:100%; margin-top:12px; font-size:14px;">';
        $thStyle = 'style="padding:8px 12px; background:#f8f9fa; border:1px solid #dee2e6; text-align:left; width:120px; font-weight:600;"';
        $tdStyle = 'style="padding:8px 12px; border:1px solid #dee2e6;"';
        foreach ($rows as [$label, $value]) {
            $html .= "<tr><th {$thStyle}>{$label}</th><td {$tdStyle}>{$value}</td></tr>";
        }
        $html .= '</table>';

        return $html;
    }

    /**
     * 승인 시 기간 내 영업일마다 Attendance 자동 생성
     */
    private function createAttendanceRecords(Leave $leave, int $tenantId, string $status = 'vacation'): void
    {
        $period = CarbonPeriod::create($leave->start_date, $leave->end_date);

        foreach ($period as $date) {
            if ($date->isWeekend()) {
                continue;
            }

            Attendance::updateOrCreate(
                [
                    'tenant_id' => $tenantId,
                    'user_id' => $leave->user_id,
                    'base_date' => $date->toDateString(),
                ],
                [
                    'status' => $status,
                    'remarks' => $leave->reason ? mb_substr($leave->reason, 0, 100) : null,
                    'updated_by' => auth()->id(),
                ]
            );
        }
    }

    /**
     * 취소 시 해당 기간 vacation Attendance soft delete
     */
    private function deleteAttendanceRecords(Leave $leave, int $tenantId): void
    {
        Attendance::query()
            ->where('tenant_id', $tenantId)
            ->where('user_id', $leave->user_id)
            ->where('status', 'vacation')
            ->whereBetween('base_date', [
                $leave->start_date->toDateString(),
                $leave->end_date->toDateString(),
            ])
            ->update(['deleted_by' => auth()->id()]);

        Attendance::query()
            ->where('tenant_id', $tenantId)
            ->where('user_id', $leave->user_id)
            ->where('status', 'vacation')
            ->whereBetween('base_date', [
                $leave->start_date->toDateString(),
                $leave->end_date->toDateString(),
            ])
            ->delete();
    }
}
