<?php

namespace App\Services\HR;

use App\Models\HR\Attendance;
use App\Models\HR\Employee;
use App\Models\HR\LeaveBalance;
use App\Models\Tenants\Department;
use Carbon\Carbon;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;

class AttendanceService
{
    /**
     * 필터 적용 쿼리 생성 (목록/엑셀 공통)
     */
    private function buildFilteredQuery(array $filters = [])
    {
        $tenantId = session('selected_tenant_id');

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

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

        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['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->whereDate('base_date', '>=', $filters['date_from']);
        } elseif (! empty($filters['date_to'])) {
            $query->whereDate('base_date', '<=', $filters['date_to']);
        }

        return $query->orderBy('base_date', 'desc')->orderBy('created_at', 'desc');
    }

    /**
     * 근태 목록 조회 (페이지네이션)
     */
    public function getAttendances(array $filters = [], int $perPage = 20): LengthAwarePaginator
    {
        return $this->buildFilteredQuery($filters)->paginate($perPage);
    }

    /**
     * 엑셀 내보내기용 데이터 (전체)
     */
    public function getExportData(array $filters = []): Collection
    {
        return $this->buildFilteredQuery($filters)->get();
    }

    /**
     * 월간 통계 (상태별 카운트)
     */
    public function getMonthlyStats(?int $year = null, ?int $month = null): array
    {
        $tenantId = session('selected_tenant_id');
        $year = $year ?? now()->year;
        $month = $month ?? now()->month;

        $startDate = sprintf('%04d-%02d-01', $year, $month);
        $endDate = now()->year == $year && now()->month == $month
            ? now()->toDateString()
            : Carbon::create($year, $month, 1)->endOfMonth()->toDateString();

        $counts = Attendance::query()
            ->forTenant($tenantId)
            ->betweenDates($startDate, $endDate)
            ->select('status', DB::raw('COUNT(*) as cnt'))
            ->groupBy('status')
            ->pluck('cnt', 'status')
            ->toArray();

        return [
            'onTime' => $counts['onTime'] ?? 0,
            'late' => $counts['late'] ?? 0,
            'absent' => $counts['absent'] ?? 0,
            'vacation' => $counts['vacation'] ?? 0,
            'etc' => ($counts['businessTrip'] ?? 0) + ($counts['fieldWork'] ?? 0) + ($counts['overtime'] ?? 0) + ($counts['remote'] ?? 0),
            'year' => $year,
            'month' => $month,
        ];
    }

    /**
     * 근태 등록 (Upsert: tenant_id + user_id + base_date)
     */
    public function storeAttendance(array $data): Attendance
    {
        $tenantId = session('selected_tenant_id');

        return DB::transaction(function () use ($data, $tenantId) {
            $jsonDetails = [];
            if (! empty($data['check_in'])) {
                $jsonDetails['check_in'] = $data['check_in'];
            }
            if (! empty($data['check_out'])) {
                $jsonDetails['check_out'] = $data['check_out'];
            }

            // 근무 시간 자동 계산
            if (! empty($data['check_in']) && ! empty($data['check_out'])) {
                $in = Carbon::createFromFormat('H:i', $data['check_in']);
                $out = Carbon::createFromFormat('H:i', $data['check_out']);
                if ($out->gt($in)) {
                    $jsonDetails['work_minutes'] = $out->diffInMinutes($in);
                }
            }

            $attendance = Attendance::updateOrCreate(
                [
                    'tenant_id' => $tenantId,
                    'user_id' => $data['user_id'],
                    'base_date' => $data['base_date'],
                ],
                [
                    'status' => $data['status'] ?? 'onTime',
                    'json_details' => ! empty($jsonDetails) ? $jsonDetails : null,
                    'remarks' => $data['remarks'] ?? null,
                    'updated_by' => auth()->id(),
                ]
            );

            if ($attendance->wasRecentlyCreated) {
                $attendance->update(['created_by' => auth()->id()]);
            }

            // 휴가 상태이면 연차 차감
            if (($data['status'] ?? '') === 'vacation') {
                $this->deductLeaveBalance($tenantId, $data['user_id']);
            }

            return $attendance->load('user');
        });
    }

