package com.magistr.app.controller;

import com.magistr.app.config.auth.RequireRoles;
import com.magistr.app.dto.TimeSlotDateAssignmentDto;
import com.magistr.app.dto.TimeSlotDto;
import com.magistr.app.dto.TimeSlotScopeDto;
import com.magistr.app.model.TimeSlot;
import com.magistr.app.model.TimeSlotDateAssignment;
import com.magistr.app.model.TimeSlotScope;
import com.magistr.app.model.Role;
import com.magistr.app.repository.TimeSlotDateAssignmentRepository;
import com.magistr.app.repository.TimeSlotRepository;
import com.magistr.app.repository.TimeSlotScopeRepository;
import com.magistr.app.service.TimeSlotService;
import com.magistr.app.service.EffectiveTimeSlotService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

@RestController
@RequestMapping("/api/admin/time-slots")
@RequireRoles({Role.ADMIN, Role.EDUCATION_OFFICE})
public class TimeSlotAdminController {

    private static final String APPLY_MODE_DEFAULT = "DEFAULT";
    private static final String APPLY_MODE_WEEKDAY = "WEEKDAY";
    private static final String APPLY_MODE_MANUAL = "MANUAL";

    private final TimeSlotRepository timeSlotRepository;
    private final TimeSlotScopeRepository timeSlotScopeRepository;
    private final TimeSlotDateAssignmentRepository dateAssignmentRepository;
    private final TimeSlotService timeSlotService;
    private final EffectiveTimeSlotService effectiveTimeSlotService;

    public TimeSlotAdminController(TimeSlotRepository timeSlotRepository,
                                   TimeSlotScopeRepository timeSlotScopeRepository,
                                   TimeSlotDateAssignmentRepository dateAssignmentRepository,
                                   TimeSlotService timeSlotService,
                                   EffectiveTimeSlotService effectiveTimeSlotService) {
        this.timeSlotRepository = timeSlotRepository;
        this.timeSlotScopeRepository = timeSlotScopeRepository;
        this.dateAssignmentRepository = dateAssignmentRepository;
        this.timeSlotService = timeSlotService;
        this.effectiveTimeSlotService = effectiveTimeSlotService;
    }

    @GetMapping
    public List<TimeSlotDto> getAll() {
        return timeSlotRepository.findAllByOrderByTimeSlotScopeDisplayOrderAscOrderNumberAscStartTimeAsc().stream()
                .map(this::toDto)
                .toList();
    }

    @GetMapping("/effective")
    public ResponseEntity<?> getEffective(@RequestParam LocalDate date) {
        return ResponseEntity.ok(effectiveTimeSlotService.findEffective(date).stream().map(this::toDto).toList());
    }

    @GetMapping("/scopes")
    public List<TimeSlotScopeDto> getScopes() {
        return timeSlotScopeRepository.findAllByOrderByDisplayOrderAscNameAsc().stream()
                .map(this::toScopeDto)
                .toList();
    }

    @PostMapping("/scopes")
    public ResponseEntity<?> createScope(@RequestBody TimeSlotScopeDto request) {
        if (request.name() == null || request.name().isBlank()) {
            return ResponseEntity.badRequest().body(Map.of("message", "Название сетки обязательно"));
        }

        TimeSlotScope scope = new TimeSlotScope();
        scope.setCode("manual_" + UUID.randomUUID().toString().replace("-", ""));
        scope.setName(request.name().trim());
        scope.setApplyMode(APPLY_MODE_MANUAL);
        scope.setDayOfWeek(null);
        scope.setSystemScope(false);
        scope.setDisplayOrder(nextManualDisplayOrder());
        return ResponseEntity.ok(toScopeDto(timeSlotScopeRepository.save(scope)));
    }

    @PutMapping("/scopes/{id}")
    public ResponseEntity<?> updateScope(@PathVariable Long id, @RequestBody TimeSlotScopeDto request) {
        TimeSlotScope scope = timeSlotScopeRepository.findById(id).orElse(null);
        if (scope == null) {
            return ResponseEntity.notFound().build();
        }
        if (Boolean.TRUE.equals(scope.getSystemScope())) {
            return ResponseEntity.badRequest().body(Map.of("message", "Системную сетку нельзя изменять"));
        }
        if (request.name() == null || request.name().isBlank()) {
            return ResponseEntity.badRequest().body(Map.of("message", "Название сетки обязательно"));
        }
        scope.setName(request.name().trim());
        return ResponseEntity.ok(toScopeDto(timeSlotScopeRepository.save(scope)));
    }

