<?php

namespace App\Services;

use App\Models\Tenants\Attendance;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;

class AttendanceService extends Service
{
    /**
     * 근태 목록 조회
     */
    public function index(array $params): LengthAwarePaginator
    {
        $tenantId = $this->tenantId();

        $query = Attendance::query()
            ->where('tenant_id', $tenantId)
            ->with([
                'user:id,name,email',
                'user.tenantProfiles' => function ($q) use ($tenantId) {
                    $q->where('tenant_id', $tenantId)
                        ->with('department:id,name');
                },
            ]);

        // 사용자 필터
        if (! empty($params['user_id'])) {
            $query->where('user_id', $params['user_id']);
        }

        // 날짜 필터 (단일)
        if (! empty($params['date'])) {
            $query->whereDate('base_date', $params['date']);
        }

        // 날짜 범위 필터
        if (! empty($params['date_from'])) {
            $query->whereDate('base_date', '>=', $params['date_from']);
        }
        if (! empty($params['date_to'])) {
            $query->whereDate('base_date', '<=', $params['date_to']);
        }

        // 상태 필터
        if (! empty($params['status'])) {
            $query->where('status', $params['status']);
        }

        // 부서 필터 (사용자의 부서)
        if (! empty($params['department_id'])) {
            $query->whereHas('user.tenantProfile', function ($q) use ($params) {
                $q->where('department_id', $params['department_id']);
            });
        }

        // 정렬
        $sortBy = $params['sort_by'] ?? 'base_date';
        $sortDir = $params['sort_dir'] ?? 'desc';
        $query->orderBy($sortBy, $sortDir);

        // 페이지네이션
        $perPage = $params['per_page'] ?? 20;

        return $query->paginate($perPage);
    }

    /**
     * 근태 상세 조회
     */
    public function show(int $id): Attendance
    {
        $tenantId = $this->tenantId();

        $attendance = Attendance::query()
            ->where('tenant_id', $tenantId)
            ->with([
                'user:id,name,email',
                'user.tenantProfiles' => function ($q) use ($tenantId) {
                    $q->where('tenant_id', $tenantId)
                        ->with('department:id,name');
                },
            ])
            ->findOrFail($id);

        return $attendance;
    }

    /**
     * 근태 등록 (Upsert)
     * - 같은 날 같은 사용자의 기록이 있으면 업데이트
     * - 없으면 새로 생성
     */
    public function store(array $data): Attendance
    {
        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        return DB::transaction(function () use ($data, $tenantId, $userId) {
            // 기존 기록 확인 (같은 날 같은 사용자)
            $existing = Attendance::query()
                ->where('tenant_id', $tenantId)
                ->where('user_id', $data['user_id'])
                ->whereDate('base_date', $data['base_date'])
                ->first();

            // json_details 구성
            $jsonDetails = isset($data['json_details']) && is_array($data['json_details'])
                ? $data['json_details']
                : $this->buildJsonDetails($data);

            if ($existing) {
                // 기존 기록 업데이트 (Upsert)
                $existing->status = $data['status'] ?? $existing->status;
                $existing->json_details = array_merge($existing->json_details ?? [], $jsonDetails);
                if (array_key_exists('remarks', $data)) {
                    $existing->remarks = $data['remarks'];
                }
                $existing->updated_by = $userId;
                $existing->save();

                return $existing->fresh(['user:id,name,email']);
            }

            // 새 기록 생성
            $attendance = Attendance::create([
                'tenant_id' => $tenantId,
                'user_id' => $data['user_id'],
                'base_date' => $data['base_date'],
                'status' => $data['status'] ?? 'onTime',
                'json_details' => $jsonDetails,
                'remarks' => $data['remarks'] ?? null,
                'created_by' => $userId,
                'updated_by' => $userId,
            ]);

            return $attendance->fresh(['user:id,name,email']);
        });
    }

    /**
     * 근태 수정
     */
    public function update(int $id, array $data): Attendance
    {
        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        return DB::transaction(function () use ($id, $data, $tenantId, $userId) {
            $attendance = Attendance::query()
                ->where('tenant_id', $tenantId)
                ->findOrFail($id);

            // 기본 필드 업데이트
            if (isset($data['status'])) {
                $attendance->status = $data['status'];
            }
            if (array_key_exists('remarks', $data)) {
                $attendance->remarks = $data['remarks'];
            }

            // json_details 업데이트
            $jsonDetails = $attendance->json_details ?? [];
            // json_details 객체가 직접 전달된 경우 그대로 병합, 아니면 개별 필드에서 구성
            $detailsUpdate = isset($data['json_details']) && is_array($data['json_details'])
                ? $data['json_details']
                : $this->buildJsonDetails($data);
            $attendance->json_details = array_merge($jsonDetails, $detailsUpdate);

            $attendance->updated_by = $userId;
            $attendance->save();

            return $attendance->fresh(['user:id,name,email']);
        });
    }

