<?php

namespace App\Http\Controllers\Mobile;

use App\Events\AttendanceUpdated;
use App\Events\QrTokenIssued;
use App\Http\Controllers\Controller;
use App\Models\Attendance;
use App\Models\AttendanceSetting;
use App\Models\User;
use App\Services\DynamicQrService;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class AbsenController extends Controller
{
    private function qrTtl(): int
    {
        return 60;
    }

    public function verify(Request $req, DynamicQrService $svc)
    {
        $req->validate([
            'token'       => 'required|string',
            'user_id'     => 'required|integer',
            'device_info' => 'nullable|string',
            'latitude'    => 'nullable|numeric|between:-90,90',
            'longitude'   => 'nullable|numeric|between:-180,180',
        ]);
        $lat = $req->input('latitude');
        $lng = $req->input('longitude');

        $location = (is_numeric($lat) && is_numeric($lng))
            ? sprintf('%.6f,%.6f', $lat, $lng)
            : null;

        $check = $svc->verifyShortToken($req->token);
        if (!$check['ok']) {
            // Token invalid/expired - tetap generate token baru untuk keamanan
            $payload = $check['payload'] ?? null;
            $sessionId = $payload ? (int) ($payload['session_id'] ?? 0) : 0;

            if ($sessionId > 0) {
                $this->generateNewToken($svc, $sessionId, $this->qrTtl());
            }

            return response()->json(['ok' => false, 'reason' => $check['reason']], 422);
        }

        $payload = $check['payload'];
        $sessionId = (int) ($payload['session_id'] ?? 0);
        $nonce = $payload['nonce'];

        $ttlRemaining = max(1, (int)($payload['exp'] - now()->timestamp));
        if (!Cache::add("used_nonce:$nonce", 1, $ttlRemaining)) {
            $this->generateNewToken($svc, $sessionId, $this->qrTtl());
            return response()->json(['ok' => false, 'reason' => 'replayed'], 409);
        }

        // Simpan data absensi ke database
        $userId = $req->integer('user_id');
        $user = User::find($userId);

        if (!$user) {
            return response()->json(['ok' => false, 'reason' => 'User not found'], 404);
        }
        $setting = AttendanceSetting::query()->first();
        if (!$setting) {
            return response()->json([
                'ok' => false,
                'reason' => 'attendance_setting_not_found',
                'message' => 'Pengaturan absensi belum tersedia. Jalankan seeder terlebih dahulu.',
            ], 500);
        }
        $timezone = $setting->timezone ?: config('app.timezone', 'Asia/Jakarta');
        $now = Carbon::now($timezone);
        $today = $now->toDateString();

        $officeLat = $setting->office_latitude;
        $officeLng = $setting->office_longitude;
        $radiusMeters = (int) ($setting->attendance_radius_meters ?? 0);

        if ($officeLat !== null && $officeLng !== null && $radiusMeters > 0) {
            if (!is_numeric($lat) || !is_numeric($lng)) {
                return response()->json([
                    'ok' => false,
                    'reason' => 'location_required',
                    'message' => 'Lokasi wajib dikirim untuk melakukan absensi.',
                ], 422);
            }

            $distanceMeters = $this->distanceInMeters(
                (float) $officeLat,
                (float) $officeLng,
                (float) $lat,
                (float) $lng
            );

            if ($distanceMeters > $radiusMeters) {
                return response()->json([
                    'ok' => false,
                    'reason' => 'outside_attendance_radius',
                    'message' => 'Anda berada di luar radius absensi yang diizinkan.',
                    'meta' => [
                        'distance_meters' => round($distanceMeters, 2),
                        'allowed_radius_meters' => $radiusMeters,
                    ],
                ], 422);
            }
        }

        $effectiveWorkdays = collect($setting->effective_workdays ?? [])
            ->map(fn($day) => (int) $day)
            ->unique()
            ->values();

        if ($effectiveWorkdays->isNotEmpty() && !$effectiveWorkdays->contains($now->isoWeekday())) {
            return response()->json([
                'ok' => false,
                'reason' => 'not_effective_workday',
                'message' => 'Hari ini bukan hari kerja efektif untuk absensi.',
            ], 422);
        }

        // Cek apakah sudah absen hari ini
        $existingAttendance = Attendance::where('user_id', $userId)
            ->whereDate('date', $today)
            ->first();
        $checkoutStart = $this->todayAt($setting->checkout_start, $today, $timezone);

        if ($existingAttendance) {
            // Update check_out jika hari ini belum punya check_out
            if (!$existingAttendance->check_out) {
                if ($now->lt($checkoutStart)) {
                    return response()->json([
                        'ok' => false,
                        'reason' => 'too_early_checkout',
                        'message' => 'Belum masuk jam check-out.',
                    ], 422);
                }

                $isCheckoutOnly = !$existingAttendance->check_in;
                $notes = $existingAttendance->notes;

                if ($isCheckoutOnly) {
                    $checkoutOnlyNote = 'Check-out saja: check-in tidak tercatat.';
                    $notes = $notes
                        ? (str_contains($notes, $checkoutOnlyNote) ? $notes : "{$notes} | {$checkoutOnlyNote}")
                        : $checkoutOnlyNote;
                }

                $existingAttendance->update([
                    'check_out' => $now->format('H:i:s'),
                    'status' => $isCheckoutOnly ? 'hadir' : $existingAttendance->status,
                    'notes' => $notes,
                    'device_info' => $req->string('device_info'),
                    'location'    => $location ?? $existingAttendance->location,
                ]);

                Log::info('ATTENDANCE_CHECKOUT', [
                    'user_id' => $userId,
                    'user_name' => $user->name,
                    'session_id' => $sessionId,
                    'check_out' => $now->toIso8601String(),
                ]);

                // Broadcast attendance update
                try {
                    broadcast(new AttendanceUpdated(
                        $userId,
                        $existingAttendance->status,
                        $existingAttendance->check_in?->toIso8601String(),
                        $now->toIso8601String()
                    ));
                } catch (\Throwable $e) {
                    Log::warning('Attendance broadcast failed (checkout): ' . $e->getMessage());
                }
            } else {
                return response()->json(['ok' => false, 'reason' => 'Already attended today'], 409);
            }
        } else {
            if ($now->gte($checkoutStart)) {
                $attendance = Attendance::create([
                    'user_id' => $userId,
                    'date' => $today,
                    'check_in' => null,
                    'check_out' => $now->format('H:i:s'),
                    'status' => 'hadir',
                    'lates_minutes' => null,
                    'notes' => 'Check-out saja: check-in tidak tercatat.',
                    'device_info' => $req->string('device_info'),
                    'location'    => $location,
                ]);

                Log::info('ATTENDANCE_CHECKOUT_ONLY', [
                    'user_id' => $userId,
                    'user_name' => $user->name,
                    'session_id' => $sessionId,
                    'check_out' => $now->toIso8601String(),
                    'attendance_id' => $attendance->id,
                ]);

                try {
                    broadcast(new AttendanceUpdated(
                        $userId,
                        'hadir',
                        null,
                        $now->toIso8601String()
                    ));
                } catch (\Throwable $e) {
                    Log::warning('Attendance broadcast failed (checkout-only): ' . $e->getMessage());
                }

                $this->generateNewToken($svc, $sessionId, $this->qrTtl());
                return response()->json(['ok' => true, 'session_id' => $sessionId, 'mode' => 'checkout_only']);
            }

            $checkinStart = $this->todayAt($setting->checkin_start, $today, $timezone);
            $checkinEnd = $this->todayAt($setting->checkin_end, $today, $timezone);
            $lateGraceMinutes = max(0, (int) ($setting->late_grace_minutes ?? 0));
            $lateLimit = $checkinEnd->copy()->addMinutes($lateGraceMinutes);

            if ($now->lt($checkinStart)) {
                return response()->json([
                    'ok' => false,
                    'reason' => 'too_early_checkin',
                    'message' => 'Belum masuk jam check-in.',
                ], 422);
            }

            if (!$setting->allow_checkin_after_end && $now->gt($lateLimit)) {
                return response()->json([
                    'ok' => false,
                    'reason' => 'checkin_closed',
                    'message' => 'Jam check-in sudah ditutup.',
                ], 422);
            }

            $lateDiff = max(0, $checkinEnd->diffInMinutes($now, false));
            $lateMinutes = $lateDiff > $lateGraceMinutes ? (string) ($lateDiff - $lateGraceMinutes) : null;

            // Buat record absensi baru
            $attendance = Attendance::create([
                'user_id' => $userId,
                'date' => $today,
                'check_in' => $now->format('H:i:s'),
                'status' => 'hadir',
                'lates_minutes' => $lateMinutes,
                'notes' => $lateMinutes ? "Terlambat {$lateMinutes} menit" : null,
                'device_info' => $req->string('device_info'),
                'location'    => $location,
            ]);

            Log::info('ATTENDANCE_CHECKIN', [
                'user_id' => $userId,
                'user_name' => $user->name,
                'session_id' => $sessionId,
                'check_in' => $now->toIso8601String(),
                'lates_minutes' => $lateMinutes,
            ]);

            // Broadcast attendance update
            try {
                broadcast(new AttendanceUpdated(
                    $userId,
                    'hadir',
                    $now->toIso8601String(),
                    null
                ));
            } catch (\Throwable $e) {
                Log::warning('Attendance broadcast failed (checkin): ' . $e->getMessage());
            }
        }

        // berhasil → SELALU ganti token baru
        $this->generateNewToken($svc, $sessionId, $this->qrTtl());

        return response()->json(['ok' => true, 'session_id' => $sessionId]);
    }

    public function daily(Request $req)
    {
        $req->validate([
            'user_id' => 'required|integer|exists:users,id',
            'date'    => 'nullable|date_format:Y-m-d',
        ]);

        $userId = (int) $req->user_id;
        $date   = $req->filled('date')
            ? Carbon::createFromFormat('Y-m-d', $req->date)->toDateString()
            : now()->toDateString();

        $attendance = Attendance::where('user_id', $userId)
            ->whereDate('date', $date)
            ->first();

        if (!$attendance) {
            return response()->json([
                'ok' => true,
                'data' => null,
                'meta' => [
                    'user_id' => $userId,
                    'date' => $date,
                    'message' => 'Belum ada absensi pada tanggal ini',
                ],
            ]);
        }

        return response()->json([
            'ok' => true,
            'data' => [
                'id'          => $attendance->id,
                'user_id'     => $attendance->user_id,
                'date'        => $attendance->date,       // Y-m-d
                'check_in'    => $attendance->check_in,   // HH:MM:SS / null
                'check_out'   => $attendance->check_out,  // HH:MM:SS / null
                'status'      => $attendance->status,
                'lates_minutes' => $attendance->lates_minutes,
                'notes'       => $attendance->notes,
                'device_info' => $attendance->device_info,
                'location'    => $attendance->location,
                'created_at'  => $attendance->created_at?->toIso8601String(),
                'updated_at'  => $attendance->updated_at?->toIso8601String(),
            ],
            'meta' => [
                'user_id' => $userId,
                'date' => $date,
            ],
        ]);
    }

    private function generateNewToken(DynamicQrService $svc, int $sessionId, int $ttl): array
    {
        $data = $svc->issueShortToken($sessionId, $ttl);
        Cache::put("qr:session:$sessionId:aktif", $data, $ttl);

        try {
            broadcast(new QrTokenIssued(
                $sessionId,
                $data['token'],
                $data['payload']['exp'],
                $ttl
            ));
        } catch (\Throwable $e) {
            Log::warning('QR broadcast failed (generateNewToken): ' . $e->getMessage(), [
                'session_id' => $sessionId,
            ]);
        }

        return $data;
    }

    private function todayAt(?string $time, string $date, string $timezone): Carbon
    {
        $value = (string) ($time ?: '00:00:00');

        if (strlen($value) === 5) {
            $value .= ':00';
        }

        return Carbon::createFromFormat('Y-m-d H:i:s', "{$date} {$value}", $timezone);
    }

    private function distanceInMeters(float $lat1, float $lng1, float $lat2, float $lng2): float
    {
        $earthRadius = 6371000;

        $dLat = deg2rad($lat2 - $lat1);
        $dLng = deg2rad($lng2 - $lng1);

        $a = sin($dLat / 2) ** 2
            + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng / 2) ** 2;

        return 2 * $earthRadius * asin(min(1, sqrt($a)));
    }
}
