package com.magistr.app.controller;

import com.magistr.app.config.auth.AuthContext;
import com.magistr.app.config.auth.AuthenticatedUser;
import com.magistr.app.dto.RenderedLessonDto;
import com.magistr.app.dto.WorkloadSummaryDto;
import com.magistr.app.model.Classroom;
import com.magistr.app.model.Department;
import com.magistr.app.model.LifecycleEntity;
import com.magistr.app.model.Role;
import com.magistr.app.model.TeacherDepartmentAssignment;
import com.magistr.app.model.User;
import com.magistr.app.repository.ClassroomRepository;
import com.magistr.app.repository.DepartmentRepository;
import com.magistr.app.service.ScheduleQueryService;
import com.magistr.app.service.TeacherDepartmentService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;

class WorkloadControllerTest {

    @AfterEach
    void clearAuthContext() {
        AuthContext.clear();
    }

    @Test
    void teacherWorkloadUsesDepartmentAtEachLessonDate() {
        ScheduleQueryService scheduleQueryService = mock(ScheduleQueryService.class);
        TeacherDepartmentService teacherDepartmentService = mock(TeacherDepartmentService.class);
        DepartmentRepository departmentRepository = mock(DepartmentRepository.class);
        LocalDate startDate = LocalDate.of(2026, 1, 1);
        LocalDate endDate = LocalDate.of(2026, 3, 31);
        RenderedLessonDto oldLesson = lesson(10L, "Петров Пётр", LocalDate.of(2026, 1, 15), 1L);
        RenderedLessonDto newLesson = lesson(10L, "Петров Пётр", LocalDate.of(2026, 3, 15), 2L);
        List<TeacherDepartmentAssignment> assignments = List.of(
                assignment(10L, 1L, LocalDate.of(2025, 9, 1), LocalDate.of(2026, 2, 28)),
                assignment(10L, 2L, LocalDate.of(2026, 3, 1), null)
        );
        when(scheduleQueryService.searchForAggregation(any(), any(), eq(startDate), eq(endDate)))
                .thenReturn(List.of(oldLesson, newLesson));
        when(teacherDepartmentService.findAssignmentsForTeachersBetween(
                eq(java.util.Set.of(10L)),
                eq(startDate),
                eq(endDate)
        )).thenReturn(Map.of(10L, assignments));
        when(teacherDepartmentService.resolveDepartmentIdAtDate(assignments, oldLesson.date()))
                .thenReturn(Optional.of(1L));
        when(teacherDepartmentService.resolveDepartmentIdAtDate(assignments, newLesson.date()))
                .thenReturn(Optional.of(2L));
        when(departmentRepository.findById(1L))
                .thenReturn(Optional.of(new Department(1L, "Кафедра ИБ", 1L)));
        when(departmentRepository.findById(2L))
                .thenReturn(Optional.of(new Department(2L, "Кафедра ВТ", 2L)));
        WorkloadController controller = new WorkloadController(
                scheduleQueryService,
                mock(ClassroomRepository.class),
                departmentRepository,
                teacherDepartmentService
        );
        authenticate(Role.ADMIN, null);

        List<WorkloadSummaryDto> result = controller.teacherWorkload(startDate, endDate, null);

        assertThat(result).hasSize(2);
        assertThat(result).extracting(WorkloadSummaryDto::departmentId)
                .containsExactly(1L, 2L);
        assertThat(result).extracting(WorkloadSummaryDto::lessonCount)
                .containsOnly(1L);
    }

    @Test
    void freeClassroomsSkipsNullAvailabilityWithoutNpe() {
        ScheduleQueryService scheduleQueryService = mock(ScheduleQueryService.class);
        ClassroomRepository classroomRepository = mock(ClassroomRepository.class);
        WorkloadController controller = new WorkloadController(
                scheduleQueryService,
                classroomRepository,
                mock(DepartmentRepository.class),
                mock(TeacherDepartmentService.class)
        );
        when(scheduleQueryService.searchForAggregation(any(), eq(1L), any(), any()))
                .thenReturn(List.of());
        when(classroomRepository.findByStatusNot(LifecycleEntity.STATUS_ARCHIVED))
                .thenReturn(List.of(classroom(1L, null), classroom(2L, false), classroom(3L, true)));
        authenticate(Role.SCHEDULE_VIEWER, null);

        var result = controller.freeClassrooms(LocalDate.of(2026, 5, 27), 1L);

        assertThat(result).extracting("id").containsExactly(3L);
    }

