<?php

namespace App\Domain\Scheduling\Services;

use App\Domain\Facility\Models\SpaceReservation;
use App\Domain\Scheduling\Models\Assignment;
use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Models\TrainingSchedule;
use App\Domain\Training\Models\TrainingSession;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;

class ScheduleConflictService
{
    /**
     * Check ALL conflicts for a trainer at a given date+time.
     * Returns array of conflict objects describing what blocks them.
     */
    public function checkTrainerConflicts(
        int $trainerId,
        string $date,
        string $startTime,
        string $endTime,
        ?int $excludeScheduleId = null,
        ?int $excludeSessionId = null
    ): array {
        $dayOfWeek = Carbon::parse($date)->dayOfWeek;
        $conflicts = [];

        // 1. Check TrainingSchedule: trainer is assigned (primary or assistant) at overlapping time
        $scheduleConflicts = TrainingSchedule::where('is_active', true)
            ->where('day_of_week', $dayOfWeek)
            ->where('start_time', '<', $endTime)
            ->where('end_time', '>', $startTime)
            ->where(function ($q) use ($trainerId) {
                $q->where('trainer_id', $trainerId)
                  ->orWhere('assistant_trainer_id', $trainerId);
            })
            ->when($excludeScheduleId, fn ($q) => $q->where('id', '!=', $excludeScheduleId))
            ->where(function ($q) use ($date) {
                $q->where(function ($q2) use ($date) {
                    $q2->whereNull('effective_from')->orWhere('effective_from', '<=', $date);
                })->where(function ($q2) use ($date) {
                    $q2->whereNull('effective_until')->orWhere('effective_until', '>=', $date);
                });
            })
            ->with('group')
            ->get();

        foreach ($scheduleConflicts as $schedule) {
            $role = $schedule->trainer_id === $trainerId ? 'مدرب أساسي' : 'مدرب مساعد';
            $conflicts[] = [
                'type' => 'schedule',
                'source' => 'training_schedule',
                'id' => $schedule->id,
                'title' => $schedule->group?->name_ar ?? __('مجموعة غير معروفة'),
                'role' => $role,
                'time' => substr($schedule->start_time, 0, 5) . ' - ' . substr($schedule->end_time, 0, 5),
                'message' => __('المدرب مسؤول عن') . ' "' . ($schedule->group?->name_ar ?? '') . '" ' . __('كـ') . $role,
            ];
        }

        // 2. Check TrainingSession: trainer assigned to a specific session on this date
        $sessionConflicts = TrainingSession::where('session_date', $date)
            ->whereIn('status', ['scheduled', 'in_progress'])
            ->where('start_time', '<', $endTime)
            ->where('end_time', '>', $startTime)
            ->where(function ($q) use ($trainerId) {
                $q->where('trainer_id', $trainerId)
                  ->orWhere('assistant_trainer_id', $trainerId);
            })
            ->when($excludeSessionId, fn ($q) => $q->where('id', '!=', $excludeSessionId))
            ->with('group')
            ->get();

        foreach ($sessionConflicts as $session) {
            $role = $session->trainer_id === $trainerId ? 'مدرب أساسي' : 'مدرب مساعد';
            $conflicts[] = [
                'type' => 'session',
                'source' => 'training_session',
                'id' => $session->id,
                'title' => $session->group?->name_ar ?? __('جلسة'),
                'role' => $role,
                'time' => substr($session->start_time, 0, 5) . ' - ' . substr($session->end_time, 0, 5),
                'message' => __('المدرب مكلّف بجلسة') . ' "' . ($session->group?->name_ar ?? '') . '" ' . __('في هذا الوقت'),
            ];
        }

        // 3. Check TrainingGroup.head_trainer_id — if trainer is head of a group
        //    that has schedules at this time
        $headGroupConflicts = TrainingSchedule::where('is_active', true)
            ->where('day_of_week', $dayOfWeek)
            ->where('start_time', '<', $endTime)
            ->where('end_time', '>', $startTime)
            ->when($excludeScheduleId, fn ($q) => $q->where('id', '!=', $excludeScheduleId))
            ->where(function ($q) use ($date) {
                $q->where(function ($q2) use ($date) {
                    $q2->whereNull('effective_from')->orWhere('effective_from', '<=', $date);
                })->where(function ($q2) use ($date) {
                    $q2->whereNull('effective_until')->orWhere('effective_until', '>=', $date);
                });
            })
            ->whereNull('trainer_id') // Only if schedule doesn't have its own trainer
            ->whereHas('group', fn ($q) => $q->where('head_trainer_id', $trainerId))
            ->with('group')
            ->get();

        foreach ($headGroupConflicts as $schedule) {
            $conflicts[] = [
                'type' => 'head_trainer',
                'source' => 'group_head_trainer',
                'id' => $schedule->id,
                'title' => $schedule->group?->name_ar ?? '',
                'role' => 'مدرب رئيسي للمجموعة',
                'time' => substr($schedule->start_time, 0, 5) . ' - ' . substr($schedule->end_time, 0, 5),
                'message' => __('المدرب هو المدرب الرئيسي لمجموعة') . ' "' . ($schedule->group?->name_ar ?? '') . '" ' . __('المجدولة في نفس الوقت'),
            ];
        }

        // 4. Check formal Assignment model
        $assignmentConflicts = Assignment::where('user_id', $trainerId)
            ->where('status', 'active')
            ->where(function ($q) use ($date) {
                $q->where(function ($q2) use ($date) {
                    $q2->whereNull('start_date')->orWhere('start_date', '<=', $date);
                })->where(function ($q2) use ($date) {
                    $q2->whereNull('end_date')->orWhere('end_date', '>=', $date);
                });
            })
            ->where(function ($q) use ($startTime, $endTime) {
                $q->where(function ($q2) use ($startTime, $endTime) {
                    $q2->whereNotNull('start_time')
                       ->where('start_time', '<', $endTime)
                       ->where('end_time', '>', $startTime);
                })->orWhereNull('start_time'); // Full-day assignments
            })
            ->where(function ($q) use ($dayOfWeek) {
                $q->whereNull('schedule_days')
                  ->orWhereJsonContains('schedule_days', $dayOfWeek);
            })
            ->get();

        foreach ($assignmentConflicts as $assignment) {
            $assignableName = '';
            if ($assignment->assignable) {
                $assignableName = $assignment->assignable->name_ar ?? $assignment->assignable->name ?? '';
            }
            $conflicts[] = [
                'type' => 'assignment',
                'source' => 'formal_assignment',
                'id' => $assignment->id,
                'title' => $assignableName ?: $assignment->role_label,
                'role' => $assignment->role_label ?? $assignment->scope,
                'time' => $assignment->start_time
                    ? (substr($assignment->start_time, 0, 5) . ' - ' . substr($assignment->end_time, 0, 5))
                    : __('طوال اليوم'),
                'message' => __('تعيين رسمي:') . ' ' . $assignment->role_label . ' — ' . $assignableName,
            ];
        }

        return $conflicts;
    }