    /**
     * 근태 삭제
     */
    public function destroy(int $id): bool
    {
        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        $attendance = Attendance::query()
            ->where('tenant_id', $tenantId)
            ->findOrFail($id);

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

        return true;
    }

    /**
     * 일괄 삭제
     */
    public function bulkDelete(array $ids): array
    {
        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        $deletedCount = 0;

        DB::transaction(function () use ($ids, $tenantId, $userId, &$deletedCount) {
            $attendances = Attendance::query()
                ->where('tenant_id', $tenantId)
                ->whereIn('id', $ids)
                ->get();

            foreach ($attendances as $attendance) {
                $attendance->deleted_by = $userId;
                $attendance->save();
                $attendance->delete();
                $deletedCount++;
            }
        });

        return ['deleted_count' => $deletedCount];
    }

    /**
     * 출근 기록 (체크인)
     * - 모든 출근 기록을 check_ins 배열에 히스토리로 저장
     * - check_in accessor는 가장 빠른 출근 시간 반환
     */
    public function checkIn(array $data): Attendance
    {
        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();
        $targetUserId = $data['user_id'] ?? $userId;
        $today = now()->toDateString();

        return DB::transaction(function () use ($data, $tenantId, $userId, $targetUserId, $today) {
            // 오늘 기록 확인
            $attendance = Attendance::query()
                ->where('tenant_id', $tenantId)
                ->where('user_id', $targetUserId)
                ->whereDate('base_date', $today)
                ->first();

            $checkInTime = $data['check_in'] ?? now()->format('H:i:s');
            $gpsData = $data['gps_data'] ?? null;

            // 출근 엔트리 생성
            $entry = [
                'time' => $checkInTime,
                'recorded_at' => now()->toIso8601String(),
            ];
            if ($gpsData) {
                $entry['gps'] = $gpsData;
            }

            if ($attendance) {
                // 기존 기록에 출근 추가
                $jsonDetails = $attendance->json_details ?? [];
                $checkIns = $jsonDetails['check_ins'] ?? [];
                $checkIns[] = $entry;
                $jsonDetails['check_ins'] = $checkIns;

                $attendance->json_details = $jsonDetails;
                $attendance->status = $this->determineStatusFromEntries($jsonDetails);
                $attendance->updated_by = $userId;
                $attendance->save();
            } else {
                // 새 기록 생성
                $jsonDetails = [
                    'check_ins' => [$entry],
                    'check_outs' => [],
                ];

                $attendance = Attendance::create([
                    'tenant_id' => $tenantId,
                    'user_id' => $targetUserId,
                    'base_date' => $today,
                    'status' => $this->determineStatus($checkInTime, null),
                    'json_details' => $jsonDetails,
                    'created_by' => $userId,
                    'updated_by' => $userId,
                ]);
            }

            return $attendance->fresh(['user:id,name,email']);
        });
    }

