package de.oaa.xxx.games.bdsm.controller;

import de.oaa.xxx.games.common.aufgaben.AufgabenGruppe;
import de.oaa.xxx.games.common.aufgaben.AufgabenGruppePage;
import de.oaa.xxx.games.common.entity.AufgabenGruppeEntity;
import de.oaa.xxx.games.common.entity.GruppenAboEntity;
import de.oaa.xxx.games.common.repository.AufgabenGruppeRepository;
import de.oaa.xxx.games.common.repository.GruppenAboRepository;
import de.oaa.xxx.user.UserEntity;
import de.oaa.xxx.user.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.Principal;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/abo")
@Transactional
public class AboController {

    private static final Logger LOGGER = LoggerFactory.getLogger(AboController.class);
    private static final int DEFAULT_PAGE_SIZE = 5;
    private static final int DISCOVER_PAGE_SIZE = 10;

    private final GruppenAboRepository aboRepository;
    private final AufgabenGruppeRepository gruppeRepository;
    private final UserService userService;

    public AboController(GruppenAboRepository aboRepository,
                         AufgabenGruppeRepository gruppeRepository,
                         UserService userService) {
        this.aboRepository  = aboRepository;
        this.gruppeRepository = gruppeRepository;
        this.userService = userService;
    }

    // ── Abonnierte Gruppen laden ──

    @GetMapping("/list")
    public ResponseEntity<AufgabenGruppePage> listSubscribed(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "" + DEFAULT_PAGE_SIZE) int size,
            Principal principal) {
        UserEntity user = userService.requireUser(principal);

        List<AufgabenGruppe> dtos = aboRepository.findByUserId(user.getUserId()).stream()
                .map(GruppenAboEntity::getAufgabenGruppe)
                .filter(g -> !g.isPrivateGruppe()) // ignoriere inzwischen wieder private Gruppen
                .map(g -> enrich(g, user.getUserId(), true))
                .sorted(Comparator.comparing(AufgabenGruppe::getName, String.CASE_INSENSITIVE_ORDER))
                .toList();

        return ResponseEntity.ok(manualPage(dtos, page, size));
    }

    // ── Entdecken ──

    @GetMapping("/discover")
    public ResponseEntity<AufgabenGruppePage> discover(
            @RequestParam(required = false) String name,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "" + DISCOVER_PAGE_SIZE) int size,
            Principal principal) {
        UserEntity user = userService.requireUser(principal);

        String namePattern = name != null && !name.isBlank() ? "%" + name.trim() + "%" : null;

        List<AufgabenGruppe> dtos = gruppeRepository
                .findPublicFromOthers(user.getUserId(), namePattern).stream()
                .map(g -> enrich(g, user.getUserId(), aboRepository.existsByUserIdAndAufgabenGruppe(user.getUserId(), g)))
                .sorted(Comparator.comparingLong(AufgabenGruppe::getSubscriberCount).reversed()
                        .thenComparing(AufgabenGruppe::getName, String.CASE_INSENSITIVE_ORDER))
                .toList();

        return ResponseEntity.ok(manualPage(dtos, page, size));
    }

    // ── Abonnieren ──

    @PostMapping("/{gruppenId}")
    public ResponseEntity<Void> subscribe(@PathVariable UUID gruppenId, Principal principal) {
        UserEntity user = userService.requireUser(principal);

        AufgabenGruppeEntity gruppe = gruppeRepository.findById(gruppenId).orElse(null);
        if (gruppe == null || gruppe.isPrivateGruppe() || user.getUserId().equals(gruppe.getUserId())) {
            return ResponseEntity.badRequest().build();
        }
        if (aboRepository.existsByUserIdAndAufgabenGruppe(user.getUserId(), gruppe)) {
            return ResponseEntity.ok().build();
        }
        GruppenAboEntity abo = new GruppenAboEntity();
        abo.setAboId(UUID.randomUUID());
        abo.setUserId(user.getUserId());
        abo.setAufgabenGruppe(gruppe);
        aboRepository.save(abo);
        LOGGER.info("User {} hat Gruppe {} abonniert", user.getUserId(), gruppenId);
        return ResponseEntity.status(201).build();
    }

    // ── Abonnement kündigen ──

    @DeleteMapping("/{gruppenId}")
    public ResponseEntity<Void> unsubscribe(@PathVariable UUID gruppenId, Principal principal) {
        UserEntity user = userService.requireUser(principal);

        AufgabenGruppeEntity gruppe = gruppeRepository.findById(gruppenId).orElse(null);
        if (gruppe == null) return ResponseEntity.noContent().build();

        aboRepository.deleteByUserIdAndAufgabenGruppe(user.getUserId(), gruppe);
        LOGGER.info("User {} hat Abo auf Gruppe {} beendet", user.getUserId(), gruppenId);
        return ResponseEntity.accepted().build();
    }

    // ── Hilfsmethoden ──

    private AufgabenGruppe enrich(AufgabenGruppeEntity entity, UUID userId, boolean subscribed) {
        AufgabenGruppe g = entity.toAufgabenGruppe();
        g.setSubscriberCount(aboRepository.countByAufgabenGruppe(entity));
        g.setSubscribed(subscribed);
        return g;
    }

    private AufgabenGruppePage manualPage(List<AufgabenGruppe> all, int page, int size) {
        int total = all.size();
        int start = page * size;
        List<AufgabenGruppe> content = start >= total ? List.of() : all.subList(start, Math.min(start + size, total));
        AufgabenGruppePage result = new AufgabenGruppePage();
        result.setContent(content);
        result.setCurrentPage(page);
        result.setTotalPages(total == 0 ? 1 : (int) Math.ceil((double) total / size));
        result.setTotalElements(total);
        return result;
    }

}