    /**
     * Check ALL conflicts for a training group at a given date+time+facility.
     * A group can't be in two places at once.
     */
    public function checkGroupConflicts(
        int $groupId,
        string $date,
        string $startTime,
        string $endTime,
        ?int $excludeScheduleId = null
    ): array {
        $dayOfWeek = Carbon::parse($date)->dayOfWeek;
        $conflicts = [];

        // 1. Check if group already has a schedule at this time (different facility or segments)
        $scheduleConflicts = TrainingSchedule::where('training_group_id', $groupId)
            ->where('is_active', true)
            ->where('day_of_week', $dayOfWeek)
            ->where('start_time', '<', $endTime)
            ->where('end_time', '>', $startTime)
            ->when($excludeScheduleId, fn ($q) => $q->where('id', '!=', $excludeScheduleId))
            ->where(function ($q) use ($date) {
                $q->where(function ($q2) use ($date) {
                    $q2->whereNull('effective_from')->orWhere('effective_from', '<=', $date);
                })->where(function ($q2) use ($date) {
                    $q2->whereNull('effective_until')->orWhere('effective_until', '>=', $date);
                });
            })
            ->with('facility')
            ->get();

        foreach ($scheduleConflicts as $schedule) {
            $conflicts[] = [
                'type' => 'group_schedule',
                'source' => 'training_schedule',
                'id' => $schedule->id,
                'facility' => $schedule->facility?->name_ar ?? '',
                'time' => substr($schedule->start_time, 0, 5) . ' - ' . substr($schedule->end_time, 0, 5),
                'message' => __('المجموعة مجدولة بالفعل في') . ' "' . ($schedule->facility?->name_ar ?? '') . '" ' . __('في نفس الوقت'),
            ];
        }

        // 2. Check if group has a session on this specific date at this time
        $sessionConflicts = TrainingSession::where('training_group_id', $groupId)
            ->where('session_date', $date)
            ->whereIn('status', ['scheduled', 'in_progress'])
            ->where('start_time', '<', $endTime)
            ->where('end_time', '>', $startTime)
            ->with('facility')
            ->get();

        foreach ($sessionConflicts as $session) {
            $conflicts[] = [
                'type' => 'group_session',
                'source' => 'training_session',
                'id' => $session->id,
                'facility' => $session->facility?->name_ar ?? '',
                'time' => substr($session->start_time, 0, 5) . ' - ' . substr($session->end_time, 0, 5),
                'message' => __('المجموعة لديها جلسة محددة في') . ' "' . ($session->facility?->name_ar ?? '') . '"',
            ];
        }

        // 3. Check confirmed space reservations for this group's schedule
        $reservationConflicts = SpaceReservation::where('reservation_date', $date)
            ->where('status', 'confirmed')
            ->where('start_time', '<', $endTime)
            ->where('end_time', '>', $startTime)
            ->where('reservable_type', TrainingSchedule::class)
            ->whereIn('reservable_id', function ($q) use ($groupId) {
                $q->select('id')
                  ->from('training_schedules')
                  ->where('training_group_id', $groupId);
            })
            ->with('facility')
            ->get();

        foreach ($reservationConflicts as $reservation) {
            // Only flag if it's a DIFFERENT facility than what we're assigning to
            $conflicts[] = [
                'type' => 'group_reservation',
                'source' => 'space_reservation',
                'id' => $reservation->id,
                'facility' => $reservation->facility?->name_ar ?? '',
                'time' => substr($reservation->start_time, 0, 5) . ' - ' . substr($reservation->end_time, 0, 5),
                'message' => __('المجموعة لديها حجز مؤكد في') . ' "' . ($reservation->facility?->name_ar ?? '') . '"',
            ];
        }

        return $conflicts;
    }

