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.AcademicCalendarSubject;
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.AcademicStructureService;
import com.magistr.app.service.ScheduleGeneratorService;
import com.magistr.app.service.StudentGroupLifecycleService;
import com.magistr.app.service.BusinessTimeService;
import com.magistr.app.utils.CourseAndSemesterCalculator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
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.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 AcademicCalendarSubjectRepository calendarSubjectRepository;
    private final StudentGroupCalendarAssignmentRepository assignmentRepository;
    private final ScheduleGeneratorService scheduleGeneratorService;
    private final StudentGroupLifecycleService groupLifecycleService;
    private final AcademicStructureService academicStructureService;
    private final BusinessTimeService businessTime;

    public GroupController(GroupRepository groupRepository,
                           AcademicCalendarSubjectRepository calendarSubjectRepository,
                           StudentGroupCalendarAssignmentRepository assignmentRepository,
                           ScheduleGeneratorService scheduleGeneratorService,
                           StudentGroupLifecycleService groupLifecycleService,
                           AcademicStructureService academicStructureService) {
        this(groupRepository, calendarSubjectRepository, assignmentRepository,
                scheduleGeneratorService, groupLifecycleService, academicStructureService,
                BusinessTimeService.systemDefault());
    }

    @Autowired
    public GroupController(GroupRepository groupRepository,
                           AcademicCalendarSubjectRepository calendarSubjectRepository,
                           StudentGroupCalendarAssignmentRepository assignmentRepository,
                           ScheduleGeneratorService scheduleGeneratorService,
                           StudentGroupLifecycleService groupLifecycleService,
                           AcademicStructureService academicStructureService,
                           BusinessTimeService businessTime) {
        this.groupRepository = groupRepository;
        this.calendarSubjectRepository = calendarSubjectRepository;
        this.assignmentRepository = assignmentRepository;
        this.scheduleGeneratorService = scheduleGeneratorService;
        this.groupLifecycleService = groupLifecycleService;
        this.academicStructureService = academicStructureService;
        this.businessTime = businessTime;
    }

    @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, businessTime.today()))
                    .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, businessTime.today()))
                    .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("Получен запрос на создание новой группы");
        StudentGroup group = academicStructureService.createGroup(request);
        logger.info("Группа успешно создана с ID - {}", group.getId());
        return ResponseEntity.ok(mapToResponse(group));
    }

    @PutMapping("/{id}")
    @RequireRoles({Role.ADMIN})
    public ResponseEntity<?> updateGroup(@PathVariable Long id, @RequestBody CreateGroupRequest request) {
        logger.info("Получен запрос на обновление группы с ID - {}", id);
        StudentGroup group = academicStructureService.updateGroup(id, request);
        logger.info("Группа с ID - {} успешно обновлена", id);
        return ResponseEntity.ok(mapToResponse(group));
    }

    @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("Группа архивирована", businessTime.today(), businessTime.now());
        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 GroupResponse mapToResponse(StudentGroup group) {
        LocalDate today = businessTime.today();
        int course = CourseAndSemesterCalculator.getActualCourse(group.getYearStartStudy(), today);
        int semester = CourseAndSemesterCalculator.getActualSemester(group.getYearStartStudy(), today);
        Speciality speciality = group.getSpeciality();
        SpecialtyProfile profile = group.getSpecialtyProfile();
        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) {
        StudentGroupCalendarAssignment saved = academicStructureService.saveAssignment(id, request);
        List<AcademicCalendarSubjectDto> subjects = calendarSubjectRepository
                .findByCalendarIdWithSubject(saved.getAcademicCalendar().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()
        );
    }
}
