package com.example.dateplanner.controllers.rest;

import com.example.dateplanner.dto.*;
import com.example.dateplanner.models.entities.CourtBookingRequest;
import com.example.dateplanner.models.entities.TennisClub;
import com.example.dateplanner.models.entities.TennisEvent;
import com.example.dateplanner.repositories.CourtBookingRequestRepository;
import com.example.dateplanner.repositories.CourtRepository;
import com.example.dateplanner.repositories.TennisClubRepository;
import com.example.dateplanner.repositories.TennisEventRepository;
import com.example.dateplanner.services.ApplicationService;
import com.example.dateplanner.services.EmailService;
import com.example.dateplanner.services.RegistrationService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class ApiPublicController {

    private final TennisClubRepository tennisClubRepository;
    private final TennisEventRepository tennisEventRepository;
    private final CourtRepository courtRepository;
    private final CourtBookingRequestRepository bookingRequestRepository;
    private final RegistrationService registrationService;
    private final ApplicationService applicationService;
    private final EmailService emailService;

    // ── Публичные данные ──────────────────────────────────────────────────────

    @GetMapping("/clubs")
    public Flux<TennisClub> getAllClubs() {
        return tennisClubRepository.findAll();
    }

    @GetMapping("/clubs/{id}")
    public Mono<ClubPublicDto> getClubPublic(@PathVariable UUID id) {
        return tennisClubRepository.findById(id)
                .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)))
                .flatMap(club -> {
                    LocalDateTime now = LocalDateTime.now();

                    Mono<List<ClubPublicDto.CourtPublicDto>> courtsMono = courtRepository
                            .findByClubUuid(club.getUuid())
                            .filter(c -> Boolean.TRUE.equals(c.getIsVisible()))
                            .map(c -> {
                                ClubPublicDto.CourtPublicDto dto = new ClubPublicDto.CourtPublicDto();
                                dto.setId(c.getUuid());
                                dto.setName(c.getName());
                                dto.setDescription(c.getDescription());
                                dto.setPhoto(c.getPhoto());
                                dto.setPrice(c.getPrice());
                                dto.setCover(c.getCover());
                                dto.setIsOutdoor(c.getIsOutdoor());
                                return dto;
                            })
                            .collectList();

                    Mono<List<EventDto>> upcomingMono = tennisEventRepository
                            .findByClubUuid(club.getUuid())
                            .filter(e -> e.getStartDateTime() != null && !e.getStartDateTime().isBefore(now))
                            .sort(Comparator.comparing(TennisEvent::getStartDateTime))
                            .take(10)
                            .map(e -> toEventDto(e, club.getName()))
                            .collectList();

                    Mono<List<EventDto>> pastMono = tennisEventRepository
                            .findByClubUuid(club.getUuid())
                            .filter(e -> e.getStartDateTime() != null && e.getStartDateTime().isBefore(now))
                            .sort(Comparator.comparing(TennisEvent::getStartDateTime, Comparator.reverseOrder()))
                            .take(6)
                            .map(e -> toEventDto(e, club.getName()))
                            .collectList();

                    return Mono.zip(courtsMono, upcomingMono, pastMono)
                            .map(tuple -> toClubPublicDto(club, tuple.getT1(), tuple.getT2(), tuple.getT3()));
                });
    }

    @GetMapping("/events")
    public Mono<PageResponse<EventDto>> getEvents(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "9") int size) {

        return tennisEventRepository.findAll()
                .skip((long) page * size)
                .take(size)
                .collectList()
                .zipWith(tennisEventRepository.findAll().count())
                .flatMap(tuple -> {
                    List<TennisEvent> events = tuple.getT1();
                    long total = tuple.getT2();
                    Set<UUID> clubIds = events.stream()
                            .map(TennisEvent::getClubUuid)
                            .filter(Objects::nonNull)
                            .collect(Collectors.toSet());
                    return tennisClubRepository.findAllById(clubIds)
                            .collectMap(TennisClub::getUuid, TennisClub::getName)
                            .map(clubMap -> {
                                List<EventDto> dtos = events.stream()
                                        .map(e -> toDto(e, clubMap))
                                        .collect(Collectors.toList());
                                return new PageResponse<>(dtos, page, size, total);
                            });
                });
    }

    // ── Бронирование кортов ───────────────────────────────────────────────────

    @PostMapping("/clubs/{clubId}/booking-request")
    public Mono<ResponseEntity<Map<String, String>>> submitBookingRequest(
            @PathVariable UUID clubId,
            @RequestBody CourtBookingRequestDto dto) {

        // Validate required fields
        if (dto.getRequesterName() == null || dto.getRequesterName().isBlank()) {
            return badRequest("Укажите ваше имя");
        }
        if (dto.getRequesterEmail() == null || dto.getRequesterEmail().isBlank()) {
            return badRequest("Укажите email для связи");
        }
        if (dto.getBookingDate() == null || dto.getBookingDate().isBlank()) {
            return badRequest("Укажите дату");
        }
        if (dto.getStartTime() == null || dto.getStartTime().isBlank()) {
            return badRequest("Укажите время начала");
        }
        if (dto.getDurationMinutes() <= 0 || dto.getDurationMinutes() % 30 != 0) {
            return badRequest("Длительность должна быть кратна 30 минутам");
        }

        // Parse date/time
        LocalDate bookingDate;
        LocalTime startTime;
        try {
            bookingDate = LocalDate.parse(dto.getBookingDate());
            startTime = LocalTime.parse(dto.getStartTime());
        } catch (DateTimeParseException e) {
            return badRequest("Неверный формат даты или времени");
        }

        // Validate time is on 30-min boundary
        if (startTime.getMinute() % 30 != 0 || startTime.getSecond() != 0) {
            return badRequest("Время начала должно быть кратно 30 минутам (например, 10:00 или 10:30)");
        }

        LocalTime endTime = startTime.plusMinutes(dto.getDurationMinutes());

        return tennisClubRepository.findById(clubId)
                .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND, "Клуб не найден")))
                .flatMap(club -> {
                    CourtBookingRequest request = new CourtBookingRequest();
                    request.setClubUuid(clubId);
                    request.setCourtUuid(dto.getCourtId());
                    request.setRequesterName(dto.getRequesterName().trim());
                    request.setRequesterEmail(dto.getRequesterEmail().trim());
                    request.setRequesterPhone(dto.getRequesterPhone() != null ? dto.getRequesterPhone().trim() : null);
                    request.setBookingDate(bookingDate);
                    request.setStartTime(startTime);
                    request.setEndTime(endTime);
                    request.setMessage(dto.getMessage());
                    request.setStatus("NEW");
                    request.setCreatedAt(LocalDateTime.now());

                    return bookingRequestRepository.save(request).flatMap(saved -> {
                        // Send email to club if email is set
                        String clubEmail = club.getEmail();
                        if (clubEmail != null && !clubEmail.isBlank()) {
                            String courtLine = dto.getCourtId() != null
                                    ? "<br>Корт UUID: " + dto.getCourtId()
                                    : "";
                            String messageLine = dto.getMessage() != null && !dto.getMessage().isBlank()
                                    ? "<br>Сообщение: " + escapeHtml(dto.getMessage())
                                    : "";
                            String phoneLine = request.getRequesterPhone() != null
                                    ? "<br>Телефон: " + escapeHtml(request.getRequesterPhone())
                                    : "";
                            String body = "<h3>Новая заявка на аренду корта</h3>"
                                    + "<b>Клуб:</b> " + escapeHtml(club.getName())
                                    + "<br><b>Имя:</b> " + escapeHtml(request.getRequesterName())
                                    + "<br><b>Email:</b> " + escapeHtml(request.getRequesterEmail())
                                    + phoneLine
                                    + "<br><b>Дата:</b> " + bookingDate
                                    + "<br><b>Время:</b> " + startTime + " — " + endTime
                                    + courtLine
                                    + messageLine;
                            return emailService.send(clubEmail, "Заявка на аренду корта — " + club.getName(), body)
                                    .onErrorResume(e -> Mono.empty())
                                    .thenReturn(ResponseEntity.ok(Map.of("message", "Заявка отправлена. Клуб свяжется с вами.")));
                        }
                        return Mono.just(ResponseEntity.<Map<String, String>>ok(
                                Map.of("message", "Заявка отправлена. Клуб свяжется с вами.")));
                    });
                })
                .onErrorResume(ResponseStatusException.class, e ->
                        Mono.just(ResponseEntity.status(e.getStatusCode())
                                .body(Map.of("message", e.getReason() != null ? e.getReason() : "Ошибка"))))
                .onErrorResume(e ->
                        Mono.just(ResponseEntity.internalServerError()
                                .body(Map.of("message", "Ошибка при отправке заявки"))));
    }

    // ── Заявки на участие ─────────────────────────────────────────────────────

    @PostMapping("/events/{eventId}/apply")
    public Mono<ResponseEntity<Map<String, String>>> applyToEvent(
            @PathVariable UUID eventId,
            @RequestBody SubmitApplicationDto dto) {
        return applicationService.submit(eventId, dto)
                .map(app -> ResponseEntity.ok(Map.of("message", "Заявка успешно принята")))
                .onErrorResume(ResponseStatusException.class, e ->
                        Mono.just(ResponseEntity.status(e.getStatusCode())
                                .body(Map.of("message",
                                        e.getReason() != null ? e.getReason() : "Ошибка при подаче заявки"))))
                .onErrorResume(e ->
                        Mono.just(ResponseEntity.internalServerError()
                                .body(Map.of("message", "Ошибка при подаче заявки"))));
    }

    // ── Регистрация ───────────────────────────────────────────────────────────

    @PostMapping("/auth/register")
    public Mono<ResponseEntity<Map<String, String>>> register(@RequestBody Map<String, String> body) {
        String email = body.get("email");
        if (email == null || email.isBlank()) {
            return Mono.just(ResponseEntity.badRequest()
                    .body(Map.of("message", "Email обязателен")));
        }

        return registrationService.register(email.trim().toLowerCase())
                .then(Mono.just(ResponseEntity.ok(
                        Map.of("message", "Пароль отправлен на " + email))))
                .onErrorResume(IllegalArgumentException.class, e ->
                        Mono.just(ResponseEntity.badRequest()
                                .body(Map.of("message", e.getMessage()))))
                .onErrorResume(e ->
                        Mono.just(ResponseEntity.internalServerError()
                                .body(Map.of("message", e.getMessage()))));
    }

    // ── Конвертация ───────────────────────────────────────────────────────────

    private ClubPublicDto toClubPublicDto(TennisClub club,
                                          List<ClubPublicDto.CourtPublicDto> courts,
                                          List<EventDto> upcoming,
                                          List<EventDto> past) {
        ClubPublicDto dto = new ClubPublicDto();
        dto.setId(club.getUuid());
        dto.setName(club.getName());
        dto.setDescription(club.getDescription());
        dto.setAddress(club.getAddress());
        dto.setPhone(club.getPhone());
        dto.setEmail(club.getEmail());
        dto.setWebsite(club.getWebsite());
        dto.setImages(club.getImages());
        dto.setConveniences(club.getConveniences());
        dto.setContactPhone(club.getContactPhone());
        dto.setContactPerson(club.getContactPerson());
        dto.setTelegramLink(club.getTelegramLink());
        dto.setCourts(courts);
        dto.setUpcomingEvents(upcoming);
        dto.setPastEvents(past);
        return dto;
    }

    private EventDto toEventDto(TennisEvent event, String clubName) {
        EventDto dto = new EventDto();
        dto.setId(event.getUuid());
        dto.setClubUuid(event.getClubUuid());
        dto.setClubName(clubName);
        dto.setTitle(event.getTitle());
        dto.setDescription(event.getDescription());
        dto.setType(event.getType());
        dto.setStartDateTime(event.getStartDateTime());
        dto.setEndDateTime(event.getEndDateTime());
        dto.setMaxParticipants(event.getMaxParticipants());
        dto.setCurrentParticipants(event.getCurrentParticipants());
        dto.setTakePartPrice(event.getTakePartPrice());
        dto.setContactPhone(event.getContactPhone());
        dto.setImageUrl(event.getImageUrl());
        dto.setRegistrationOpen(event.getRegistrationOpen() == null || event.getRegistrationOpen());
        return dto;
    }

    private EventDto toDto(TennisEvent event, Map<UUID, String> clubMap) {
        EventDto dto = new EventDto();
        dto.setId(event.getUuid());
        dto.setClubUuid(event.getClubUuid());
        dto.setClubName(event.getClubUuid() != null ? clubMap.get(event.getClubUuid()) : null);
        dto.setTitle(event.getTitle());
        dto.setDescription(event.getDescription());
        dto.setType(event.getType());
        dto.setStartDateTime(event.getStartDateTime());
        dto.setEndDateTime(event.getEndDateTime());
        dto.setMaxParticipants(event.getMaxParticipants());
        dto.setCurrentParticipants(event.getCurrentParticipants());
        dto.setTakePartPrice(event.getTakePartPrice());
        dto.setContactPhone(event.getContactPhone());
        dto.setImageUrl(event.getImageUrl());
        dto.setRegistrationOpen(event.getRegistrationOpen() == null || event.getRegistrationOpen());
        return dto;
    }

    private Mono<ResponseEntity<Map<String, String>>> badRequest(String message) {
        return Mono.just(ResponseEntity.badRequest().body(Map.of("message", message)));
    }

    private String escapeHtml(String s) {
        if (s == null) return "";
        return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
    }
}
