package com.example.dateplanner.controllers.rest;

import com.example.dateplanner.dto.ClubWithCourtsDto;
import com.example.dateplanner.dto.CourtDto;
import com.example.dateplanner.dto.EventApplicationDto;
import com.example.dateplanner.dto.EventDto;
import com.example.dateplanner.dto.PageResponse;
import com.example.dateplanner.models.entities.AppUser;
import com.example.dateplanner.models.entities.Court;
import com.example.dateplanner.models.entities.EventApplication;
import com.example.dateplanner.models.entities.GalleryPost;
import com.example.dateplanner.models.entities.SiteSettings;
import com.example.dateplanner.models.entities.TennisClub;
import com.example.dateplanner.models.entities.TennisEvent;
import com.example.dateplanner.repositories.CourtRepository;
import com.example.dateplanner.repositories.EventApplicationRepository;
import com.example.dateplanner.repositories.GalleryPostRepository;
import com.example.dateplanner.repositories.SiteSettingsRepository;
import com.example.dateplanner.repositories.TennisClubRepository;
import com.example.dateplanner.repositories.TennisEventRepository;
import com.example.dateplanner.services.ApplicationService;
import com.example.dateplanner.services.MinioService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
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.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

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

    private final TennisClubRepository tennisClubRepository;
    private final TennisEventRepository tennisEventRepository;
    private final CourtRepository courtRepository;
    private final EventApplicationRepository applicationRepository;
    private final ApplicationService applicationService;
    private final MinioService minioService;
    private final SiteSettingsRepository siteSettingsRepository;
    private final GalleryPostRepository galleryPostRepository;

    // ── Загрузка изображений ───────────────────────────────────────────────────

    @PostMapping(value = "/upload/image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Mono<Map<String, String>> uploadImage(
            @RequestPart("file") FilePart filePart,
            @AuthenticationPrincipal AppUser user) {
        return minioService.uploadImage(filePart)
                .map(url -> Map.of("url", url));
    }

    // ── Клубы ─────────────────────────────────────────────────────────────────

    @GetMapping("/clubs")
    public Flux<ClubWithCourtsDto> getMyClubs(@AuthenticationPrincipal AppUser user) {
        return tennisClubRepository.findAll()
                .filter(c -> c.getOwnerUuid() != null && c.getOwnerUuid().equals(user.getUuid()))
                .map(this::convertToClubWithCourtsDto);
    }

    @PostMapping("/clubs")
    public Mono<TennisClub> createClub(@RequestBody TennisClub club,
                                       @AuthenticationPrincipal AppUser user) {
        club.setOwnerUuid(user.getUuid());
        club.setCreatedAt(LocalDateTime.now());
        club.setUpdatedAt(LocalDateTime.now());
        return tennisClubRepository.save(club);
    }

    @PutMapping("/clubs/{id}")
    public Mono<TennisClub> updateClub(@PathVariable UUID id,
                                       @RequestBody TennisClub body,
                                       @AuthenticationPrincipal AppUser user) {
        return tennisClubRepository.findById(id)
                .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)))
                .flatMap(existing -> {
                    if (!existing.getOwnerUuid().equals(user.getUuid())) {
                        return Mono.error(new ResponseStatusException(HttpStatus.FORBIDDEN));
                    }
                    existing.setName(body.getName());
                    existing.setDescription(body.getDescription());
                    existing.setAddress(body.getAddress());
                    existing.setPhone(body.getPhone());
                    existing.setEmail(body.getEmail());
                    existing.setWebsite(body.getWebsite());
                    existing.setConveniences(body.getConveniences());
                    existing.setImages(body.getImages());
                    existing.setContactPhone(body.getContactPhone());
                    existing.setContactPerson(body.getContactPerson());
                    existing.setTelegramLink(body.getTelegramLink());
                    existing.setUpdatedAt(LocalDateTime.now());
                    return tennisClubRepository.save(existing);
                });
    }

    // ── Мероприятия ───────────────────────────────────────────────────────────

    @GetMapping("/events")
    public Mono<PageResponse<EventDto>> getMyEvents(
            @AuthenticationPrincipal AppUser user,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") 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 -> convertToEventDto(e, clubMap))
                                        .collect(Collectors.toList());
                                return new PageResponse<>(dtos, page, size, total);
                            });
                });
    }

    @GetMapping("/events/by-club/{clubId}")
    public Flux<Map<String, Object>> getEventsByClub(@PathVariable UUID clubId,
                                                      @AuthenticationPrincipal AppUser user) {
        return tennisEventRepository.findByClubUuid(clubId)
                .sort(Comparator.comparing(TennisEvent::getStartDateTime,
                        Comparator.nullsLast(Comparator.naturalOrder())))
                .map(e -> {
                    Map<String, Object> m = new HashMap<>();
                    m.put("id", e.getUuid());
                    m.put("title", e.getTitle());
                    m.put("startDateTime", e.getStartDateTime() != null
                            ? e.getStartDateTime().toString() : null);
                    return m;
                });
    }

    @PatchMapping("/events/{id}/toggle-registration")
    public Mono<Map<String, Object>> toggleRegistration(@PathVariable UUID id,
                                                         @AuthenticationPrincipal AppUser user) {
        return tennisEventRepository.findById(id)
                .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)))
                .flatMap(event -> {
                    boolean next = !Boolean.TRUE.equals(event.getRegistrationOpen());
                    event.setRegistrationOpen(next);
                    event.setUpdatedAt(LocalDateTime.now());
                    return tennisEventRepository.save(event);
                })
                .map(event -> {
                    Map<String, Object> m = new HashMap<>();
                    m.put("registrationOpen", event.getRegistrationOpen());
                    return m;
                });
    }

    @PostMapping("/events")
    public Mono<TennisEvent> createEvent(@RequestBody TennisEvent event,
                                         @AuthenticationPrincipal AppUser user) {
        event.setCreatedAt(LocalDateTime.now());
        event.setUpdatedAt(LocalDateTime.now());
        return tennisEventRepository.save(event);
    }

    @PutMapping("/events/{id}")
    public Mono<TennisEvent> updateEvent(@PathVariable UUID id,
                                         @RequestBody TennisEvent body,
                                         @AuthenticationPrincipal AppUser user) {
        return tennisEventRepository.findById(id)
                .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)))
                .flatMap(existing -> {
                    existing.setTitle(body.getTitle());
                    existing.setDescription(body.getDescription());
                    existing.setType(body.getType());
                    existing.setClubUuid(body.getClubUuid());
                    existing.setStartDateTime(body.getStartDateTime());
                    existing.setEndDateTime(body.getEndDateTime());
                    existing.setMaxParticipants(body.getMaxParticipants());
                    existing.setTakePartPrice(body.getTakePartPrice());
                    existing.setRequirements(body.getRequirements());
                    existing.setContactPhone(body.getContactPhone());
                    existing.setImageUrl(body.getImageUrl());
                    existing.setUpdatedAt(LocalDateTime.now());
                    return tennisEventRepository.save(existing);
                });
    }

    // ── Заявки ────────────────────────────────────────────────────────────────

    @GetMapping("/applications")
    public Mono<PageResponse<EventApplicationDto>> getApplications(
            @AuthenticationPrincipal AppUser user,
            @RequestParam(required = false) UUID eventId,
            @RequestParam(required = false) UUID clubId,
            @RequestParam(defaultValue = "PENDING") String status,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {

        boolean allStatuses = "ALL".equalsIgnoreCase(status);

        Flux<EventApplication> source;
        if (eventId != null) {
            source = allStatuses
                    ? applicationRepository.findByEventUuid(eventId)
                    : applicationRepository.findByEventUuidAndStatus(eventId, status.toUpperCase());
        } else if (clubId != null) {
            source = allStatuses
                    ? applicationRepository.findByClubUuid(clubId)
                    : applicationRepository.findByClubUuidAndStatus(clubId, status.toUpperCase());
        } else {
            source = allStatuses
                    ? applicationRepository.findAll()
                    : applicationRepository.findByStatus(status.toUpperCase());
        }

        return source.collectList()
                .flatMap(all -> {
                    long total = all.size();
                    List<EventApplication> paged = all.stream()
                            .sorted(Comparator.comparing(EventApplication::getCreatedAt,
                                    Comparator.nullsLast(Comparator.reverseOrder())))
                            .skip((long) page * size)
                            .limit(size)
                            .collect(Collectors.toList());

                    Set<UUID> eventUuids = paged.stream()
                            .map(EventApplication::getEventUuid)
                            .filter(Objects::nonNull)
                            .collect(Collectors.toSet());
                    Set<UUID> clubUuids = paged.stream()
                            .map(EventApplication::getClubUuid)
                            .filter(Objects::nonNull)
                            .collect(Collectors.toSet());

                    Mono<Map<UUID, String>> eventTitlesMono = tennisEventRepository.findAllById(eventUuids)
                            .collectMap(TennisEvent::getUuid, TennisEvent::getTitle);
                    Mono<Map<UUID, String>> clubNamesMono = tennisClubRepository.findAllById(clubUuids)
                            .collectMap(TennisClub::getUuid, TennisClub::getName);

                    return Mono.zip(eventTitlesMono, clubNamesMono)
                            .map(tuple -> {
                                List<EventApplicationDto> dtos = paged.stream()
                                        .map(a -> toApplicationDto(a, tuple.getT1(), tuple.getT2()))
                                        .collect(Collectors.toList());
                                return new PageResponse<>(dtos, page, size, total);
                            });
                });
    }

    @GetMapping("/applications/counts")
    public Mono<Map<String, Long>> getApplicationCounts(
            @AuthenticationPrincipal AppUser user,
            @RequestParam(required = false) UUID eventId,
            @RequestParam(required = false) UUID clubId) {

        Mono<Long> pendingMono, approvedMono, rejectedMono;
        if (eventId != null) {
            pendingMono  = applicationRepository.countByEventUuidAndStatus(eventId, "PENDING");
            approvedMono = applicationRepository.countByEventUuidAndStatus(eventId, "APPROVED");
            rejectedMono = applicationRepository.countByEventUuidAndStatus(eventId, "REJECTED");
        } else if (clubId != null) {
            pendingMono  = applicationRepository.countByClubUuidAndStatus(clubId, "PENDING");
            approvedMono = applicationRepository.countByClubUuidAndStatus(clubId, "APPROVED");
            rejectedMono = applicationRepository.countByClubUuidAndStatus(clubId, "REJECTED");
        } else {
            pendingMono  = applicationRepository.countByStatus("PENDING");
            approvedMono = applicationRepository.countByStatus("APPROVED");
            rejectedMono = applicationRepository.countByStatus("REJECTED");
        }

        return Mono.zip(pendingMono, approvedMono, rejectedMono)
                .map(t -> {
                    Map<String, Long> m = new HashMap<>();
                    m.put("PENDING",  t.getT1());
                    m.put("APPROVED", t.getT2());
                    m.put("REJECTED", t.getT3());
                    return m;
                });
    }

    @PatchMapping("/applications/{id}/approve")
    public Mono<EventApplicationDto> approveApplication(@PathVariable UUID id,
                                                         @AuthenticationPrincipal AppUser user) {
        return applicationService.approve(id)
                .map(app -> toApplicationDto(app, Map.of(), Map.of()));
    }

    @PatchMapping("/applications/{id}/reject")
    public Mono<EventApplicationDto> rejectApplication(@PathVariable UUID id,
                                                        @RequestBody Map<String, String> body,
                                                        @AuthenticationPrincipal AppUser user) {
        String reason = body != null ? body.get("reason") : null;
        return applicationService.reject(id, reason)
                .map(app -> toApplicationDto(app, Map.of(), Map.of()));
    }

    // ── Корты ─────────────────────────────────────────────────────────────────

    @GetMapping("/courts/by-club/{clubId}")
    public Flux<CourtDto> getCourtsByClub(@PathVariable UUID clubId,
                                          @AuthenticationPrincipal AppUser user) {
        return courtRepository.findByClubUuid(clubId).map(this::toCourtDto);
    }

    @PostMapping("/courts")
    public Mono<CourtDto> createCourt(@RequestBody Court court,
                                      @AuthenticationPrincipal AppUser user) {
        if (court.getIsVisible() == null) court.setIsVisible(Boolean.TRUE);
        if (court.getIsOutdoor() == null) court.setIsOutdoor(Boolean.FALSE);
        court.setCreatedAt(LocalDateTime.now());
        court.setUpdatedAt(LocalDateTime.now());
        return courtRepository.save(court)
                .flatMap(saved -> tennisClubRepository.findById(saved.getClubUuid())
                        .flatMap(club -> {
                            List<UUID> ids = club.getCourtUuids() != null
                                    ? new ArrayList<>(club.getCourtUuids()) : new ArrayList<>();
                            if (!ids.contains(saved.getUuid())) ids.add(saved.getUuid());
                            club.setCourtUuids(ids);
                            return tennisClubRepository.save(club).thenReturn(saved);
                        })
                        .defaultIfEmpty(saved))
                .map(this::toCourtDto);
    }

    @PutMapping("/courts/{id}")
    public Mono<CourtDto> updateCourt(@PathVariable UUID id,
                                      @RequestBody Court body,
                                      @AuthenticationPrincipal AppUser user) {
        return courtRepository.findById(id)
                .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)))
                .flatMap(existing -> {
                    existing.setName(body.getName());
                    existing.setDescription(body.getDescription());
                    existing.setPhoto(body.getPhoto());
                    existing.setPrice(body.getPrice());
                    existing.setCover(body.getCover());
                    existing.setIsOutdoor(body.getIsOutdoor() != null ? body.getIsOutdoor() : Boolean.FALSE);
                    existing.setIsVisible(body.getIsVisible() != null ? body.getIsVisible() : Boolean.TRUE);
                    existing.setUpdatedAt(LocalDateTime.now());
                    return courtRepository.save(existing);
                })
                .map(this::toCourtDto);
    }

    @DeleteMapping("/courts/{id}")
    public Mono<Void> deleteCourt(@PathVariable UUID id,
                                  @AuthenticationPrincipal AppUser user) {
        return courtRepository.findById(id)
                .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)))
                .flatMap(court -> {
                    UUID courtUuid = court.getUuid();
                    UUID clubUuid  = court.getClubUuid();
                    Mono<Void> deleteMono = courtRepository.deleteById(courtUuid);
                    if (clubUuid == null) return deleteMono;
                    return deleteMono
                            .then(tennisClubRepository.findById(clubUuid))
                            .flatMap(club -> {
                                List<UUID> ids = club.getCourtUuids() != null
                                        ? new ArrayList<>(club.getCourtUuids()) : new ArrayList<>();
                                ids.remove(courtUuid);
                                club.setCourtUuids(ids);
                                return tennisClubRepository.save(club).then();
                            })
                            .then();
                });
    }

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

    private ClubWithCourtsDto convertToClubWithCourtsDto(TennisClub club) {
        ClubWithCourtsDto dto = new ClubWithCourtsDto();
        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.setCourtUuids(club.getCourtUuids());
        dto.setConveniences(club.getConveniences());
        dto.setContactPhone(club.getContactPhone());
        dto.setContactPerson(club.getContactPerson());
        dto.setTelegramLink(club.getTelegramLink());
        dto.setOwnerUuid(club.getOwnerUuid());
        dto.setCreatedAt(club.getCreatedAt());
        dto.setUpdatedAt(club.getUpdatedAt());
        dto.setCourts(List.of());
        return dto;
    }

    private EventDto convertToEventDto(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.setRequirements(event.getRequirements());
        dto.setContactPhone(event.getContactPhone());
        dto.setImageUrl(event.getImageUrl());
        dto.setRegistrationOpen(event.getRegistrationOpen() == null || event.getRegistrationOpen());
        dto.setCreatedAt(event.getCreatedAt());
        dto.setUpdatedAt(event.getUpdatedAt());
        return dto;
    }

    private CourtDto toCourtDto(Court court) {
        CourtDto dto = new CourtDto();
        dto.setId(court.getUuid());
        dto.setName(court.getName());
        dto.setDescription(court.getDescription());
        dto.setPhoto(court.getPhoto());
        dto.setPrice(court.getPrice());
        dto.setCover(court.getCover());
        dto.setIsOutdoor(court.getIsOutdoor());
        dto.setIsVisible(court.getIsVisible());
        return dto;
    }

    private EventApplicationDto toApplicationDto(EventApplication app,
            Map<UUID, String> eventTitles, Map<UUID, String> clubNames) {
        EventApplicationDto dto = new EventApplicationDto();
        dto.setId(app.getUuid());
        dto.setEventUuid(app.getEventUuid());
        dto.setEventTitle(eventTitles.getOrDefault(app.getEventUuid(), ""));
        dto.setClubUuid(app.getClubUuid());
        dto.setClubName(clubNames.getOrDefault(app.getClubUuid(), ""));
        dto.setFullName(app.getFullName());
        dto.setEmail(app.getEmail());
        dto.setBirthDate(app.getBirthDate());
        dto.setGender(app.getGender());
        dto.setPhone(app.getPhone());
        dto.setRatingPoints(app.getRatingPoints());
        dto.setStatus(app.getStatus());
        dto.setRejectionReason(app.getRejectionReason());
        dto.setCreatedAt(app.getCreatedAt());
        return dto;
    }

    // ── Настройки сайта ───────────────────────────────────────────────────────

    @GetMapping("/site-settings")
    public Mono<SiteSettings> getSiteSettings(@AuthenticationPrincipal AppUser user) {
        return siteSettingsRepository.findFirstByOrderByUpdatedAtDesc()
                .switchIfEmpty(Mono.defer(() -> {
                    SiteSettings settings = new SiteSettings();
                    settings.setHeroImageUrl(
                            "https://avatars.mds.yandex.net/get-altay/1880524/2a0000016ef11d02f18395e01a44f0ddbdb7/XXL_height");
                    settings.setBackgroundImageUrl(
                            "https://avatars.mds.yandex.net/get-altay/1880524/2a0000016ef11d02f18395e01a44f0ddbdb7/XXL_height");
                    return siteSettingsRepository.save(settings);
                }));
    }

    @PutMapping("/site-settings")
    public Mono<SiteSettings> updateSiteSettings(@RequestBody SiteSettings body,
                                                  @AuthenticationPrincipal AppUser user) {
        return siteSettingsRepository.findFirstByOrderByUpdatedAtDesc()
                .switchIfEmpty(Mono.defer(() -> {
                    SiteSettings settings = new SiteSettings();
                    return siteSettingsRepository.save(settings);
                }))
                .flatMap(existing -> {
                    existing.setHeroImageUrl(body.getHeroImageUrl());
                    existing.setBackgroundImageUrl(body.getBackgroundImageUrl());
                    existing.setUpdatedAt(LocalDateTime.now());
                    return siteSettingsRepository.save(existing);
                });
    }

    // Публичный endpoint для главной страницы
    @GetMapping("/site-settings/public")
    public Mono<SiteSettings> getPublicSiteSettings() {
        return siteSettingsRepository.findFirstByOrderByUpdatedAtDesc()
                .switchIfEmpty(Mono.defer(() -> {
                    SiteSettings settings = new SiteSettings();
                    settings.setHeroImageUrl(
                            "https://avatars.mds.yandex.net/get-altay/1880524/2a0000016ef11d02f18395e01a44f0ddbdb7/XXL_height");
                    settings.setBackgroundImageUrl(
                            "https://avatars.mds.yandex.net/get-altay/1880524/2a0000016ef11d02f18395e01a44f0ddbdb7/XXL_height");
                    return siteSettingsRepository.save(settings);
                }));
    }

    // ── Галерея ───────────────────────────────────────────────────────────────

    @GetMapping("/gallery")
    public Mono<PageResponse<GalleryPost>> getGalleryPosts(
            @AuthenticationPrincipal AppUser user,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {

        return galleryPostRepository.countPublished()
                .flatMap(total -> galleryPostRepository.findRecentPublished(size, page * size)
                        .collectList()
                        .map(posts -> new PageResponse<>(posts, page, size, total)));
    }

    @PostMapping("/gallery")
    public Mono<GalleryPost> createGalleryPost(@RequestBody GalleryPost post,
                                                @AuthenticationPrincipal AppUser user) {
        post.setCreatedAt(LocalDateTime.now());
        post.setUpdatedAt(LocalDateTime.now());
        if (post.getPublishedAt() == null && Boolean.TRUE.equals(post.getIsPublished())) {
            post.setPublishedAt(LocalDateTime.now());
        }
        return galleryPostRepository.save(post);
    }

    @PutMapping("/gallery/{id}")
    public Mono<GalleryPost> updateGalleryPost(@PathVariable UUID id,
                                                @RequestBody GalleryPost body,
                                                @AuthenticationPrincipal AppUser user) {
        return galleryPostRepository.findById(id)
                .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)))
                .flatMap(existing -> {
                    existing.setTitle(body.getTitle());
                    existing.setContent(body.getContent());
                    existing.setImageUrls(body.getImageUrls());
                    existing.setIsPublished(body.getIsPublished());
                    if (Boolean.TRUE.equals(body.getIsPublished()) && existing.getPublishedAt() == null) {
                        existing.setPublishedAt(LocalDateTime.now());
                    }
                    existing.setUpdatedAt(LocalDateTime.now());
                    return galleryPostRepository.save(existing);
                });
    }

    @DeleteMapping("/gallery/{id}")
    public Mono<Void> deleteGalleryPost(@PathVariable UUID id,
                                         @AuthenticationPrincipal AppUser user) {
        return galleryPostRepository.findById(id)
                .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)))
                .flatMap(post -> galleryPostRepository.deleteById(id));
    }
}
