package com.magistr.app.controller;

import com.magistr.app.config.auth.RequireRoles;
import com.magistr.app.dto.*;
import com.magistr.app.model.*;
import com.magistr.app.repository.*;
import com.magistr.app.service.AcademicCalendarGridService;
import com.magistr.app.service.AcademicStructureService;
import com.magistr.app.service.ScheduleGeneratorService;
import com.magistr.app.utils.AcademicCalendarTitleBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/admin/academic-calendars")
@RequireRoles({Role.ADMIN, Role.EDUCATION_OFFICE})
public class AcademicCalendarController {

    private final AcademicCalendarRepository calendarRepository;
    private final AcademicCalendarDayRepository calendarDayRepository;
    private final AcademicYearRepository academicYearRepository;
    private final SpecialtiesRepository specialtiesRepository;
    private final SpecialtyProfileRepository profileRepository;
    private final EducationFormRepository educationFormRepository;
    private final SubjectRepository subjectRepository;
    private final AcademicCalendarSubjectRepository calendarSubjectRepository;
    private final AcademicCalendarGridService calendarGridService;
    private final ScheduleGeneratorService scheduleGeneratorService;
    private final AcademicStructureService academicStructureService;

    public AcademicCalendarController(AcademicCalendarRepository calendarRepository,
                                      AcademicCalendarDayRepository calendarDayRepository,
                                      AcademicYearRepository academicYearRepository,
                                      SpecialtiesRepository specialtiesRepository,
                                      SpecialtyProfileRepository profileRepository,
                                      EducationFormRepository educationFormRepository,
                                      SubjectRepository subjectRepository,
                                      AcademicCalendarSubjectRepository calendarSubjectRepository,
                                      AcademicCalendarGridService calendarGridService,
                                      ScheduleGeneratorService scheduleGeneratorService,
                                      AcademicStructureService academicStructureService) {
        this.calendarRepository = calendarRepository;
        this.calendarDayRepository = calendarDayRepository;
        this.academicYearRepository = academicYearRepository;
        this.specialtiesRepository = specialtiesRepository;
        this.profileRepository = profileRepository;
        this.educationFormRepository = educationFormRepository;
        this.subjectRepository = subjectRepository;
        this.calendarSubjectRepository = calendarSubjectRepository;
        this.calendarGridService = calendarGridService;
        this.scheduleGeneratorService = scheduleGeneratorService;
        this.academicStructureService = academicStructureService;
    }

    @GetMapping
    public List<AcademicCalendarDto> getCalendars(@RequestParam(required = false) Long academicYearId,
                                                  @RequestParam(required = false) Long specialtyId,
                                                  @RequestParam(required = false) Long profileId) {
        return calendarRepository.findAllWithDetails(academicYearId, specialtyId, profileId).stream()
                .map(this::toCalendarDto)
                .toList();
    }

    @GetMapping("/{id}")
    public ResponseEntity<?> getCalendar(@PathVariable Long id) {
        return calendarRepository.findByIdWithDetails(id)
                .<ResponseEntity<?>>map(calendar -> ResponseEntity.ok(toCalendarDto(calendar)))
                .orElseGet(() -> ResponseEntity.notFound().build());
    }

    @PostMapping
    public ResponseEntity<?> createCalendar(@RequestBody AcademicCalendarDto request) {
        try {
            AcademicCalendar calendar = new AcademicCalendar();
            applyCalendar(calendar, request);
            scheduleGeneratorService.clearCache();
            return ResponseEntity.ok(toCalendarDto(calendarRepository.save(calendar)));
        } catch (IllegalArgumentException e) {
            return ResponseEntity.badRequest().body(Map.of("message", e.getMessage()));
        }
    }

