package com.magistr.app.controller;

import com.magistr.app.config.auth.RequireRoles;
import com.magistr.app.dto.ClassroomResponse;
import com.magistr.app.dto.RenderedLessonDto;
import com.magistr.app.dto.WorkloadSummaryDto;
import com.magistr.app.model.*;
import com.magistr.app.repository.ClassroomRepository;
import com.magistr.app.repository.DepartmentRepository;
import com.magistr.app.repository.UserRepository;
import com.magistr.app.service.ScheduleQueryService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDate;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/workload")
@RequireRoles({Role.ADMIN, Role.EDUCATION_OFFICE, Role.DEPARTMENT, Role.SCHEDULE_VIEWER})
public class WorkloadController {

    private static final int ACADEMIC_HOURS_PER_LESSON = 2;

    private final ScheduleQueryService scheduleQueryService;
    private final ClassroomRepository classroomRepository;
    private final UserRepository userRepository;
    private final DepartmentRepository departmentRepository;

    public WorkloadController(ScheduleQueryService scheduleQueryService,
                              ClassroomRepository classroomRepository,
                              UserRepository userRepository,
                              DepartmentRepository departmentRepository) {
        this.scheduleQueryService = scheduleQueryService;
        this.classroomRepository = classroomRepository;
        this.userRepository = userRepository;
        this.departmentRepository = departmentRepository;
    }

    @GetMapping("/teachers")
    public List<WorkloadSummaryDto> teacherWorkload(
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate,
            @RequestParam(required = false) Long departmentId
    ) {
        List<RenderedLessonDto> lessons = scheduleQueryService.search(null, null, null, departmentId,
                null, null, null, null, startDate, endDate);
        Map<Long, User> teachersById = userRepository.findAllById(lessons.stream()
                        .map(RenderedLessonDto::teacherId)
                        .filter(Objects::nonNull)
                        .collect(Collectors.toSet()))
                .stream()
                .collect(Collectors.toMap(User::getId, Function.identity()));
        return summarize(lessons, RenderedLessonDto::teacherId, RenderedLessonDto::teacherName,
                teacherId -> {
                    User teacher = teachersById.get(teacherId);
                    return teacher == null ? null : teacher.getDepartmentId();
                });
    }

    @GetMapping("/classrooms")
    public List<WorkloadSummaryDto> classroomWorkload(
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate,
            @RequestParam(required = false) Long departmentId
    ) {
        List<RenderedLessonDto> lessons = scheduleQueryService.search(null, null, null, departmentId,
                null, null, null, null, startDate, endDate);
        return summarize(lessons, RenderedLessonDto::classroomId, RenderedLessonDto::classroomName, ignored -> null);
    }

    @GetMapping("/departments")
    public List<WorkloadSummaryDto> departmentWorkload(
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate,
            @RequestParam(required = false) Long departmentId
    ) {
        List<RenderedLessonDto> lessons = scheduleQueryService.search(null, null, null, departmentId,
                null, null, null, null, startDate, endDate);
        Map<Long, String> departments = departmentRepository.findAll().stream()
                .collect(Collectors.toMap(Department::getId, Department::getDepartmentName));
        Map<Long, User> teachersById = userRepository.findAllById(lessons.stream()
                        .map(RenderedLessonDto::teacherId)
                        .filter(Objects::nonNull)
                        .collect(Collectors.toSet()))
                .stream()
                .collect(Collectors.toMap(User::getId, Function.identity()));

        Map<Long, List<RenderedLessonDto>> byDepartment = lessons.stream()
                .collect(Collectors.groupingBy(lesson -> {
                    User teacher = teachersById.get(lesson.teacherId());
                    return teacher == null ? 0L : teacher.getDepartmentId();
                }));

        return byDepartment.entrySet().stream()
                .map(entry -> summary(entry.getKey(), departments.getOrDefault(entry.getKey(), "Кафедра не указана"), entry.getKey(), entry.getValue()))
                .sorted(Comparator.comparing(WorkloadSummaryDto::name, Comparator.nullsLast(String::compareTo)))
                .toList();
    }

    @GetMapping("/time-slots")
    public List<WorkloadSummaryDto> timeSlotWorkload(
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate,
            @RequestParam(required = false) Long departmentId
    ) {
        List<RenderedLessonDto> lessons = scheduleQueryService.search(null, null, null, departmentId,
                null, null, null, null, startDate, endDate);
        return summarize(lessons, RenderedLessonDto::timeSlotId,
                lesson -> lesson.timeSlotOrder() == null
                        ? "Пара"
                        : lesson.timeSlotOrder() + " пара (" + lesson.startTime() + " - " + lesson.endTime() + ")",
                ignored -> null);
    }

    @GetMapping("/free-classrooms")
    public List<ClassroomResponse> freeClassrooms(
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
            @RequestParam Long timeSlotId
    ) {
        Set<Long> busyClassroomIds = scheduleQueryService.search(null, null, null, null,
                        null, null, timeSlotId, null, date, date)
                .stream()
                .map(RenderedLessonDto::classroomId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        return classroomRepository.findByStatusNot(LifecycleEntity.STATUS_ARCHIVED).stream()
                .filter(classroom -> Boolean.TRUE.equals(classroom.getIsAvailable()))
                .filter(classroom -> !busyClassroomIds.contains(classroom.getId()))
                .map(this::toClassroomResponse)
                .toList();
    }

    private List<WorkloadSummaryDto> summarize(List<RenderedLessonDto> lessons,
                                               Function<RenderedLessonDto, Long> idFn,
                                               Function<RenderedLessonDto, String> nameFn,
                                               Function<Long, Long> departmentFn) {
        Map<Long, List<RenderedLessonDto>> grouped = lessons.stream()
                .filter(lesson -> idFn.apply(lesson) != null)
                .collect(Collectors.groupingBy(idFn, LinkedHashMap::new, Collectors.toList()));
        return grouped.entrySet().stream()
                .map(entry -> summary(entry.getKey(), nameFn.apply(entry.getValue().get(0)), departmentFn.apply(entry.getKey()), entry.getValue()))
                .sorted(Comparator.comparing(WorkloadSummaryDto::name, Comparator.nullsLast(String::compareTo)))
                .toList();
    }

    private WorkloadSummaryDto summary(Long id, String name, Long departmentId, List<RenderedLessonDto> lessons) {
        return new WorkloadSummaryDto(
                id,
                name,
                departmentId,
                departmentName(departmentId),
                lessons.size(),
                lessons.size() * ACADEMIC_HOURS_PER_LESSON,
                lessons.stream()
                        .map(lesson -> lesson.date() + ":" + lesson.timeSlotId())
                        .distinct()
                        .count()
        );
    }

    private String departmentName(Long departmentId) {
        if (departmentId == null) {
            return null;
        }
        return departmentRepository.findById(departmentId)
                .map(Department::getDepartmentName)
                .orElse("Кафедра не найдена");
    }

    private ClassroomResponse toClassroomResponse(Classroom classroom) {
        return new ClassroomResponse(
                classroom.getId(),
                classroom.getName(),
                classroom.getCapacity(),
                classroom.getBuilding(),
                classroom.getFloor(),
                classroom.getIsAvailable(),
                classroom.getStatus(),
                classroom.getArchivedAt(),
                classroom.getArchiveReason(),
                new ArrayList<>(classroom.getEquipments())
        );
    }
}
