package com.magistr.app.service;

import com.magistr.app.dto.CreateSubjectRequest;
import com.magistr.app.model.Subject;
import com.magistr.app.repository.SubjectRepository;
import org.junit.jupiter.api.Test;
import org.springframework.dao.DataIntegrityViolationException;

import java.util.List;
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.anyList;
import static org.mockito.Mockito.*;

class SubjectImportServiceTest {

    @Test
    void rejectsSubjectOwnedByAnotherDepartmentWithoutMutation() {
        SubjectRepository repository = mock(SubjectRepository.class);
        Subject foreign = subject(10L, "Информатика", "INF", 2L);
        when(repository.findByNameIgnoreCase("информатика")).thenReturn(Optional.of(foreign));
        SubjectImportService service = new SubjectImportService(repository);

        assertThatThrownBy(() -> service.importSubjects(
                List.of(request("информатика", "NEW", 1L)),
                1L
        ))
                .isInstanceOf(ScheduleConflictException.class)
                .hasMessage("Дисциплина «информатика» уже принадлежит другой кафедре");

        assertThat(foreign.getDepartmentId()).isEqualTo(2L);
        assertThat(foreign.getCode()).isEqualTo("INF");
        verify(repository, never()).saveAllAndFlush(anyList());
    }

    @Test
    void repeatedOwnImportUpdatesSameSubjectAndRestoresIt() {
        SubjectRepository repository = mock(SubjectRepository.class);
        Subject own = subject(10L, "Информатика", "OLD", 1L);
        own.archive("Архив");
        when(repository.findByNameIgnoreCase("информатика")).thenReturn(Optional.of(own));
        when(repository.saveAllAndFlush(anyList())).thenAnswer(invocation -> invocation.getArgument(0));
        SubjectImportService service = new SubjectImportService(repository);

        List<Subject> firstResult = service.importSubjects(
                List.of(request("информатика", " INF-2 ", 1L)),
                1L
        );
        List<Subject> repeatedResult = service.importSubjects(
                List.of(request("информатика", "INF-2", 1L)),
                1L
        );

        assertThat(firstResult).containsExactly(own);
        assertThat(repeatedResult).containsExactly(own);
        assertThat(own.getId()).isEqualTo(10L);
        assertThat(own.getDepartmentId()).isEqualTo(1L);
        assertThat(own.getCode()).isEqualTo("INF-2");
        assertThat(own.isArchivedRecord()).isFalse();
        verify(repository, times(2)).saveAllAndFlush(List.of(own));
    }

    @Test
    void duplicateNamesInsidePayloadAreImportedOnceCaseInsensitively() {
        SubjectRepository repository = mock(SubjectRepository.class);
        when(repository.findByNameIgnoreCase(anyString())).thenReturn(Optional.empty());
        when(repository.saveAllAndFlush(anyList())).thenAnswer(invocation -> invocation.getArgument(0));
        SubjectImportService service = new SubjectImportService(repository);

        List<Subject> result = service.importSubjects(
                List.of(
                        request("Информатика", "OLD", 1L),
                        request(" информатика ", "NEW", 1L)
                ),
                1L
        );

        assertThat(result).singleElement().satisfies(subject -> {
            assertThat(subject.getName()).isEqualTo("информатика");
            assertThat(subject.getCode()).isEqualTo("NEW");
            assertThat(subject.getDepartmentId()).isEqualTo(1L);
        });
    }

    @Test
    void mapsConcurrentUniqueViolationToConflict() {
        SubjectRepository repository = mock(SubjectRepository.class);
        when(repository.findByNameIgnoreCase("Физика")).thenReturn(Optional.empty());
        when(repository.saveAllAndFlush(anyList()))
                .thenThrow(new DataIntegrityViolationException("uq_subjects_name_ci"));
        SubjectImportService service = new SubjectImportService(repository);

        assertThatThrownBy(() -> service.importSubjects(
                List.of(request("Физика", "PHY", 1L)),
                1L
        ))
                .isInstanceOf(ScheduleConflictException.class)
                .hasMessage("Дисциплина с таким названием уже существует");
    }

    private CreateSubjectRequest request(String name, String code, Long departmentId) {
        return new CreateSubjectRequest(null, name, code, departmentId);
    }

    private Subject subject(Long id, String name, String code, Long departmentId) {
        return new Subject(id, name, code, departmentId);
    }
}