    @Test
    void departmentRoleUsesOwnDepartmentForEveryWorkloadEndpoint() {
        ScheduleQueryService scheduleQueryService = mock(ScheduleQueryService.class);
        ClassroomRepository classroomRepository = mock(ClassroomRepository.class);
        DepartmentRepository departmentRepository = mock(DepartmentRepository.class);
        TeacherDepartmentService teacherDepartmentService = mock(TeacherDepartmentService.class);
        WorkloadController controller = new WorkloadController(
                scheduleQueryService,
                classroomRepository,
                departmentRepository,
                teacherDepartmentService
        );
        LocalDate startDate = LocalDate.of(2026, 5, 1);
        LocalDate endDate = LocalDate.of(2026, 5, 31);
        LocalDate date = LocalDate.of(2026, 5, 15);
        when(scheduleQueryService.searchForAggregation(any(), any(), any(), any()))
                .thenReturn(List.of());
        when(departmentRepository.findAll()).thenReturn(List.of());
        when(classroomRepository.findByStatusNot(LifecycleEntity.STATUS_ARCHIVED)).thenReturn(List.of());
        authenticate(Role.DEPARTMENT, 7L);

        controller.teacherWorkload(startDate, endDate, null);
        controller.classroomWorkload(startDate, endDate, 7L);
        controller.departmentWorkload(startDate, endDate, null);
        controller.timeSlotWorkload(startDate, endDate, 7L);
        controller.freeClassrooms(date, 3L);

        verify(scheduleQueryService, times(4))
                .searchForAggregation(eq(7L), isNull(), eq(startDate), eq(endDate));
        verify(scheduleQueryService)
                .searchForAggregation(eq(7L), eq(3L), eq(date), eq(date));
    }

    @Test
    void departmentRoleCannotRequestAnotherDepartment() {
        ScheduleQueryService scheduleQueryService = mock(ScheduleQueryService.class);
        WorkloadController controller = new WorkloadController(
                scheduleQueryService,
                mock(ClassroomRepository.class),
                mock(DepartmentRepository.class),
                mock(TeacherDepartmentService.class)
        );
        authenticate(Role.DEPARTMENT, 7L);

        assertThatThrownBy(() -> controller.teacherWorkload(
                LocalDate.of(2026, 5, 1),
                LocalDate.of(2026, 5, 31),
                8L
        ))
                .isInstanceOfSatisfying(ResponseStatusException.class, exception -> {
                    assertThat(exception.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
                    assertThat(exception.getReason()).isEqualTo("Нельзя просматривать нагрузку другой кафедры");
                });
        verifyNoInteractions(scheduleQueryService);
    }

    @Test
    void privilegedRoleKeepsGlobalScope() {
        ScheduleQueryService scheduleQueryService = mock(ScheduleQueryService.class);
        WorkloadController controller = new WorkloadController(
                scheduleQueryService,
                mock(ClassroomRepository.class),
                mock(DepartmentRepository.class),
                mock(TeacherDepartmentService.class)
        );
        LocalDate startDate = LocalDate.of(2026, 5, 1);
        LocalDate endDate = LocalDate.of(2026, 5, 31);
        when(scheduleQueryService.searchForAggregation(any(), any(), any(), any()))
                .thenReturn(List.of());
        authenticate(Role.EDUCATION_OFFICE, null);

        controller.timeSlotWorkload(startDate, endDate, null);

        verify(scheduleQueryService)
                .searchForAggregation(isNull(), isNull(), eq(startDate), eq(endDate));
    }

    private Classroom classroom(Long id, Boolean available) {
        Classroom classroom = new Classroom();
        classroom.setId(id);
        classroom.setName("Аудитория " + id);
        classroom.setCapacity(30);
        classroom.setIsAvailable(available);
        return classroom;
    }

    private RenderedLessonDto lesson(Long teacherId,
                                     String teacherName,
                                     LocalDate date,
                                     Long timeSlotId) {
        RenderedLessonDto lesson = mock(RenderedLessonDto.class);
        when(lesson.teacherId()).thenReturn(teacherId);
        when(lesson.teacherName()).thenReturn(teacherName);
        when(lesson.date()).thenReturn(date);
        when(lesson.timeSlotId()).thenReturn(timeSlotId);
        return lesson;
    }

    private TeacherDepartmentAssignment assignment(Long teacherId,
                                                   Long departmentId,
                                                   LocalDate validFrom,
                                                   LocalDate validTo) {
        User teacher = new User();
        teacher.setId(teacherId);
        Department department = new Department(departmentId, "Кафедра " + departmentId, departmentId);
        TeacherDepartmentAssignment assignment = new TeacherDepartmentAssignment();
        assignment.setTeacher(teacher);
        assignment.setDepartment(department);
        assignment.setValidFrom(validFrom);
        assignment.setValidTo(validTo);
        assignment.setPrimaryAssignment(true);
        return assignment;
    }

    private void authenticate(Role role, Long departmentId) {
        AuthContext.setCurrentUser(new AuthenticatedUser(100L, "test", role, departmentId));
    }
}