    /**
     * Get the default trainer(s) for a group at a given time.
     * Returns trainers who will be automatically committed when the group is scheduled.
     */
    public function getImpliedTrainers(int $groupId, string $date, string $startTime, string $endTime): array
    {
        $dayOfWeek = Carbon::parse($date)->dayOfWeek;
        $group = TrainingGroup::with('headTrainer')->find($groupId);
        if (!$group) return [];

        $trainers = [];

        // Check if there's already a schedule with explicit trainer
        $schedule = TrainingSchedule::where('training_group_id', $groupId)
            ->where('is_active', true)
            ->where('day_of_week', $dayOfWeek)
            ->where('start_time', '<', $endTime)
            ->where('end_time', '>', $startTime)
            ->where(function ($q) use ($date) {
                $q->where(function ($q2) use ($date) {
                    $q2->whereNull('effective_from')->orWhere('effective_from', '<=', $date);
                })->where(function ($q2) use ($date) {
                    $q2->whereNull('effective_until')->orWhere('effective_until', '>=', $date);
                });
            })
            ->first();

        if ($schedule) {
            if ($schedule->trainer_id) {
                $trainers[] = [
                    'id' => $schedule->trainer_id,
                    'source' => 'schedule',
                    'role' => 'مدرب أساسي',
                ];
            }
            if ($schedule->assistant_trainer_id) {
                $trainers[] = [
                    'id' => $schedule->assistant_trainer_id,
                    'source' => 'schedule',
                    'role' => 'مدرب مساعد',
                ];
            }
        }

        // If no schedule-level trainer, fall back to group head trainer
        if (empty($trainers) && $group->head_trainer_id) {
            $trainers[] = [
                'id' => $group->head_trainer_id,
                'source' => 'group_head',
                'role' => 'مدرب رئيسي للمجموعة',
            ];
        }

        return $trainers;
    }

