<?php

namespace App\Domain\Attendance\Services;

use App\Domain\Attendance\Enums\AttendanceStatus;
use App\Domain\Attendance\Events\AttendanceMarked;
use App\Domain\Attendance\Events\AttendanceThresholdBreached;
use App\Domain\Attendance\Events\ParticipantAbsent;
use App\Domain\Attendance\Models\AttendanceRecord;
use App\Domain\Document\Models\Document;
use App\Domain\Participant\Models\Participant;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Services\SettingsService;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;

class AttendanceMarkingService
{
    public function __construct(
        private SettingsService $settings,
    ) {}

    public function markPresent(AttendanceRecord $record, User $marker, ?Carbon $checkInTime = null): AttendanceRecord
    {
        $this->enforceMedicalCertificate($record);

        return DB::transaction(function () use ($record, $marker, $checkInTime) {
            $checkInTime ??= now();
            $session = $record->session;

            $graceMinutes = $this->getGraceMinutes($record);
            $sessionStart = Carbon::parse($session->session_date->format('Y-m-d') . ' ' . $session->start_time);
            $graceDeadline = $sessionStart->copy()->addMinutes($graceMinutes);

            if ($checkInTime->lte($graceDeadline)) {
                $status = AttendanceStatus::Present;
                $lateMinutes = null;
            } else {
                $status = AttendanceStatus::Late;
                $lateMinutes = (int) $checkInTime->diffInMinutes($sessionStart);
            }

            $record->update([
                'status' => $status,
                'check_in_at' => $checkInTime,
                'late_minutes' => $lateMinutes,
                'marked_by' => $marker->id,
                'marked_at' => now(),
                'is_auto_generated' => false,
            ]);

            AttendanceMarked::dispatch($record->fresh(), $marker);
            $this->checkThresholds($record);

            return $record->fresh();
        });
    }

    public function markStatus(AttendanceRecord $record, AttendanceStatus $status, User $marker, ?string $reason = null): AttendanceRecord
    {
        // Enforce medical certificate for positive attendance statuses
        if (in_array($status, [AttendanceStatus::Present, AttendanceStatus::Late, AttendanceStatus::Partial])) {
            $this->enforceMedicalCertificate($record);
        }

        return DB::transaction(function () use ($record, $status, $marker, $reason) {
            $data = [
                'status' => $status,
                'marked_by' => $marker->id,
                'marked_at' => now(),
                'is_auto_generated' => false,
            ];

            if ($reason) {
                $data['status_reason'] = $reason;
            }

            if ($status === AttendanceStatus::Present || $status === AttendanceStatus::Late) {
                $data['check_in_at'] = $record->check_in_at ?? now();
            }

            $record->update($data);

            $freshRecord = $record->fresh();
            AttendanceMarked::dispatch($freshRecord, $marker);

            if (in_array($status, [AttendanceStatus::Absent, AttendanceStatus::NoShow])) {
                ParticipantAbsent::dispatch($freshRecord);
            }

            $this->checkThresholds($record);

            return $freshRecord;
        });
    }

    public function markCheckOut(AttendanceRecord $record, User $marker, ?Carbon $checkOutTime = null): AttendanceRecord
    {
        return DB::transaction(function () use ($record, $marker, $checkOutTime) {
            $checkOutTime ??= now();

            $durationMinutes = null;
            if ($record->check_in_at) {
                $durationMinutes = (int) $record->check_in_at->diffInMinutes($checkOutTime);
            }

            // If left early (before session end), mark accordingly
            $session = $record->session;
            $sessionEnd = Carbon::parse($session->session_date->format('Y-m-d') . ' ' . $session->end_time);
            $earlyLeaveMinutes = (int) $this->settings->get('early_leave_threshold_minutes', 10);

            $status = $record->status;
            if ($checkOutTime->lt($sessionEnd->subMinutes($earlyLeaveMinutes))) {
                $status = AttendanceStatus::LeftEarly;
            }

            $record->update([
                'check_out_at' => $checkOutTime,
                'duration_minutes' => $durationMinutes,
                'status' => $status,
                'marked_by' => $marker->id,
                'marked_at' => now(),
            ]);

            return $record->fresh();
        });
    }

