package com.magistr.app.service;

import com.magistr.app.dto.DepartmentTransferRequest;
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.DepartmentRepository;
import com.magistr.app.repository.TeacherDepartmentAssignmentRepository;
import com.magistr.app.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

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

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class TeacherDepartmentServiceTest {

    private static final LocalDate TODAY = LocalDate.of(2026, 7, 18);

    @Mock
    private TeacherDepartmentAssignmentRepository assignmentRepository;
    @Mock
    private UserRepository userRepository;
    @Mock
    private DepartmentRepository departmentRepository;

    private TeacherDepartmentService service;
    private Department oldDepartment;
    private Department newDepartment;
    private User teacher;

    @BeforeEach
    void setUp() {
        service = new TeacherDepartmentService(
                assignmentRepository,
                userRepository,
                departmentRepository
        );
        oldDepartment = new Department(1L, "Кафедра ИБ", 1L);
        newDepartment = new Department(2L, "Кафедра ВТ", 2L);
        teacher = teacher();
    }

    @Test
    void futureTransferKeepsCurrentLegacyDepartmentUntilEffectiveDate() {
        LocalDate validFrom = TODAY.plusMonths(1);
        TeacherDepartmentAssignment current = assignment(
                oldDepartment,
                LocalDate.of(2025, 9, 1),
                null,
                true
        );
        prepareTransfer(current);

        TeacherDepartmentAssignment saved = service.transferTeacher(
                teacher.getId(),
                new DepartmentTransferRequest(newDepartment.getId(), validFrom, "Будущий перевод"),
                TODAY,
                99L
        );

        assertThat(current.getValidTo()).isEqualTo(validFrom.minusDays(1));
        assertThat(saved.getDepartment()).isEqualTo(newDepartment);
        assertThat(saved.getValidFrom()).isEqualTo(validFrom);
        assertThat(saved.getValidTo()).isNull();
        assertThat(teacher.getDepartmentId()).isEqualTo(oldDepartment.getId());
        verify(userRepository, never()).save(any());
    }

    @Test
    void effectiveTransferSynchronizesLegacyDepartment() {
        TeacherDepartmentAssignment current = assignment(
                oldDepartment,
                LocalDate.of(2025, 9, 1),
                null,
                true
        );
        prepareTransfer(current);

        service.transferTeacher(
                teacher.getId(),
                new DepartmentTransferRequest(newDepartment.getId(), TODAY, null),
                TODAY,
                99L
        );

        assertThat(current.getValidTo()).isEqualTo(TODAY.minusDays(1));
        assertThat(teacher.getDepartmentId()).isEqualTo(newDepartment.getId());
        verify(userRepository).save(teacher);
    }

    @Test
    void historicalDepartmentIncludesTeacherArchivedAfterTargetDate() {
        teacher.setStatus(LifecycleEntity.STATUS_ARCHIVED);
        teacher.setActiveTo(LocalDate.of(2026, 1, 31));
        TeacherDepartmentAssignment assignment = assignment(
                oldDepartment,
                LocalDate.of(2025, 9, 1),
                null,
                true
        );
        when(assignmentRepository.findDepartmentTeachersAtDate(
                oldDepartment.getId(),
                LocalDate.of(2025, 12, 1)
        )).thenReturn(List.of(assignment));
        when(assignmentRepository.findDepartmentTeachersAtDate(
                oldDepartment.getId(),
                LocalDate.of(2026, 2, 1)
        )).thenReturn(List.of(assignment));

        assertThat(service.findTeachersForDepartmentAtDate(
                oldDepartment.getId(),
                LocalDate.of(2025, 12, 1)
        )).containsExactly(teacher);
        assertThat(service.findTeachersForDepartmentAtDate(
                oldDepartment.getId(),
                LocalDate.of(2026, 2, 1)
        )).isEmpty();
    }

    @Test
    void primaryAssignmentWinsOverAdditionalAssignment() {
        TeacherDepartmentAssignment additional = assignment(
                newDepartment,
                LocalDate.of(2025, 9, 1),
                null,
                false
        );
        TeacherDepartmentAssignment primary = assignment(
                oldDepartment,
                LocalDate.of(2025, 9, 1),
                null,
                true
        );

        assertThat(service.resolveDepartmentIdAtDate(
                List.of(additional, primary),
                TODAY
        )).contains(oldDepartment.getId());
    }

    @Test
    void additionalAssignmentIsUsedWhenPrimaryIsOutsideTargetDate() {
        TeacherDepartmentAssignment oldPrimary = assignment(
                oldDepartment,
                LocalDate.of(2025, 9, 1),
                TODAY.minusDays(1),
                true
        );
        TeacherDepartmentAssignment additional = assignment(
                newDepartment,
                LocalDate.of(2025, 9, 1),
                null,
                false
        );

        assertThat(service.resolveDepartmentIdAtDate(
                List.of(oldPrimary, additional),
                TODAY
        )).contains(newDepartment.getId());
    }

    private void prepareTransfer(TeacherDepartmentAssignment current) {
        when(assignmentRepository.saveAndFlush(any(TeacherDepartmentAssignment.class)))
                .thenAnswer(invocation -> invocation.getArgument(0));
        when(userRepository.findByIdForUpdate(teacher.getId())).thenReturn(Optional.of(teacher));
        when(departmentRepository.findById(newDepartment.getId()))
                .thenReturn(Optional.of(newDepartment));
        when(assignmentRepository.findPrimaryHistoryForUpdate(teacher.getId()))
                .thenReturn(List.of(current));
    }

    private TeacherDepartmentAssignment assignment(Department department,
                                                   LocalDate validFrom,
                                                   LocalDate validTo,
                                                   boolean primary) {
        TeacherDepartmentAssignment assignment = new TeacherDepartmentAssignment();
        assignment.setTeacher(teacher);
        assignment.setDepartment(department);
        assignment.setValidFrom(validFrom);
        assignment.setValidTo(validTo);
        assignment.setPrimaryAssignment(primary);
        return assignment;
    }

    private User teacher() {
        User value = new User();
        value.setId(10L);
        value.setUsername("teacher");
        value.setRole(Role.TEACHER);
        value.setFullName("Петров Пётр Петрович");
        value.setJobTitle("Доцент");
        value.setDepartmentId(oldDepartment.getId());
        return value;
    }
}