    /**
     * Full validation before assignment. Returns all issues (blocking + warnings).
     */
    public function validateAssignment(
        string $entityType, // 'group' or 'trainer'
        int $entityId,
        int $facilityId,
        string $date,
        string $startTime,
        string $endTime,
        array $segmentIds,
        ?int $excludeScheduleId = null
    ): array {
        $errors = [];
        $warnings = [];

        if ($entityType === 'group') {
            // Check group conflicts (can't be in two places)
            $groupConflicts = $this->checkGroupConflicts($entityId, $date, $startTime, $endTime, $excludeScheduleId);
            foreach ($groupConflicts as $c) {
                // If same facility, it might just be a re-assignment — warning not error
                if (isset($c['facility']) && $c['source'] === 'space_reservation') {
                    $warnings[] = $c;
                } else {
                    $errors[] = $c;
                }
            }

            // Check implied trainers for conflicts
            $impliedTrainers = $this->getImpliedTrainers($entityId, $date, $startTime, $endTime);
            foreach ($impliedTrainers as $trainer) {
                $trainerConflicts = $this->checkTrainerConflicts(
                    $trainer['id'],
                    $date,
                    $startTime,
                    $endTime,
                    $excludeScheduleId
                );
                foreach ($trainerConflicts as $tc) {
                    $warnings[] = array_merge($tc, [
                        'implied_trainer' => true,
                        'trainer_role' => $trainer['role'],
                        'trainer_id' => $trainer['id'],
                    ]);
                }
            }
        } elseif ($entityType === 'trainer') {
            $trainerConflicts = $this->checkTrainerConflicts($entityId, $date, $startTime, $endTime, $excludeScheduleId);
            foreach ($trainerConflicts as $c) {
                $errors[] = $c;
            }
        }

        return [
            'valid' => empty($errors),
            'errors' => $errors,
            'warnings' => $warnings,
        ];
    }