    @DeleteMapping("/scopes/{id}")
    public ResponseEntity<?> deleteScope(@PathVariable Long id) {
        TimeSlotScope scope = timeSlotScopeRepository.findById(id).orElse(null);
        if (scope == null) {
            return ResponseEntity.notFound().build();
        }
        if (Boolean.TRUE.equals(scope.getSystemScope())) {
            return ResponseEntity.badRequest().body(Map.of("message", "Системную сетку нельзя удалить"));
        }
        timeSlotScopeRepository.delete(scope);
        return ResponseEntity.ok(Map.of("message", "Сетка времени удалена"));
    }

    @GetMapping("/date-assignments")
    public List<TimeSlotDateAssignmentDto> getDateAssignments(@RequestParam(required = false) LocalDate startDate,
                                                              @RequestParam(required = false) LocalDate endDate) {
        List<TimeSlotDateAssignment> assignments = startDate != null && endDate != null
                ? dateAssignmentRepository.findByDateBetweenOrderByDateAsc(startDate, endDate)
                : dateAssignmentRepository.findAllByOrderByDateAsc();
        return assignments.stream().map(this::toAssignmentDto).toList();
    }

    @PostMapping("/date-assignments")
    public ResponseEntity<?> saveDateAssignment(@RequestBody TimeSlotDateAssignmentDto request) {
        if (request.date() == null) {
            return ResponseEntity.badRequest().body(Map.of("message", "Дата обязательна"));
        }
        if (request.scopeId() == null) {
            return ResponseEntity.badRequest().body(Map.of("message", "Сетка времени обязательна"));
        }
        TimeSlotScope scope = timeSlotScopeRepository.findById(request.scopeId()).orElse(null);
        if (scope == null) {
            return ResponseEntity.badRequest().body(Map.of("message", "Сетка времени не найдена"));
        }
        if (!APPLY_MODE_MANUAL.equals(scope.getApplyMode())) {
            return ResponseEntity.badRequest().body(Map.of("message", "Вручную можно применять только пользовательские сетки"));
        }

        TimeSlotDateAssignment assignment = dateAssignmentRepository.findByDate(request.date())
                .orElseGet(TimeSlotDateAssignment::new);
        assignment.setDate(request.date());
        assignment.setTimeSlotScope(scope);
        return ResponseEntity.ok(toAssignmentDto(dateAssignmentRepository.save(assignment)));
    }

    @DeleteMapping("/date-assignments/{id}")
    public ResponseEntity<?> deleteDateAssignment(@PathVariable Long id) {
        if (!dateAssignmentRepository.existsById(id)) {
            return ResponseEntity.notFound().build();
        }
        dateAssignmentRepository.deleteById(id);
        return ResponseEntity.ok(Map.of("message", "Ручное назначение удалено"));
    }

    @PostMapping
    public ResponseEntity<?> create(@RequestBody TimeSlotDto request) {
        return ResponseEntity.ok(toDto(timeSlotService.create(request)));
    }

    @PutMapping("/{id}")
    public ResponseEntity<?> update(@PathVariable Long id, @RequestBody TimeSlotDto request) {
        return ResponseEntity.ok(toDto(timeSlotService.update(id, request)));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<?> delete(@PathVariable Long id) {
        timeSlotService.delete(id);
        return ResponseEntity.ok(Map.of("message", "Временной слот удалён"));
    }

    private int nextManualDisplayOrder() {
        return timeSlotScopeRepository.findAllByOrderByDisplayOrderAscNameAsc().stream()
                .map(TimeSlotScope::getDisplayOrder)
                .filter(value -> value != null)
                .max(Integer::compareTo)
                .orElse(100) + 10;
    }

    private TimeSlotDto toDto(TimeSlot timeSlot) {
        TimeSlotScope scope = timeSlot.getTimeSlotScope();
        return new TimeSlotDto(
                timeSlot.getId(),
                timeSlot.getOrderNumber(),
                scope == null ? null : scope.getId(),
                scope == null ? null : scope.getName(),
                scope == null ? null : scope.getApplyMode(),
                scope == null ? null : scope.getDayOfWeek(),
                timeSlot.getStartTime(),
                timeSlot.getEndTime(),
                timeSlot.getDurationMinutes()
        );
    }

    private TimeSlotScopeDto toScopeDto(TimeSlotScope scope) {
        return new TimeSlotScopeDto(
                scope.getId(),
                scope.getCode(),
                scope.getName(),
                scope.getApplyMode(),
                scope.getDayOfWeek(),
                scope.getSystemScope(),
                scope.getDisplayOrder()
        );
    }

    private TimeSlotDateAssignmentDto toAssignmentDto(TimeSlotDateAssignment assignment) {
        TimeSlotScope scope = assignment.getTimeSlotScope();
        return new TimeSlotDateAssignmentDto(
                assignment.getId(),
                assignment.getDate(),
                scope.getId(),
                scope.getName()
        );
    }
}
