package com.magistr.app.controller;

import com.magistr.app.config.auth.RequireRoles;
import com.magistr.app.dto.AcademicCalendarSubjectDto;
import com.magistr.app.dto.CreateGroupRequest;
import com.magistr.app.dto.GroupCalendarAssignmentDto;
import com.magistr.app.dto.GroupResponse;
import com.magistr.app.model.AcademicCalendar;
import com.magistr.app.model.AcademicCalendarSubject;
import com.magistr.app.model.AcademicYear;
import com.magistr.app.model.EducationForm;
import com.magistr.app.model.Role;
import com.magistr.app.model.Speciality;
import com.magistr.app.model.SpecialtyProfile;
import com.magistr.app.model.Subject;
import com.magistr.app.model.StudentGroup;
import com.magistr.app.model.StudentGroupCalendarAssignment;
import com.magistr.app.model.StudentGroupStudyState;
import com.magistr.app.repository.*;
import com.magistr.app.service.ScheduleGeneratorService;
import com.magistr.app.service.StudentGroupLifecycleService;
import com.magistr.app.utils.CourseAndSemesterCalculator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

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

    private static final Logger logger = LoggerFactory.getLogger(GroupController.class);

    private final GroupRepository groupRepository;
    private final EducationFormRepository educationFormRepository;
    private final SpecialtiesRepository specialtiesRepository;
    private final SpecialtyProfileRepository specialtyProfileRepository;
    private final AcademicYearRepository academicYearRepository;
    private final AcademicCalendarRepository academicCalendarRepository;
    private final AcademicCalendarSubjectRepository calendarSubjectRepository;
    private final StudentGroupCalendarAssignmentRepository assignmentRepository;
    private final ScheduleGeneratorService scheduleGeneratorService;
    private final StudentGroupLifecycleService groupLifecycleService;

    public GroupController(GroupRepository groupRepository,
                           EducationFormRepository educationFormRepository,
                           SpecialtiesRepository specialtiesRepository,
                           SpecialtyProfileRepository specialtyProfileRepository,
                           AcademicYearRepository academicYearRepository,
                           AcademicCalendarRepository academicCalendarRepository,
                           AcademicCalendarSubjectRepository calendarSubjectRepository,
                           StudentGroupCalendarAssignmentRepository assignmentRepository,
                           ScheduleGeneratorService scheduleGeneratorService,
                           StudentGroupLifecycleService groupLifecycleService) {
        this.groupRepository = groupRepository;
        this.educationFormRepository = educationFormRepository;
        this.specialtiesRepository = specialtiesRepository;
        this.specialtyProfileRepository = specialtyProfileRepository;
        this.academicYearRepository = academicYearRepository;
        this.academicCalendarRepository = academicCalendarRepository;
        this.calendarSubjectRepository = calendarSubjectRepository;
        this.assignmentRepository = assignmentRepository;
        this.scheduleGeneratorService = scheduleGeneratorService;
        this.groupLifecycleService = groupLifecycleService;
    }

    @GetMapping
    public List<GroupResponse> getAllGroups(@RequestParam(defaultValue = "false") boolean includeArchived) {
        logger.info("Получен запрос на получение всех групп");

        try {
            List<StudentGroup> groups = includeArchived
                    ? groupRepository.findAll()
                    : groupRepository.findAll().stream()
                    .filter(group -> groupLifecycleService.isAvailableForSelection(group, LocalDate.now()))
                    .toList();

            List<GroupResponse> response = groups.stream()
                    .map(this::mapToResponse)
                    .toList();
            logger.info("Получено {} групп", response.size());
            return response;
        } catch (Exception e) {
            logger.error("Ошибка при получении списка групп: {}", e.getMessage(), e);
            throw e;
        }
    }

    @GetMapping("/{departmentId}")
    public ResponseEntity<?> getGroupsByDepartmentId(@PathVariable Long departmentId) {
        logger.info("Получен запрос на получение списка групп для кафедры с ID - {}", departmentId);
        try {
            List<StudentGroup> groups = groupRepository.findByDepartmentId(departmentId).stream()
                    .filter(group -> groupLifecycleService.isAvailableForSelection(group, LocalDate.now()))
                    .toList();

            if(groups.isEmpty()) {
                logger.info("Группы для кафедры с ID - {} не найдены", departmentId);
                return ResponseEntity.status(HttpStatus.NOT_FOUND)
                        .body("Группы для указанной кафедры не найдены");
            }

            List<GroupResponse> response = groups.stream()
                    .map(this::mapToResponse)
                    .toList();

            logger.info("Найдено {} групп для кафедры с ID - {}", response.size(), departmentId);

            return ResponseEntity.ok(response);
        } catch (Exception e) {
            logger.error("Получена ошибка при получении списка групп для кафедры с ID - {}: {}", departmentId, e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("Произошла ошибка при получении списка групп");
        }
    }

    @PostMapping
    @RequireRoles({Role.ADMIN})
    public ResponseEntity<?> createGroup(@RequestBody CreateGroupRequest request) {
        logger.info("Получен запрос на создание новой группы: name = {}, groupSize = {}, educationFormId = {}, departmentId = {}, yearStartStudy = {}",
                request.getName(), request.getGroupSize(), request.getEducationFormId(), request.getDepartmentId(), request.getYearStartStudy());
        try {
            ResponseEntity<?> validationError = validateGroupRequest(request);
            if (validationError != null) {
                return validationError;
            }

            Optional<EducationForm> efOpt = educationFormRepository.findById(request.getEducationFormId());
            if (efOpt.isEmpty()) {
                return ResponseEntity.badRequest().body(Map.of("message", "Форма обучения не найдена"));
            }
            Speciality speciality = specialtiesRepository.findById(request.getEffectiveSpecialtyId())
                    .orElse(null);
            if (speciality == null) {
                return ResponseEntity.badRequest().body(Map.of("message", "Специальность не найдена"));
            }
            SpecialtyProfile profile = specialtyProfileRepository.findById(request.getSpecialtyProfileId())
                    .orElse(null);
            if (profile == null || !profile.getSpeciality().getId().equals(speciality.getId())) {
                return ResponseEntity.badRequest().body(Map.of("message", "Профиль обучения не найден для выбранной специальности"));
            }

            StudentGroup group = new StudentGroup();
            group.setName(request.getName().trim());
            group.setGroupSize(request.getGroupSize());
            group.setEducationForm(efOpt.get());
            group.setDepartmentId(request.getDepartmentId());
            group.setYearStartStudy(request.getYearStartStudy());
            group.setSpeciality(speciality);
            group.setSpecialtyProfile(profile);
            groupRepository.save(group);

            logger.info("Группа успешно создана с ID - {}", group.getId());

            return ResponseEntity.ok(mapToResponse(group));
        } catch (Exception e ) {
            logger.error("Ошибка при создании группы: {}", e.getMessage(), e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(Map.of("message", "Произошла ошибка при создании группы: " + e.getMessage()));
        }
    }

    @PutMapping("/{id}")
    @RequireRoles({Role.ADMIN})
    public ResponseEntity<?> updateGroup(@PathVariable Long id, @RequestBody CreateGroupRequest request) {
        logger.info("Получен запрос на обновление группы с ID - {}", id);
        try {
            Optional<StudentGroup> groupOpt = groupRepository.findById(id);
            if (groupOpt.isEmpty()) {
                logger.info("Группа с ID - {} не найдена", id);
                return ResponseEntity.notFound().build();
            }

            ResponseEntity<?> validationError = validateGroupRequest(request);
            if (validationError != null) {
                return validationError;
            }

            Optional<EducationForm> efOpt = educationFormRepository.findById(request.getEducationFormId());
            if (efOpt.isEmpty()) {
                return ResponseEntity.badRequest().body(Map.of("message", "Форма обучения не найдена"));
            }
            Speciality speciality = specialtiesRepository.findById(request.getEffectiveSpecialtyId())
                    .orElse(null);
            if (speciality == null) {
                return ResponseEntity.badRequest().body(Map.of("message", "Специальность не найдена"));
            }
            SpecialtyProfile profile = specialtyProfileRepository.findById(request.getSpecialtyProfileId())
                    .orElse(null);
            if (profile == null || !profile.getSpeciality().getId().equals(speciality.getId())) {
                return ResponseEntity.badRequest().body(Map.of("message", "Профиль обучения не найден для выбранной специальности"));
            }

            StudentGroup group = groupOpt.get();
            group.setName(request.getName().trim());
            group.setGroupSize(request.getGroupSize());
            group.setEducationForm(efOpt.get());
            group.setDepartmentId(request.getDepartmentId());
            group.setYearStartStudy(request.getYearStartStudy());
            group.setSpeciality(speciality);
            group.setSpecialtyProfile(profile);
            groupRepository.save(group);

            logger.info("Группа с ID - {} успешно обновлена", id);
            return ResponseEntity.ok(mapToResponse(group));
        } catch (Exception e) {
            logger.error("Ошибка при обновлении группы с ID - {}: {}", id, e.getMessage(), e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(Map.of("message", "Произошла ошибка при обновлении группы: " + e.getMessage()));
        }
    }

    @DeleteMapping("/{id}")
    @RequireRoles({Role.ADMIN})
    public ResponseEntity<?> deleteGroup(@PathVariable Long id) {
        logger.info("Получен запрос на удаление группы с ID - {}", id);
        StudentGroup group = groupRepository.findById(id).orElse(null);
        if (group == null) {
            logger.info("Группа с ID - {} не найдена", id);
            return ResponseEntity.notFound().build();
        }
        group.archive("Группа архивирована");
        groupRepository.save(group);
        logger.info("Группа с ID - {} успешно архивирована", id);
        return ResponseEntity.ok(Map.of("message", "Группа архивирована"));
    }

    @PostMapping("/{id}/restore")
    @RequireRoles({Role.ADMIN})
    public ResponseEntity<?> restoreGroup(@PathVariable Long id) {
        StudentGroup group = groupRepository.findById(id).orElse(null);
        if (group == null) {
            return ResponseEntity.notFound().build();
        }
        group.restore();
        groupRepository.save(group);
        return ResponseEntity.ok(mapToResponse(group));
    }

    private ResponseEntity<?> validateGroupRequest(CreateGroupRequest request) {
        if (request.getName() == null || request.getName().isBlank()) {
            return validationError("Название группы обязательно");
        }
        if (request.getGroupSize() == null) {
            return validationError("Численность группы обязательна");
        }
        if (request.getEducationFormId() == null) {
            return validationError("Форма обучения обязательна");
        }
        if (request.getDepartmentId() == null || request.getDepartmentId() == 0) {
            return validationError("ID кафедры обязателен");
        }
        if (request.getYearStartStudy() == null || request.getYearStartStudy() == 0) {
            return validationError("Год начала обучения обязателен");
        }
        if (request.getEffectiveSpecialtyId() == null || request.getEffectiveSpecialtyId() == 0) {
            return validationError("Код специальности обязателен");
        }
        if (request.getSpecialtyProfileId() == null || request.getSpecialtyProfileId() == 0) {
            return validationError("Профиль обучения обязателен");
        }
        return null;
    }

    private ResponseEntity<?> validationError(String errorMessage) {
        logger.error("Ошибка валидации: {}", errorMessage);
        return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
    }

    private GroupResponse mapToResponse(StudentGroup group) {
        int course = CourseAndSemesterCalculator.getActualCourse(group.getYearStartStudy());
        int semester = CourseAndSemesterCalculator.getActualSemester(group.getYearStartStudy());
        Speciality speciality = group.getSpeciality();
        SpecialtyProfile profile = group.getSpecialtyProfile();
        LocalDate today = LocalDate.now();
        StudentGroupStudyState studyState = groupLifecycleService.getStudyState(group, today);
        return new GroupResponse(
                group.getId(),
                group.getName(),
                group.getGroupSize(),
                group.getEducationForm().getId(),
                group.getEducationForm().getName(),
                group.getDepartmentId(),
                group.getYearStartStudy(),
                course,
                semester,
                speciality.getId(),
                speciality.getSpecialityCode(),
                speciality.getSpecialityName(),
                profile.getId(),
                profile.getName(),
                group.getStatus(),
                groupLifecycleService.isAvailableForSelection(group, today),
                studyState.name(),
                studyState.getLabel()
        );
    }

    @GetMapping("/{id}/calendar-assignments")
    @RequireRoles({Role.ADMIN, Role.EDUCATION_OFFICE})
    public ResponseEntity<?> getCalendarAssignments(@PathVariable Long id) {
        if (!groupRepository.existsById(id)) {
            return ResponseEntity.notFound().build();
        }
        List<StudentGroupCalendarAssignment> assignments = assignmentRepository.findByGroupIdWithDetails(id);
        Map<Long, List<AcademicCalendarSubjectDto>> subjectsByCalendarId = loadCalendarSubjectsByCalendar(assignments);
        return ResponseEntity.ok(assignments.stream()
                .map(assignment -> toAssignmentDto(assignment, subjectsByCalendarId))
                .toList());
    }

    @PutMapping("/{id}/calendar-assignments")
    @RequireRoles({Role.ADMIN, Role.EDUCATION_OFFICE})
    public ResponseEntity<?> saveCalendarAssignment(@PathVariable Long id,
                                                    @RequestBody GroupCalendarAssignmentDto request) {
        if (request == null || request.academicYearId() == null || request.calendarId() == null) {
            return ResponseEntity.badRequest().body(Map.of("message", "Учебный год и календарный график обязательны"));
        }
        StudentGroup group = groupRepository.findById(id).orElse(null);
        if (group == null) {
            return ResponseEntity.notFound().build();
        }
        AcademicYear academicYear = academicYearRepository.findById(request.academicYearId()).orElse(null);
        if (academicYear == null) {
            return ResponseEntity.badRequest().body(Map.of("message", "Учебный год не найден"));
        }
        AcademicCalendar calendar = academicCalendarRepository.findByIdWithDetails(request.calendarId()).orElse(null);
        if (calendar == null) {
            return ResponseEntity.badRequest().body(Map.of("message", "Календарный график не найден"));
        }
        if (!calendar.getAcademicYear().getId().equals(academicYear.getId())) {
            return ResponseEntity.badRequest().body(Map.of("message", "График относится к другому учебному году"));
        }
        if (!calendar.getSpeciality().getId().equals(group.getSpeciality().getId())
                || !calendar.getSpecialtyProfile().getId().equals(group.getSpecialtyProfile().getId())) {
            return ResponseEntity.badRequest().body(Map.of("message", "График не соответствует специальности и профилю группы"));
        }
        if (!calendar.getStudyForm().getId().equals(group.getEducationForm().getId())) {
            return ResponseEntity.badRequest().body(Map.of("message", "График не соответствует форме обучения группы"));
        }

        StudentGroupCalendarAssignment assignment = assignmentRepository
                .findByGroupIdAndAcademicYearIdWithDetails(group.getId(), academicYear.getId())
                .orElseGet(StudentGroupCalendarAssignment::new);
        assignment.setStudentGroup(group);
        assignment.setAcademicYear(academicYear);
        assignment.setAcademicCalendar(calendar);
        scheduleGeneratorService.clearCache();
        StudentGroupCalendarAssignment saved = assignmentRepository.save(assignment);
        List<AcademicCalendarSubjectDto> subjects = calendarSubjectRepository
                .findByCalendarIdWithSubject(calendar.getId())
                .stream()
                .map(this::toCalendarSubjectDto)
                .toList();
        return ResponseEntity.ok(toAssignmentDto(saved, subjects));
    }

    @DeleteMapping("/{id}/calendar-assignments/{assignmentId}")
    @RequireRoles({Role.ADMIN, Role.EDUCATION_OFFICE})
    public ResponseEntity<?> deleteCalendarAssignment(@PathVariable Long id, @PathVariable Long assignmentId) {
        StudentGroupCalendarAssignment assignment = assignmentRepository.findById(assignmentId).orElse(null);
        if (assignment == null || !assignment.getStudentGroup().getId().equals(id)) {
            return ResponseEntity.notFound().build();
        }
        assignmentRepository.delete(assignment);
        scheduleGeneratorService.clearCache();
        return ResponseEntity.ok(Map.of("message", "Назначение календарного графика удалено"));
    }

    private Map<Long, List<AcademicCalendarSubjectDto>> loadCalendarSubjectsByCalendar(
            List<StudentGroupCalendarAssignment> assignments) {
        Set<Long> calendarIds = assignments.stream()
                .map(assignment -> assignment.getAcademicCalendar().getId())
                .collect(Collectors.toSet());
        if (calendarIds.isEmpty()) {
            return Collections.emptyMap();
        }
        return calendarSubjectRepository.findByCalendarIdInWithSubject(calendarIds).stream()
                .collect(Collectors.groupingBy(
                        calendarSubject -> calendarSubject.getAcademicCalendar().getId(),
                        Collectors.mapping(this::toCalendarSubjectDto, Collectors.toList())
                ));
    }

    private GroupCalendarAssignmentDto toAssignmentDto(StudentGroupCalendarAssignment assignment,
                                                       Map<Long, List<AcademicCalendarSubjectDto>> subjectsByCalendarId) {
        return toAssignmentDto(
                assignment,
                subjectsByCalendarId.getOrDefault(assignment.getAcademicCalendar().getId(), List.of())
        );
    }

    private GroupCalendarAssignmentDto toAssignmentDto(StudentGroupCalendarAssignment assignment,
                                                       List<AcademicCalendarSubjectDto> subjects) {
        return new GroupCalendarAssignmentDto(
                assignment.getId(),
                assignment.getStudentGroup().getId(),
                assignment.getStudentGroup().getName(),
                assignment.getAcademicYear().getId(),
                assignment.getAcademicYear().getTitle(),
                assignment.getAcademicCalendar().getId(),
                assignment.getAcademicCalendar().getTitle(),
                subjects
        );
    }

    private AcademicCalendarSubjectDto toCalendarSubjectDto(AcademicCalendarSubject calendarSubject) {
        Subject subject = calendarSubject.getSubject();
        return new AcademicCalendarSubjectDto(
                calendarSubject.getId(),
                calendarSubject.getAcademicCalendar().getId(),
                calendarSubject.getSemesterNumber(),
                subject.getId(),
                subject.getName(),
                subject.getCode(),
                subject.getDepartmentId()
        );
    }
}