    @PutMapping("/{id}")
    public ResponseEntity<?> updateCalendar(@PathVariable Long id, @RequestBody AcademicCalendarDto request) {
        return ResponseEntity.ok(toCalendarDto(academicStructureService.updateCalendar(id, request)));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<?> deleteCalendar(@PathVariable Long id) {
        if (!calendarRepository.existsById(id)) {
            return ResponseEntity.notFound().build();
        }
        calendarRepository.deleteById(id);
        scheduleGeneratorService.clearCache();
        return ResponseEntity.ok(Map.of("message", "Календарный график удалён"));
    }

    @GetMapping("/{id}/grid")
    public ResponseEntity<?> getGrid(@PathVariable Long id) {
        if (!calendarRepository.existsById(id)) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(calendarDayRepository.findByCalendarIdWithActivity(id).stream()
                .map(this::toGridDayDto)
                .toList());
    }

    @PutMapping("/{id}/grid")
    public ResponseEntity<?> saveGrid(@PathVariable("id") Long id,
                                      @RequestBody List<AcademicCalendarGridDayDto> rows) {
        calendarGridService.replaceGrid(id, rows);
        return ResponseEntity.ok(Map.of("message", "Календарный учебный график сохранён"));
    }

    @GetMapping("/{id}/subjects")
    public ResponseEntity<?> getSubjects(@PathVariable Long id) {
        if (!calendarRepository.existsById(id)) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(calendarSubjectRepository.findByCalendarIdWithSubject(id).stream()
                .map(this::toCalendarSubjectDto)
                .toList());
    }

    @PutMapping("/{id}/subjects")
    @Transactional
    public ResponseEntity<?> saveSubjects(@PathVariable Long id,
                                          @RequestBody List<AcademicCalendarSubjectDto> rows) {
        AcademicCalendar calendar = calendarRepository.findByIdWithDetails(id).orElse(null);
        if (calendar == null) {
            return ResponseEntity.notFound().build();
        }
        try {
            replaceCalendarSubjects(calendar, rows == null ? List.of() : rows);
            scheduleGeneratorService.clearCache();
            return ResponseEntity.ok(calendarSubjectRepository.findByCalendarIdWithSubject(id).stream()
                    .map(this::toCalendarSubjectDto)
                    .toList());
        } catch (IllegalArgumentException e) {
            return ResponseEntity.badRequest().body(Map.of("message", e.getMessage()));
        }
    }

    private void applyCalendar(AcademicCalendar calendar, AcademicCalendarDto request) {
        if (request == null) {
            throw new IllegalArgumentException("Передайте данные календарного графика");
        }
        if (request.courseCount() == null || request.courseCount() <= 0) {
            throw new IllegalArgumentException("Количество курсов должно быть больше нуля");
        }
        if (request.courseCount() > 8) {
            throw new IllegalArgumentException("Количество курсов не может быть больше 8");
        }
        if (request.academicYearId() == null || request.specialtyId() == null
                || request.specialtyProfileId() == null || request.studyFormId() == null) {
            throw new IllegalArgumentException("Учебный год, специальность, профиль и форма обучения обязательны");
        }

        AcademicYear year = academicYearRepository.findById(request.academicYearId())
                .orElseThrow(() -> new IllegalArgumentException("Учебный год не найден"));
        Speciality speciality = specialtiesRepository.findById(request.specialtyId())
                .orElseThrow(() -> new IllegalArgumentException("Специальность не найдена"));
        SpecialtyProfile profile = profileRepository.findById(request.specialtyProfileId())
                .orElseThrow(() -> new IllegalArgumentException("Профиль обучения не найден"));
        if (!profile.getSpeciality().getId().equals(speciality.getId())) {
            throw new IllegalArgumentException("Профиль не относится к выбранной специальности");
        }
        EducationForm studyForm = educationFormRepository.findById(request.studyFormId())
                .orElseThrow(() -> new IllegalArgumentException("Форма обучения не найдена"));

        calendar.setTitle(AcademicCalendarTitleBuilder.build(year, speciality, profile, studyForm));
        calendar.setAcademicYear(year);
        calendar.setSpeciality(speciality);
        calendar.setSpecialtyProfile(profile);
        calendar.setStudyForm(studyForm);
        calendar.setCourseCount(request.courseCount());
    }

    private void replaceCalendarSubjects(AcademicCalendar calendar, List<AcademicCalendarSubjectDto> rows) {
        if (rows.size() > 1000) {
            throw new IllegalArgumentException("Слишком много дисциплин для одного графика");
        }

        int maxSemesterNumber = Math.max(1, calendar.getCourseCount() == null ? 0 : calendar.getCourseCount() * 2);
        Set<Long> subjectIds = new HashSet<>();
        Set<String> uniqueRows = new HashSet<>();
        for (AcademicCalendarSubjectDto row : rows) {
            if (row == null || row.semesterNumber() == null || row.subjectId() == null) {
                throw new IllegalArgumentException("Семестр и дисциплина обязательны");
            }
            if (row.semesterNumber() < 1 || row.semesterNumber() > maxSemesterNumber) {
                throw new IllegalArgumentException("Номер семестра выходит за пределы графика");
            }
            if (!uniqueRows.add(row.semesterNumber() + ":" + row.subjectId())) {
                throw new IllegalArgumentException("Дисциплина уже привязана к этому семестру");
            }
            subjectIds.add(row.subjectId());
        }

        Map<Long, Subject> subjectsById = subjectRepository.findAllById(subjectIds).stream()
                .collect(Collectors.toMap(Subject::getId, subject -> subject, (left, right) -> left, HashMap::new));
        for (Long subjectId : subjectIds) {
            Subject subject = subjectsById.get(subjectId);
            if (subject == null) {
                throw new IllegalArgumentException("Дисциплина не найдена");
            }
            if (subject.isArchivedRecord()) {
                throw new IllegalArgumentException("Архивную дисциплину нельзя привязать к графику");
            }
        }

        calendarSubjectRepository.deleteByAcademicCalendar_Id(calendar.getId());
        calendarSubjectRepository.flush();
        List<AcademicCalendarSubject> entities = rows.stream()
                .map(row -> {
                    AcademicCalendarSubject calendarSubject = new AcademicCalendarSubject();
                    calendarSubject.setAcademicCalendar(calendar);
                    calendarSubject.setSemesterNumber(row.semesterNumber());
                    calendarSubject.setSubject(subjectsById.get(row.subjectId()));
                    return calendarSubject;
                })
                .toList();
        calendarSubjectRepository.saveAll(entities);
    }

    private AcademicCalendarDto toCalendarDto(AcademicCalendar calendar) {
        AcademicYear year = calendar.getAcademicYear();
        Speciality speciality = calendar.getSpeciality();
        SpecialtyProfile profile = calendar.getSpecialtyProfile();
        EducationForm studyForm = calendar.getStudyForm();
        return new AcademicCalendarDto(
                calendar.getId(),
                calendar.getTitle(),
                year.getId(),
                year.getTitle(),
                year.getStartDate(),
                year.getEndDate(),
                speciality.getId(),
                speciality.getSpecialityCode(),
                speciality.getSpecialityName(),
                profile.getId(),
                profile.getName(),
                studyForm.getId(),
                studyForm.getName(),
                calendar.getCourseCount()
        );
    }

    private AcademicCalendarGridDayDto toGridDayDto(AcademicCalendarDay day) {
        AcademicCalendarActivityType activityType = day.getActivityType();
        return new AcademicCalendarGridDayDto(
                day.getId(),
                day.getAcademicCalendar().getId(),
                day.getCourseNumber(),
                day.getDate(),
                day.getWeekNumber(),
                day.getDayOfWeek(),
                activityType.getId(),
                activityType.getCode(),
                activityType.getName(),
                activityType.getAllowSchedule()
        );
    }

    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()
        );
    }
}