    public function bulkMark(int $sessionId, array $marks, User $marker): int
    {
        $count = 0;
        DB::transaction(function () use ($sessionId, $marks, $marker, &$count) {
            foreach ($marks as $mark) {
                $record = AttendanceRecord::where('training_session_id', $sessionId)
                    ->where('id', $mark['record_id'])
                    ->first();

                if (!$record) {
                    continue;
                }

                $status = AttendanceStatus::from($mark['status']);
                $this->markStatus($record, $status, $marker, $mark['reason'] ?? null);
                $count++;
            }
        });

        return $count;
    }

    public function calculateRate(int $participantId, ?int $groupId = null): float
    {
        $query = AttendanceRecord::where('subject_type', \App\Domain\Participant\Models\Participant::class)
            ->where('subject_id', $participantId);

        if ($groupId) {
            $query->whereHas('session', fn ($q) => $q->where('training_group_id', $groupId));
        }

        $total = (clone $query)->count();
        $excluded = (clone $query)->whereIn('status', [
            AttendanceStatus::Cancelled,
            AttendanceStatus::Exempt,
            AttendanceStatus::Expected,
        ])->count();

        $denominator = $total - $excluded;
        if ($denominator <= 0) {
            return 100.0;
        }

        $positive = (clone $query)->whereIn('status', [
            AttendanceStatus::Present,
            AttendanceStatus::Late,
            AttendanceStatus::Partial,
        ])->count();

        return round(($positive / $denominator) * 100, 1);
    }

    private function getGraceMinutes(AttendanceRecord $record): int
    {
        // Trainers (users) get less grace
        if ($record->subject_type === \App\Models\User::class) {
            return (int) $this->settings->get('trainer_grace_minutes', 10);
        }

        return (int) $this->settings->get('participant_grace_minutes', 15);
    }

    private function enforceMedicalCertificate(AttendanceRecord $record): void
    {
        if ($record->subject_type !== Participant::class) {
            return;
        }

        $blockWithoutMedical = (bool) $this->settings->get('block_attendance_without_medical', false);
        if (!$blockWithoutMedical) {
            return;
        }

        $hasValidCert = Document::where('documentable_type', Participant::class)
            ->where('documentable_id', $record->subject_id)
            ->where('document_type', 'medical_certificate')
            ->where('status', 'approved')
            ->where(function ($q) {
                $q->whereNull('expires_at')->orWhere('expires_at', '>=', now());
            })
            ->exists();

        if (!$hasValidCert) {
            throw new DomainException('لا يمكن تسجيل الحضور — الشهادة الطبية مطلوبة أو منتهية');
        }
    }

    private function checkThresholds(AttendanceRecord $record): void
    {
        if ($record->subject_type !== Participant::class) {
            return;
        }

        $participant = Participant::find($record->subject_id);
        if (!$participant) {
            return;
        }

        $minSample = (int) $this->settings->get('min_attendance_sample_size', 5);
        $evaluatedCount = AttendanceRecord::where('subject_type', Participant::class)
            ->where('subject_id', $participant->id)
            ->whereNotIn('status', [
                AttendanceStatus::Expected,
                AttendanceStatus::Cancelled,
                AttendanceStatus::Exempt,
            ])
            ->count();

        if ($evaluatedCount < $minSample) {
            return;
        }

        $rate = $this->calculateRate($participant->id);
        $threshold = (float) $this->settings->get('minAttendancePercent', 75);

        if ($rate < $threshold) {
            AttendanceThresholdBreached::dispatch($participant, $rate, $threshold);
        }
    }
}