    /**
     * 일괄 등록
     */
    public function bulkStore(array $data): array
    {
        $tenantId = session('selected_tenant_id');
        $created = 0;
        $updated = 0;

        DB::transaction(function () use ($data, $tenantId, &$created, &$updated) {
            $jsonDetails = [];
            if (! empty($data['check_in'])) {
                $jsonDetails['check_in'] = $data['check_in'];
            }
            if (! empty($data['check_out'])) {
                $jsonDetails['check_out'] = $data['check_out'];
            }

            if (! empty($data['check_in']) && ! empty($data['check_out'])) {
                $in = Carbon::createFromFormat('H:i', $data['check_in']);
                $out = Carbon::createFromFormat('H:i', $data['check_out']);
                if ($out->gt($in)) {
                    $jsonDetails['work_minutes'] = $out->diffInMinutes($in);
                }
            }

            foreach ($data['user_ids'] as $userId) {
                $attendance = Attendance::updateOrCreate(
                    [
                        'tenant_id' => $tenantId,
                        'user_id' => $userId,
                        'base_date' => $data['base_date'],
                    ],
                    [
                        'status' => $data['status'] ?? 'onTime',
                        'json_details' => ! empty($jsonDetails) ? $jsonDetails : null,
                        'remarks' => $data['remarks'] ?? null,
                        'updated_by' => auth()->id(),
                    ]
                );

                if ($attendance->wasRecentlyCreated) {
                    $attendance->update(['created_by' => auth()->id()]);
                    $created++;
                } else {
                    $updated++;
                }

                if (($data['status'] ?? '') === 'vacation') {
                    $this->deductLeaveBalance($tenantId, $userId);
                }
            }
        });

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

    /**
     * 근태 수정
     */
    public function updateAttendance(int $id, array $data): ?Attendance
    {
        $tenantId = session('selected_tenant_id');

        $attendance = Attendance::query()
            ->forTenant($tenantId)
            ->find($id);

        if (! $attendance) {
            return null;
        }

        $updateData = [];

        if (array_key_exists('status', $data)) {
            $updateData['status'] = $data['status'];
        }
        if (array_key_exists('remarks', $data)) {
            $updateData['remarks'] = $data['remarks'];
        }

        // json_details 업데이트
        $jsonDetails = $attendance->json_details ?? [];
        if (array_key_exists('check_in', $data)) {
            if ($data['check_in']) {
                $jsonDetails['check_in'] = $data['check_in'];
            } else {
                unset($jsonDetails['check_in']);
            }
        }
        if (array_key_exists('check_out', $data)) {
            if ($data['check_out']) {
                $jsonDetails['check_out'] = $data['check_out'];
            } else {
                unset($jsonDetails['check_out']);
            }
        }

        // 근무 시간 재계산
        $checkIn = $jsonDetails['check_in'] ?? null;
        $checkOut = $jsonDetails['check_out'] ?? null;
        if ($checkIn && $checkOut) {
            $in = Carbon::createFromFormat('H:i', $checkIn);
            $out = Carbon::createFromFormat('H:i', $checkOut);
            if ($out->gt($in)) {
                $jsonDetails['work_minutes'] = $out->diffInMinutes($in);
            }
        } else {
            unset($jsonDetails['work_minutes']);
        }

        $updateData['json_details'] = ! empty($jsonDetails) ? $jsonDetails : null;
        $updateData['updated_by'] = auth()->id();

        $attendance->update($updateData);

        return $attendance->fresh('user');
    }

    /**
     * 근태 삭제
     */
    public function deleteAttendance(int $id): bool
    {
        $tenantId = session('selected_tenant_id');

        $attendance = Attendance::query()
            ->forTenant($tenantId)
            ->find($id);

        if (! $attendance) {
            return false;
        }

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

        return true;
    }

    /**
     * 일괄 삭제
     */
    public function bulkDelete(array $ids): int
    {
        $tenantId = session('selected_tenant_id');

        $attendances = Attendance::query()
            ->forTenant($tenantId)
            ->whereIn('id', $ids)
            ->get();

        $count = 0;
        foreach ($attendances as $attendance) {
            $attendance->update(['deleted_by' => auth()->id()]);
            $attendance->delete();
            $count++;
        }

        return $count;
    }

    /**
     * 월간 캘린더 데이터 (base_date 기준 그룹화)
     */
    public function getMonthlyCalendarData(int $year, int $month, ?int $userId = null): Collection
    {
        $tenantId = session('selected_tenant_id');

        $startDate = sprintf('%04d-%02d-01', $year, $month);
        $endDate = Carbon::create($year, $month, 1)->endOfMonth()->toDateString();

        $query = Attendance::query()
            ->with(['user', 'user.tenantProfiles' => fn ($q) => $q->where('tenant_id', $tenantId), 'user.tenantProfiles.department'])
            ->forTenant($tenantId)
            ->betweenDates($startDate, $endDate)
            ->orderBy('base_date')
            ->orderBy('user_id');

        if ($userId) {
            $query->where('user_id', $userId);
        }

        return $query->get();
    }

    /**
     * 사원별 월간 요약
     */
    public function getEmployeeMonthlySummary(int $year, int $month): array
    {
        $tenantId = session('selected_tenant_id');

        $startDate = sprintf('%04d-%02d-01', $year, $month);
        $endDate = Carbon::create($year, $month, 1)->endOfMonth()->toDateString();

        $raw = Attendance::query()
            ->forTenant($tenantId)
            ->betweenDates($startDate, $endDate)
            ->select(
                'user_id',
                'status',
                DB::raw('COUNT(*) as cnt'),
                DB::raw("SUM(GREATEST(0, COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(json_details, '$.work_minutes')) AS SIGNED), 0))) as total_minutes")
            )
            ->groupBy('user_id', 'status')
            ->get();

        $summary = [];
        foreach ($raw as $row) {
            if (! isset($summary[$row->user_id])) {
                $summary[$row->user_id] = [
                    'user_id' => $row->user_id,
                    'total_days' => 0,
                    'total_minutes' => 0,
                    'statuses' => [],
                ];
            }
            $summary[$row->user_id]['total_days'] += $row->cnt;
            $summary[$row->user_id]['total_minutes'] += (int) $row->total_minutes;
            $summary[$row->user_id]['statuses'][$row->status] = $row->cnt;
        }

        // 사원 정보 가져오기
        $employees = Employee::query()
            ->with(['user:id,name', 'department:id,name'])
            ->forTenant($tenantId)
            ->activeEmployees()
            ->get()
            ->keyBy('user_id');

        foreach ($summary as &$item) {
            $emp = $employees[$item['user_id']] ?? null;
            $item['name'] = $emp?->display_name ?? $emp?->user?->name ?? '-';
            $item['department'] = $emp?->department?->name ?? '-';
        }

        return array_values($summary);
    }

    /**
     * 초과근무 알림 (이번 주 기준)
     */
    public function getOvertimeAlerts(): array
    {
        $tenantId = session('selected_tenant_id');

        $weekStart = now()->startOfWeek(Carbon::MONDAY)->toDateString();
        $weekEnd = now()->endOfWeek(Carbon::SUNDAY)->toDateString();

        $results = Attendance::query()
            ->forTenant($tenantId)
            ->betweenDates($weekStart, $weekEnd)
            ->select(
                'user_id',
                DB::raw("SUM(GREATEST(0, COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(json_details, '$.work_minutes')) AS SIGNED), 0))) as week_minutes")
            )
            ->groupBy('user_id')
            ->having('week_minutes', '>=', 2880) // 48시간 = 2880분
            ->get();

        $alerts = [];
        if ($results->isNotEmpty()) {
            $employees = Employee::query()
                ->with(['user:id,name'])
                ->forTenant($tenantId)
                ->activeEmployees()
                ->get()
                ->keyBy('user_id');

            foreach ($results as $row) {
                $emp = $employees[$row->user_id] ?? null;
                $hours = round($row->week_minutes / 60, 1);
                $alerts[] = [
                    'user_id' => $row->user_id,
                    'name' => $emp?->display_name ?? $emp?->user?->name ?? '-',
                    'hours' => $hours,
                    'level' => $row->week_minutes >= 3120 ? 'danger' : 'warning', // 52h = 3120분
                ];
            }
        }

        return $alerts;
    }

    /**
     * 자동 결근 처리 (영업일에 출근 기록 없는 사원)
     */
    public function markAbsentees(?string $date = null): int
    {
        $date = $date ?? now()->toDateString();
        $carbonDate = Carbon::parse($date);

        // 주말이면 스킵
        if ($carbonDate->isWeekend()) {
            return 0;
        }

        $count = 0;

        // 모든 테넌트의 활성 사원 조회
        $tenantIds = DB::table('tenants')->pluck('id');

        foreach ($tenantIds as $tenantId) {
            $activeUserIds = Employee::query()
                ->where('tenant_id', $tenantId)
                ->activeEmployees()
                ->pluck('user_id')
                ->toArray();

            if (empty($activeUserIds)) {
                continue;
            }

            // 이미 기록이 있는 사원 제외
            $existingUserIds = Attendance::query()
                ->where('tenant_id', $tenantId)
                ->whereDate('base_date', $date)
                ->pluck('user_id')
                ->toArray();

            $absentUserIds = array_diff($activeUserIds, $existingUserIds);

            foreach ($absentUserIds as $userId) {
                Attendance::create([
                    'tenant_id' => $tenantId,
                    'user_id' => $userId,
                    'base_date' => $date,
                    'status' => 'absent',
                    'remarks' => '자동 결근 처리',
                    'created_by' => null,
                ]);
                $count++;
            }
        }

        return $count;
    }

    /**
     * 연차 잔여 조회
     */
    public function getLeaveBalance(int $userId): ?LeaveBalance
    {
        $tenantId = session('selected_tenant_id');
        $year = now()->year;

        return LeaveBalance::query()
            ->where('tenant_id', $tenantId)
            ->where('user_id', $userId)
            ->where('year', $year)
            ->first();
    }

    /**
     * 연차 차감 (remaining_days는 stored generated이므로 used_days만 업데이트)
     */
    private function deductLeaveBalance(int $tenantId, int $userId): void
    {
        $year = now()->year;

        $balance = LeaveBalance::query()
            ->where('tenant_id', $tenantId)
            ->where('user_id', $userId)
            ->where('year', $year)
            ->first();

        if ($balance && $balance->remaining_days > 0) {
            $balance->update([
                'used_days' => $balance->used_days + 1,
            ]);
        }
    }

    /**
     * 부서 목록 (드롭다운용)
     */
    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))
            ->orderBy('sort_order')
            ->orderBy('name')
            ->get(['id', 'name', 'code']);
    }

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

        return Employee::query()
            ->with('user:id,name')
            ->forTenant($tenantId)
            ->activeEmployees()
            ->orderBy('display_name')
            ->get(['id', 'user_id', 'display_name', 'department_id']);
    }
}