    /**
     * 퇴근 기록 (체크아웃)
     * - 모든 퇴근 기록을 check_outs 배열에 히스토리로 저장
     * - check_out accessor는 가장 늦은 퇴근 시간 반환
     */
    public function checkOut(array $data): Attendance
    {
        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();
        $targetUserId = $data['user_id'] ?? $userId;
        $today = now()->toDateString();

        return DB::transaction(function () use ($data, $tenantId, $userId, $targetUserId, $today) {
            $attendance = Attendance::query()
                ->where('tenant_id', $tenantId)
                ->where('user_id', $targetUserId)
                ->whereDate('base_date', $today)
                ->first();

            if (! $attendance) {
                throw new \Exception(__('error.attendance.no_check_in'));
            }

            $checkOutTime = $data['check_out'] ?? now()->format('H:i:s');
            $gpsData = $data['gps_data'] ?? null;

            // 퇴근 엔트리 생성
            $entry = [
                'time' => $checkOutTime,
                'recorded_at' => now()->toIso8601String(),
            ];
            if ($gpsData) {
                $entry['gps'] = $gpsData;
            }

            $jsonDetails = $attendance->json_details ?? [];
            $checkOuts = $jsonDetails['check_outs'] ?? [];
            $checkOuts[] = $entry;
            $jsonDetails['check_outs'] = $checkOuts;

            // 근무 시간 계산 (가장 빠른 출근 ~ 가장 늦은 퇴근)
            $checkIns = $jsonDetails['check_ins'] ?? [];
            if (! empty($checkIns)) {
                $earliestIn = $this->getEarliestTime($checkIns);
                $latestOut = $this->getLatestTime($checkOuts);
                if ($earliestIn && $latestOut) {
                    $checkInCarbon = \Carbon\Carbon::createFromFormat('H:i:s', $earliestIn);
                    $checkOutCarbon = \Carbon\Carbon::createFromFormat('H:i:s', $latestOut);
                    $totalMinutes = $checkOutCarbon->diffInMinutes($checkInCarbon);

                    // 휴게시간 계산 (근무 설정에서 조회)
                    $workSettingService = app(WorkSettingService::class);
                    $workSetting = $workSettingService->getWorkSetting();
                    $breakMinutes = 0;

                    // 설정된 휴게시간이 있으면 적용
                    if ($workSetting->break_start && $workSetting->break_end) {
                        $breakStart = \Carbon\Carbon::createFromFormat('H:i:s', $workSetting->break_start);
                        $breakEnd = \Carbon\Carbon::createFromFormat('H:i:s', $workSetting->break_end);

                        // 출근~퇴근 시간이 휴게시간을 포함하면 휴게시간 적용
                        if ($checkInCarbon->lte($breakStart) && $checkOutCarbon->gte($breakEnd)) {
                            $breakMinutes = $breakEnd->diffInMinutes($breakStart);
                            $jsonDetails['break_minutes'] = $breakMinutes;
                        }
                    }

                    // 실제 근무시간 = 총 시간 - 휴게시간
                    $jsonDetails['work_minutes'] = $totalMinutes - $breakMinutes;
                }
            }

            $attendance->json_details = $jsonDetails;
            $attendance->status = $this->determineStatusFromEntries($jsonDetails);
            $attendance->updated_by = $userId;
            $attendance->save();

            return $attendance->fresh(['user:id,name,email']);
        });
    }

    /**
     * 엑셀 내보내기용 데이터 조회
     *
     * @return array{data: array<int, array<string, mixed>>, headings: array<int, string>}
     */
    public function getExportData(array $params): array
    {
        $tenantId = $this->tenantId();

        $query = Attendance::query()
            ->where('tenant_id', $tenantId)
            ->with([
                'user:id,name,email',
                'user.tenantProfiles' => function ($q) use ($tenantId) {
                    $q->where('tenant_id', $tenantId)
                        ->with('department:id,name');
                },
            ]);

        // 사용자 필터
        if (! empty($params['user_id'])) {
            $query->where('user_id', $params['user_id']);
        }

        // 날짜 필터 (단일)
        if (! empty($params['date'])) {
            $query->whereDate('base_date', $params['date']);
        }

        // 날짜 범위 필터
        if (! empty($params['date_from'])) {
            $query->whereDate('base_date', '>=', $params['date_from']);
        }
        if (! empty($params['date_to'])) {
            $query->whereDate('base_date', '<=', $params['date_to']);
        }

        // 상태 필터
        if (! empty($params['status'])) {
            $query->where('status', $params['status']);
        }

        // 부서 필터
        if (! empty($params['department_id'])) {
            $query->whereHas('user.tenantProfile', function ($q) use ($params) {
                $q->where('department_id', $params['department_id']);
            });
        }

        // 정렬
        $sortBy = $params['sort_by'] ?? 'base_date';
        $sortDir = $params['sort_dir'] ?? 'desc';
        $query->orderBy($sortBy, $sortDir);

        $attendances = $query->get();

        // 상태 레이블 매핑
        $statusLabels = [
            'onTime' => '정상출근',
            'late' => '지각',
            'absent' => '결근',
            'vacation' => '휴가',
            'businessTrip' => '출장',
            'fieldWork' => '외근',
            'overtime' => '야근',
            'remote' => '재택',
        ];

        // 엑셀 데이터 변환
        $data = $attendances->map(function ($attendance) use ($statusLabels) {
            $profile = $attendance->user?->tenantProfiles?->first();
            $jsonDetails = $attendance->json_details ?? [];

            return [
                $attendance->base_date,
                $attendance->user?->name ?? '-',
                $profile?->department?->name ?? '-',
                $statusLabels[$attendance->status] ?? $attendance->status,
                $attendance->check_in ?? '-',
                $attendance->check_out ?? '-',
                isset($jsonDetails['work_minutes']) ? round($jsonDetails['work_minutes'] / 60, 1) : '-',
                $attendance->remarks ?? '',
            ];
        })->toArray();

        $headings = [
            '날짜',
            '직원명',
            '부서',
            '상태',
            '출근시간',
            '퇴근시간',
            '근무시간(h)',
            '비고',
        ];

        return [
            'data' => $data,
            'headings' => $headings,
        ];
    }