    /**
     * Get all occupied time ranges for a trainer on a given date.
     * Useful for showing trainer availability in the UI.
     */
    public function getTrainerOccupiedSlots(int $trainerId, string $date): array
    {
        $dayOfWeek = Carbon::parse($date)->dayOfWeek;
        $slots = [];

        // From schedules
        $schedules = TrainingSchedule::where('is_active', true)
            ->where('day_of_week', $dayOfWeek)
            ->where(function ($q) use ($trainerId) {
                $q->where('trainer_id', $trainerId)
                  ->orWhere('assistant_trainer_id', $trainerId);
            })
            ->where(function ($q) use ($date) {
                $q->where(function ($q2) use ($date) {
                    $q2->whereNull('effective_from')->orWhere('effective_from', '<=', $date);
                })->where(function ($q2) use ($date) {
                    $q2->whereNull('effective_until')->orWhere('effective_until', '>=', $date);
                });
            })
            ->with('group')
            ->get();

        foreach ($schedules as $s) {
            $slots[] = [
                'start' => substr($s->start_time, 0, 5),
                'end' => substr($s->end_time, 0, 5),
                'title' => $s->group?->name_ar ?? '',
                'type' => 'schedule',
            ];
        }

        // From head_trainer groups
        $headSchedules = TrainingSchedule::where('is_active', true)
            ->where('day_of_week', $dayOfWeek)
            ->whereNull('trainer_id')
            ->whereHas('group', fn ($q) => $q->where('head_trainer_id', $trainerId))
            ->where(function ($q) use ($date) {
                $q->where(function ($q2) use ($date) {
                    $q2->whereNull('effective_from')->orWhere('effective_from', '<=', $date);
                })->where(function ($q2) use ($date) {
                    $q2->whereNull('effective_until')->orWhere('effective_until', '>=', $date);
                });
            })
            ->with('group')
            ->get();

        foreach ($headSchedules as $s) {
            $slots[] = [
                'start' => substr($s->start_time, 0, 5),
                'end' => substr($s->end_time, 0, 5),
                'title' => $s->group?->name_ar ?? '',
                'type' => 'head_trainer',
            ];
        }

        // From sessions
        $sessions = TrainingSession::where('session_date', $date)
            ->whereIn('status', ['scheduled', 'in_progress'])
            ->where(function ($q) use ($trainerId) {
                $q->where('trainer_id', $trainerId)
                  ->orWhere('assistant_trainer_id', $trainerId);
            })
            ->with('group')
            ->get();

        foreach ($sessions as $s) {
            $slots[] = [
                'start' => substr($s->start_time, 0, 5),
                'end' => substr($s->end_time, 0, 5),
                'title' => $s->group?->name_ar ?? '',
                'type' => 'session',
            ];
        }

        // Sort by start time
        usort($slots, fn ($a, $b) => strcmp($a['start'], $b['start']));

        return $slots;
    }

    /**
     * Get all occupied time ranges for a group on a given date.
     */
    public function getGroupOccupiedSlots(int $groupId, string $date): array
    {
        $dayOfWeek = Carbon::parse($date)->dayOfWeek;
        $slots = [];

        $schedules = TrainingSchedule::where('training_group_id', $groupId)
            ->where('is_active', true)
            ->where('day_of_week', $dayOfWeek)
            ->where(function ($q) use ($date) {
                $q->where(function ($q2) use ($date) {
                    $q2->whereNull('effective_from')->orWhere('effective_from', '<=', $date);
                })->where(function ($q2) use ($date) {
                    $q2->whereNull('effective_until')->orWhere('effective_until', '>=', $date);
                });
            })
            ->with('facility')
            ->get();

        foreach ($schedules as $s) {
            $slots[] = [
                'start' => substr($s->start_time, 0, 5),
                'end' => substr($s->end_time, 0, 5),
                'facility' => $s->facility?->name_ar ?? '',
                'type' => 'schedule',
            ];
        }

        $sessions = TrainingSession::where('training_group_id', $groupId)
            ->where('session_date', $date)
            ->whereIn('status', ['scheduled', 'in_progress'])
            ->with('facility')
            ->get();

        foreach ($sessions as $s) {
            $slots[] = [
                'start' => substr($s->start_time, 0, 5),
                'end' => substr($s->end_time, 0, 5),
                'facility' => $s->facility?->name_ar ?? '',
                'type' => 'session',
            ];
        }

        usort($slots, fn ($a, $b) => strcmp($a['start'], $b['start']));

        return $slots;
    }

    /**
     * Check if a time range overlaps with facility operating hours.
     */
    public function isWithinOperatingHours(string $startTime, string $endTime, string $opStart, string $opEnd): bool
    {
        return $startTime >= $opStart && $endTime <= $opEnd;
    }

    /**
     * Get minimum gap between consecutive sessions (configurable per academy, default 0).
     */
    public function getMinGapMinutes(): int
    {
        return (int) (app('current_academy')?->getSetting('min_session_gap_minutes') ?? 0);
    }
}
