package com.magistr.app.controller;

import com.magistr.app.config.auth.AuthContext;
import com.magistr.app.config.auth.RequireRoles;
import com.magistr.app.dto.CreateUserRequest;
import com.magistr.app.dto.DepartmentTransferRequest;
import com.magistr.app.dto.TeacherDepartmentAssignmentDto;
import com.magistr.app.dto.UserResponse;
import com.magistr.app.model.Department;
import com.magistr.app.model.LifecycleEntity;
import com.magistr.app.model.Role;
import com.magistr.app.model.TeacherDepartmentAssignment;
import com.magistr.app.model.User;
import com.magistr.app.repository.DepartmentRepository;
import com.magistr.app.repository.TeacherDepartmentAssignmentRepository;
import com.magistr.app.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDate;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@RestController
@RequestMapping("/api/users")
@RequireRoles({Role.ADMIN})
public class UserController {

    private static final Logger logger = LoggerFactory.getLogger(UserController.class);
    private final UserRepository userRepository;
    private final DepartmentRepository departmentRepository;
    private final TeacherDepartmentAssignmentRepository teacherDepartmentAssignmentRepository;
    private final BCryptPasswordEncoder passwordEncoder;

    public UserController(UserRepository userRepository,
                          BCryptPasswordEncoder passwordEncoder,
                          DepartmentRepository departmentRepository,
                          TeacherDepartmentAssignmentRepository teacherDepartmentAssignmentRepository) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
        this.departmentRepository = departmentRepository;
        this.teacherDepartmentAssignmentRepository = teacherDepartmentAssignmentRepository;
    }

    @GetMapping
    public List<UserResponse> getAllUsers(@RequestParam(defaultValue = "false") boolean includeArchived) {
        logger.info("Получен запрос на получение всех пользователей");
        List<User> users = includeArchived
                ? userRepository.findAll()
                : userRepository.findByStatusNot(LifecycleEntity.STATUS_ARCHIVED);
        List<UserResponse> response = users.stream()
                .map(this::toUserResponse)
                .toList();
        logger.info("Получено {} пользователей", response.size());
        return response;
    }

    @GetMapping("/teachers")
    @RequireRoles({Role.ADMIN, Role.EDUCATION_OFFICE, Role.DEPARTMENT, Role.SCHEDULE_VIEWER})
    public List<UserResponse> getTeachers() {
        logger.info("Запрос на получение пользователей с ролью 'Преподаватель'");
        List<UserResponse> response = userRepository.findByRoleAndStatusNot(Role.TEACHER, LifecycleEntity.STATUS_ARCHIVED).stream()
                .map(this::toUserResponse)
                .toList();
        logger.info("Получено {} преподавателей", response.size());
        return response;
    }

    @GetMapping("/teachers/{departmentId}")
    @RequireRoles({Role.ADMIN, Role.EDUCATION_OFFICE, Role.DEPARTMENT, Role.SCHEDULE_VIEWER})
    public ResponseEntity<?> getTeachersByDepartmentId(@PathVariable Long departmentId){
        if (!canAccessDepartment(departmentId)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN)
                    .body(Map.of("message", "Недостаточно прав для просмотра преподавателей этой кафедры"));
        }
        logger.info("Получен запрос на получение преподавателей для кафедры с ID - {}", departmentId);
        List<User> users = teachersForDepartment(departmentId, LocalDate.now());

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

        logger.info("Найдено {} преподавателей для кафедры с ID - {}", users.size(), departmentId);
        return ResponseEntity.ok(users.stream()
                .map(this::toUserResponse)
                .toList());
    }

    @PostMapping
    public ResponseEntity<?> createUser(@RequestBody CreateUserRequest request) {
        if (request == null) {
            String errorMessage = "Данные пользователя обязательны";
            logger.error("Ошибка валидации: {}", errorMessage);
            return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
        }
        logger.info("Получен запрос на создание нового пользователя: username = {}, fullName = {}, jobTitle = {}, departmentId = {}", request.getUsername(), request.getFullName(), request.getJobTitle(), request.getDepartmentId());

        if (request.getUsername() == null || request.getUsername().isBlank()) {
            String errorMessage = "Имя пользователя обязательно";
            logger.error("Ошибка валидации: {}", errorMessage);
            return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
        }
        if (request.getPassword() == null || request.getPassword().length() < 8) {
            String errorMessage = "Пароль минимум 8 символов";
            logger.error("Ошибка валидации: {}", errorMessage);
            return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
        }
        if (userRepository.findByUsername(request.getUsername()).isPresent()) {
            String errorMessage = "Пользователь уже существует";
            logger.error("Ошибка валидации: {}", errorMessage);
            return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
        }
        if (request.getFullName() == null || request.getFullName().isBlank()) {
            String errorMessage = "Имя пользователя обязательно";
            logger.error("Ошибка валидации: {}", errorMessage);
            return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
        }
        if (request.getJobTitle() == null || request.getJobTitle().isBlank()) {
            logger.info("Должность не была указана, установлено значение по умолчанию: 'Не указано'");
            request.setJobTitle("Не указано");
        }
        if (request.getDepartmentId() == null || request.getDepartmentId() == 0) {
            String errorMessage = "ID кафедры не может быть равен 0 или пустым";
            logger.error("Ошибка валидации: {}", errorMessage);
            return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
        }
        Department requestedDepartment = departmentRepository.findById(request.getDepartmentId()).orElse(null);
        if (requestedDepartment == null || requestedDepartment.isArchivedRecord()) {
            String errorMessage = "Активная кафедра не найдена";
            logger.error("Ошибка валидации: {}", errorMessage);
            return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
        }

        Role role;
        try {
            role = Role.valueOf(request.getRole());
        } catch (IllegalArgumentException | NullPointerException e) {
            logger.error("Ошибка при преобразовании роли: {}", e.getMessage());
            return ResponseEntity.badRequest().body(Map.of("message", "Недопустимая роль"));
        }

        User user = new User();
        user.setUsername(request.getUsername());
        user.setPassword(passwordEncoder.encode(request.getPassword()));
        user.setRole(role);
        user.setFullName(request.getFullName());
        user.setJobTitle(request.getJobTitle());
        user.setDepartmentId(request.getDepartmentId());
        userRepository.save(user);
        if (role == Role.TEACHER) {
            TeacherDepartmentAssignment assignment = new TeacherDepartmentAssignment();
            assignment.setTeacher(user);
            assignment.setDepartment(requestedDepartment);
            assignment.setValidFrom(LocalDate.now());
            assignment.setPrimaryAssignment(true);
            assignment.setComment("Начальная кафедра при создании пользователя");
            assignment.setCreatedBy(AuthContext.currentUserId());
            teacherDepartmentAssignmentRepository.save(assignment);
        }

        logger.info("Пользователь успешно создан с ID: {}", user.getId());
        return ResponseEntity.ok(toUserResponse(user));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<?> deleteUser(@PathVariable Long id) {
        logger.info("Получен запрос на удаление пользователя с ID: {}", id);
        User user = userRepository.findById(id).orElse(null);
        if (user == null) {
            logger.info("Пользователь с ID - {} не найден", id);
            return ResponseEntity.notFound().build();
        }
        user.archive("Пользователь архивирован");
        userRepository.save(user);
        logger.info("Пользователь с ID - {} успешно архивирован", id);
        return ResponseEntity.ok(Map.of("message", "Пользователь архивирован"));
    }

    @PostMapping("/{id}/restore")
    public ResponseEntity<?> restoreUser(@PathVariable Long id) {
        User user = userRepository.findById(id).orElse(null);
        if (user == null) {
            return ResponseEntity.notFound().build();
        }
        user.restore();
        userRepository.save(user);
        return ResponseEntity.ok(toUserResponse(user));
    }

    @GetMapping("/{id}/department-history")
    @RequireRoles({Role.ADMIN, Role.EDUCATION_OFFICE, Role.DEPARTMENT})
    public ResponseEntity<?> getDepartmentHistory(@PathVariable Long id) {
        if (!userRepository.existsById(id)) {
            return ResponseEntity.notFound().build();
        }
        var currentUser = AuthContext.getCurrentUser();
        if (currentUser != null
                && currentUser.role() == Role.DEPARTMENT
                && !teacherDepartmentAssignmentRepository.existsActiveAssignment(id, currentUser.departmentId(), LocalDate.now())) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN)
                    .body(Map.of("message", "Недостаточно прав для просмотра истории этого преподавателя"));
        }
        return ResponseEntity.ok(teacherDepartmentAssignmentRepository.findHistoryByTeacherId(id).stream()
                .map(this::toAssignmentDto)
                .toList());
    }

    @PostMapping("/{id}/department-transfer")
    @Transactional
    public ResponseEntity<?> transferTeacher(@PathVariable Long id, @RequestBody DepartmentTransferRequest request) {
        User teacher = userRepository.findById(id).orElse(null);
        if (teacher == null || teacher.getRole() != Role.TEACHER) {
            return ResponseEntity.badRequest().body(Map.of("message", "Преподаватель не найден"));
        }
        if (teacher.isArchivedRecord()) {
            return ResponseEntity.badRequest().body(Map.of("message", "Архивного преподавателя нельзя перевести"));
        }
        if (request.departmentId() == null) {
            return ResponseEntity.badRequest().body(Map.of("message", "Кафедра обязательна"));
        }
        Department department = departmentRepository.findById(request.departmentId()).orElse(null);
        if (department == null || department.isArchivedRecord()) {
            return ResponseEntity.badRequest().body(Map.of("message", "Активная кафедра не найдена"));
        }
        LocalDate validFrom = request.validFrom() == null ? LocalDate.now() : request.validFrom();

        teacherDepartmentAssignmentRepository.findOpenPrimaryByTeacherId(id)
                .ifPresent(current -> {
                    if (current.getValidFrom().isBefore(validFrom)) {
                        current.setValidTo(validFrom.minusDays(1));
                        teacherDepartmentAssignmentRepository.save(current);
                    } else {
                        teacherDepartmentAssignmentRepository.delete(current);
                    }
                });

        TeacherDepartmentAssignment assignment = new TeacherDepartmentAssignment();
        assignment.setTeacher(teacher);
        assignment.setDepartment(department);
        assignment.setValidFrom(validFrom);
        assignment.setPrimaryAssignment(true);
        assignment.setComment(request.comment());
        assignment.setCreatedBy(AuthContext.currentUserId());
        teacherDepartmentAssignmentRepository.save(assignment);

        teacher.setDepartmentId(department.getId());
        userRepository.save(teacher);

        return ResponseEntity.ok(toAssignmentDto(assignment));
    }

    @GetMapping("/teachers/by-department/{departmentId}")
    @RequireRoles({Role.ADMIN, Role.EDUCATION_OFFICE, Role.DEPARTMENT})
    public ResponseEntity<?> getTeachersByDepartmentAtDate(@PathVariable Long departmentId,
                                                           @RequestParam(required = false)
                                                           @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) {
        if (!canAccessDepartment(departmentId)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN)
                    .body(Map.of("message", "Недостаточно прав для просмотра преподавателей этой кафедры"));
        }
        LocalDate effectiveDate = date == null ? LocalDate.now() : date;
        return ResponseEntity.ok(teachersForDepartment(departmentId, effectiveDate).stream()
                .map(this::toUserResponse)
                .toList());
    }

    private UserResponse toUserResponse(User user) {
        String departmentName = user.getDepartmentId() == null
                ? null
                : departmentRepository.findById(user.getDepartmentId())
                .map(Department::getDepartmentName)
                .orElse("Неизвестно");
        UserResponse response = new UserResponse(
                user.getId(),
                user.getUsername(),
                user.getRole().name(),
                user.getFullName(),
                user.getJobTitle(),
                departmentName
        );
        response.setDepartmentId(user.getDepartmentId());
        response.setStatus(user.getStatus());
        return response;
    }

    private TeacherDepartmentAssignmentDto toAssignmentDto(TeacherDepartmentAssignment assignment) {
        return new TeacherDepartmentAssignmentDto(
                assignment.getId(),
                assignment.getTeacher().getId(),
                assignment.getTeacher().getFullName(),
                assignment.getDepartment().getId(),
                assignment.getDepartment().getDepartmentName(),
                assignment.getValidFrom(),
                assignment.getValidTo(),
                assignment.getPrimaryAssignment(),
                assignment.getComment()
        );
    }

    private List<User> teachersForDepartment(Long departmentId, LocalDate date) {
        Map<Long, User> teachersById = new LinkedHashMap<>();
        List<TeacherDepartmentAssignment> assignments = teacherDepartmentAssignmentRepository.findDepartmentTeachersAtDate(departmentId, date);
        if (assignments != null) {
            assignments.stream()
                    .map(TeacherDepartmentAssignment::getTeacher)
                    .filter(Objects::nonNull)
                    .filter(User::isActiveRecord)
                    .forEach(teacher -> teachersById.put(teacher.getId(), teacher));
        }
        List<User> directTeachers = userRepository.findByRoleAndDepartmentIdAndStatusNot(Role.TEACHER, departmentId, LifecycleEntity.STATUS_ARCHIVED);
        if (directTeachers != null) {
            directTeachers.forEach(teacher -> teachersById.putIfAbsent(teacher.getId(), teacher));
        }
        return List.copyOf(teachersById.values());
    }

    private boolean canAccessDepartment(Long departmentId) {
        var currentUser = AuthContext.getCurrentUser();
        return currentUser == null
                || currentUser.role() != Role.DEPARTMENT
                || (departmentId != null && departmentId.equals(currentUser.departmentId()));
    }
}
