package com.magistr.app.service;

import com.magistr.app.dto.RenderedLessonDto;
import com.magistr.app.model.Classroom;
import com.magistr.app.model.Role;
import com.magistr.app.model.ScheduleOverride;
import com.magistr.app.model.ScheduleParity;
import com.magistr.app.model.ScheduleRule;
import com.magistr.app.model.ScheduleRuleSlot;
import com.magistr.app.model.Semester;
import com.magistr.app.model.StudentGroup;
import com.magistr.app.model.TimeSlot;
import com.magistr.app.model.User;
import com.magistr.app.repository.GroupRepository;
import com.magistr.app.repository.ScheduleOverrideRepository;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class ScheduleQueryServiceOverrideTest {

    private static final LocalDate DATE = LocalDate.of(2026, 9, 1);
    private static final LocalDate TARGET_DATE = LocalDate.of(2026, 9, 10);

    @Test
    void appliesAllReplacementFieldsToProvidedDaySnapshot() {
        ScheduleQueryService service = service();
        RenderedLessonDto base = lesson();
        ScheduleOverride override = override("MOVE");

        TimeSlot newTimeSlot = new TimeSlot();
        newTimeSlot.setId(2L);
        newTimeSlot.setOrderNumber(2);
        newTimeSlot.setStartTime(LocalTime.of(10, 40));
        newTimeSlot.setEndTime(LocalTime.of(12, 10));
        override.setNewTimeSlot(newTimeSlot);

        Classroom newClassroom = new Classroom();
        newClassroom.setId(22L);
        newClassroom.setName("Б-202");
        override.setNewClassroom(newClassroom);

        User newTeacher = new User();
        newTeacher.setId(12L);
        newTeacher.setRole(Role.TEACHER);
        newTeacher.setFullName("Новый преподаватель");
        override.setNewTeacher(newTeacher);
        override.setNewLessonFormat("Онлайн");

        List<RenderedLessonDto> result = service.buildEffectiveDayForAllGroups(
                List.of(base),
                DATE,
                List.of(override)
        );

        assertThat(result).singleElement().satisfies(lesson -> {
            assertThat(lesson.timeSlotId()).isEqualTo(2L);
            assertThat(lesson.startTime()).isEqualTo(LocalTime.of(10, 40));
            assertThat(lesson.endTime()).isEqualTo(LocalTime.of(12, 10));
            assertThat(lesson.teacherId()).isEqualTo(12L);
            assertThat(lesson.teacherName()).isEqualTo("Новый преподаватель");
            assertThat(lesson.classroomId()).isEqualTo(22L);
            assertThat(lesson.classroomName()).isEqualTo("Б-202");
            assertThat(lesson.lessonFormat()).isEqualTo("Онлайн");
            assertThat(lesson.groupIds()).containsExactly(31L);
        });
    }

    @Test
    void removesCancelledOccurrenceFromProvidedDaySnapshot() {
        ScheduleQueryService service = service();

        List<RenderedLessonDto> result = service.buildEffectiveDayForAllGroups(
                List.of(lesson()),
                DATE,
                List.of(override("CANCEL"))
        );

        assertThat(result).isEmpty();
    }

    @Test
    void ignoresOverrideForAnotherDate() {
        ScheduleQueryService service = service();
        ScheduleOverride override = override("CANCEL");
        override.setLessonDate(DATE.plusDays(1));

        List<RenderedLessonDto> result = service.buildEffectiveDayForAllGroups(
                List.of(lesson()),
                DATE,
                List.of(override)
        );

        assertThat(result).singleElement().extracting(RenderedLessonDto::scheduleRuleSlotId).isEqualTo(101L);
    }

    @Test
    void movesOccurrenceToTargetDateAndRecalculatesCalendarFields() {
        AcademicDateService academicDateService = mock(AcademicDateService.class);
        ScheduleQueryService service = service(academicDateService);
        ScheduleOverride override = relocationOverride();
        when(academicDateService.getWeekNumber(any(Semester.class), eq(TARGET_DATE))).thenReturn(2);
        when(academicDateService.getParity(any(Semester.class), eq(TARGET_DATE))).thenReturn(ScheduleParity.EVEN);

        List<RenderedLessonDto> result = service.buildEffectiveDayForAllGroups(
                List.of(lesson()),
                DATE,
                List.of(override)
        );

        assertThat(result).singleElement().satisfies(moved -> {
            assertThat(moved.date()).isEqualTo(TARGET_DATE);
            assertThat(moved.dayOfWeek()).isEqualTo(4);
            assertThat(moved.dayName()).isEqualTo("Четверг");
            assertThat(moved.weekNumber()).isEqualTo(2);
            assertThat(moved.parity()).isEqualTo(ScheduleParity.EVEN);
            assertThat(moved.timeSlotId()).isEqualTo(2L);
            assertThat(moved.scheduleOverrideId()).isEqualTo(700L);
            assertThat(moved.overrideAction()).isEqualTo("MOVE");
            assertThat(moved.originalLessonDate()).isEqualTo(DATE);
            assertThat(moved.lessonTypeAcademicHours()).isEqualTo(2);
        });
    }

    @Test
    void searchIncludesIncomingMoveWhenOnlyTargetDateIsRequested() {
        ScheduleGeneratorService generator = mock(ScheduleGeneratorService.class);
        GroupRepository groupRepository = mock(GroupRepository.class);
        ScheduleOverrideRepository overrideRepository = mock(ScheduleOverrideRepository.class);
        StudentGroupLifecycleService lifecycleService = mock(StudentGroupLifecycleService.class);
        AcademicDateService academicDateService = mock(AcademicDateService.class);
        ScheduleQueryService service = new ScheduleQueryService(
                generator,
                groupRepository,
                overrideRepository,
                lifecycleService,
                academicDateService
        );
        StudentGroup group = new StudentGroup();
        group.setId(31L);
        group.setName("Группа 31");
        ScheduleOverride override = relocationOverride();

        when(groupRepository.findAll()).thenReturn(List.of(group));
        when(lifecycleService.mayHaveScheduleInRange(eq(group), any(LocalDate.class), any(LocalDate.class)))
                .thenReturn(true);
        when(generator.buildScheduleForGroups(anyList(), eq(TARGET_DATE), eq(TARGET_DATE))).thenReturn(List.of());
        when(generator.buildScheduleForGroups(anyList(), eq(DATE), eq(DATE))).thenReturn(List.of(lesson()));
        when(overrideRepository.findByLessonDateBetweenWithDetails(TARGET_DATE, TARGET_DATE))
                .thenReturn(List.of(override));
        when(academicDateService.getWeekNumber(any(Semester.class), eq(TARGET_DATE))).thenReturn(2);
        when(academicDateService.getParity(any(Semester.class), eq(TARGET_DATE))).thenReturn(ScheduleParity.EVEN);

        List<RenderedLessonDto> result = service.search(
                null, null, null, null, null, null, null, null, TARGET_DATE, TARGET_DATE
        );

        assertThat(result).singleElement().satisfies(moved -> {
            assertThat(moved.date()).isEqualTo(TARGET_DATE);
            assertThat(moved.originalLessonDate()).isEqualTo(DATE);
            assertThat(moved.scheduleRuleSlotId()).isEqualTo(101L);
        });
    }

    private ScheduleQueryService service() {
        return new ScheduleQueryService(
                mock(ScheduleGeneratorService.class),
                mock(GroupRepository.class),
                mock(ScheduleOverrideRepository.class),
                mock(StudentGroupLifecycleService.class)
        );
    }

    private ScheduleQueryService service(AcademicDateService academicDateService) {
        return new ScheduleQueryService(
                mock(ScheduleGeneratorService.class),
                mock(GroupRepository.class),
                mock(ScheduleOverrideRepository.class),
                mock(StudentGroupLifecycleService.class),
                academicDateService
        );
    }

    private ScheduleOverride relocationOverride() {
        ScheduleOverride override = override("MOVE");
        ReflectionTestUtils.setField(override, "id", 700L);
        override.setTargetLessonDate(TARGET_DATE);
        TimeSlot targetTime = new TimeSlot();
        targetTime.setId(2L);
        targetTime.setOrderNumber(2);
        targetTime.setStartTime(LocalTime.of(10, 40));
        targetTime.setEndTime(LocalTime.of(12, 10));
        override.setNewTimeSlot(targetTime);

        Semester semester = new Semester();
        semester.setId(80L);
        semester.setStartDate(LocalDate.of(2026, 9, 1));
        semester.setEndDate(LocalDate.of(2027, 1, 31));
        ScheduleRule rule = new ScheduleRule();
        rule.setSemester(semester);
        override.getBaseRuleSlot().setScheduleRule(rule);
        return override;
    }

    private ScheduleOverride override(String action) {
        ScheduleRuleSlot baseSlot = new ScheduleRuleSlot();
        baseSlot.setId(101L);
        ScheduleOverride override = new ScheduleOverride();
        override.setBaseRuleSlot(baseSlot);
        override.setLessonDate(DATE);
        override.setAction(action);
        return override;
    }

    private RenderedLessonDto lesson() {
        return new RenderedLessonDto(
                900L,
                101L,
                DATE,
                2,
                "Вторник",
                1,
                ScheduleParity.BOTH,
                1L,
                1,
                LocalTime.of(9, 0),
                LocalTime.of(10, 30),
                501L,
                "Тестовая дисциплина",
                11L,
                "Исходный преподаватель",
                21L,
                "А-101",
                601L,
                "Лекция",
                "Очно",
                null,
                null,
                List.of(),
                List.of(),
                List.of(31L),
                List.of("Группа 31"),
                "Т",
                2,
                0,
                0,
                ScheduleParity.BOTH
        );
    }
}