    /**
     * 월간 통계 조회
     */
    public function monthlyStats(array $params): array
    {
        $tenantId = $this->tenantId();
        $year = $params['year'] ?? now()->year;
        $month = $params['month'] ?? now()->month;
        $userId = $params['user_id'] ?? null;

        $startDate = \Carbon\Carbon::create($year, $month, 1)->startOfMonth();
        $endDate = $startDate->copy()->endOfMonth();

        $query = Attendance::query()
            ->where('tenant_id', $tenantId)
            ->whereBetween('base_date', [$startDate, $endDate]);

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

        $attendances = $query->get();

        $stats = [
            'year' => $year,
            'month' => $month,
            'total_days' => $attendances->count(),
            'by_status' => [
                'onTime' => $attendances->where('status', 'onTime')->count(),
                'late' => $attendances->where('status', 'late')->count(),
                'absent' => $attendances->where('status', 'absent')->count(),
                'vacation' => $attendances->where('status', 'vacation')->count(),
                'businessTrip' => $attendances->where('status', 'businessTrip')->count(),
                'fieldWork' => $attendances->where('status', 'fieldWork')->count(),
                'overtime' => $attendances->where('status', 'overtime')->count(),
                'remote' => $attendances->where('status', 'remote')->count(),
            ],
            'total_work_minutes' => $attendances->sum(function ($a) {
                return $a->json_details['work_minutes'] ?? 0;
            }),
            'total_overtime_minutes' => $attendances->sum(function ($a) {
                return $a->json_details['overtime_minutes'] ?? 0;
            }),
        ];

        return $stats;
    }

    /**
     * json_details 구성
     */
    private function buildJsonDetails(array $data): array
    {
        $details = [];

        $detailKeys = [
            'check_in',
            'check_out',
            'gps_data',
            'external_work',
            'multiple_entries',
            'work_minutes',
            'overtime_minutes',
            'late_minutes',
            'early_leave_minutes',
            'vacation_type',
        ];

        foreach ($detailKeys as $key) {
            if (isset($data[$key])) {
                $details[$key] = $data[$key];
            }
        }

        return $details;
    }

    /**
     * 상태 자동 결정
     * 근무 설정의 출근 시간 기준으로 지각 여부 판단
     * 출근 시간 설정이 없으면 지각 개념 없음 (항상 정시)
     */
    private function determineStatus(?string $checkIn, ?string $checkOut): string
    {
        if (! $checkIn) {
            return 'absent';
        }

        // 근무 설정에서 출근 시간 조회
        $workSettingService = app(WorkSettingService::class);
        $workSetting = $workSettingService->getWorkSetting();

        // 출근 시간 설정이 없으면 지각 판정 안함
        if (! $workSetting->start_time) {
            return 'onTime';
        }

        if ($checkIn > $workSetting->start_time) {
            return 'late';
        }

        return 'onTime';
    }

    /**
     * 엔트리 기반 상태 결정
     * check_ins 배열에서 가장 빠른 시간을 기준으로 상태 판단
     */
    private function determineStatusFromEntries(array $jsonDetails): string
    {
        $checkIns = $jsonDetails['check_ins'] ?? [];
        $checkOuts = $jsonDetails['check_outs'] ?? [];

        if (empty($checkIns)) {
            return 'absent';
        }

        $earliestIn = $this->getEarliestTime($checkIns);
        $latestOut = ! empty($checkOuts) ? $this->getLatestTime($checkOuts) : null;

        return $this->determineStatus($earliestIn, $latestOut);
    }

    /**
     * 엔트리 배열에서 가장 빠른 시간 추출
     */
    private function getEarliestTime(array $entries): ?string
    {
        if (empty($entries)) {
            return null;
        }

        $times = array_map(function ($entry) {
            return $entry['time'] ?? null;
        }, $entries);

        $times = array_filter($times);

        if (empty($times)) {
            return null;
        }

        sort($times);

        return $times[0];
    }

    /**
     * 엔트리 배열에서 가장 늦은 시간 추출
     */
    private function getLatestTime(array $entries): ?string
    {
        if (empty($entries)) {
            return null;
        }

        $times = array_map(function ($entry) {
            return $entry['time'] ?? null;
        }, $entries);

        $times = array_filter($times);

        if (empty($times)) {
            return null;
        }

        rsort($times);

        return $times[0];
    }
}
